diff --git a/core/deployment/src/main/java/io/quarkus/deployment/Capability.java b/core/deployment/src/main/java/io/quarkus/deployment/Capability.java index 5f3d5d46246d1f..700d8ad1163dd2 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/Capability.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/Capability.java @@ -32,10 +32,12 @@ public interface Capability { String LRA_PARTICIPANT = QUARKUS_PREFIX + ".lra.participant"; String JACKSON = QUARKUS_PREFIX + ".jackson"; + String JSONB = QUARKUS_PREFIX + ".jsonb"; - String KOTLIN = QUARKUS_PREFIX + ".kotlin"; + String JAXB = QUARKUS_PREFIX + ".jaxb"; + String JAXP = QUARKUS_PREFIX + ".jaxp"; - String JSONB = QUARKUS_PREFIX + ".jsonb"; + String KOTLIN = QUARKUS_PREFIX + ".kotlin"; String HAL = QUARKUS_PREFIX + ".hal"; diff --git a/docs/src/main/asciidoc/hibernate-orm.adoc b/docs/src/main/asciidoc/hibernate-orm.adoc index fb61012dc90d13..091cfe22f46bd7 100644 --- a/docs/src/main/asciidoc/hibernate-orm.adoc +++ b/docs/src/main/asciidoc/hibernate-orm.adoc @@ -1240,3 +1240,82 @@ to tell Quarkus it should be used in the default persistence unit. + For <>, use `@PersistenceUnitExtension("nameOfYourPU")` <2> Implement `org.hibernate.engine.jdbc.spi.StatementInspector`. + +[[json_xml_serialization_deserialization]] +== Customizing JSON/XML serialization/deserialization + +By default, Quarkus will try to automatically configure format mappers depending on available extensions. +Globally configured `ObjectMapper` (or `Jsonb`) will be used for serialization/deserialization operations when Jackson (or JSON-B) is available. +Jackson will take precedence if both Jackson and JSON-B are available at the same time. + +JSON and XML serialization/deserialization in Hibernate ORM can be customized by implementing a `org.hibernate.type.format.FormatMapper` +and annotating the implementation with the appropriate qualifiers: + +[source,java] +---- +@JsonFormat // <1> +@PersistenceUnitExtension // <2> +public class MyJsonFormatMapper implements FormatMapper { // <3> + @Override + public String inspect(String sql) { + // ... + return sql; + } + @Override + public T fromString(CharSequence charSequence, JavaType javaType, WrapperOptions wrapperOptions) { + // ... + } + + @Override + public String toString(T value, JavaType javaType, WrapperOptions wrapperOptions) { + // ... + } +} +---- +<1> Annotate the format mapper implementation with the `@JsonFormat` qualifier +to tell Quarkus that this mapper is specific to JSON serialization/deserialization. ++ +<2> Annotate the format mapper implementation with the `@PersistenceUnitExtension` qualifier +to tell Quarkus it should be used in the default persistence unit. ++ +For <>, use `@PersistenceUnitExtension("nameOfYourPU")` +<3> Implement `org.hibernate.type.format.FormatMapper`. + +In case of a custom XML format mapper, a different CDI qualifier must be applied: + +[source,java] +---- +@XmlFormat // <1> +@PersistenceUnitExtension // <2> +public class MyJsonFormatMapper implements FormatMapper { // <3> + @Override + public String inspect(String sql) { + // ... + return sql; + } + @Override + public T fromString(CharSequence charSequence, JavaType javaType, WrapperOptions wrapperOptions) { + // ... + } + + @Override + public String toString(T value, JavaType javaType, WrapperOptions wrapperOptions) { + // ... + } +} +---- +<1> Annotate the format mapper implementation with the `@XmlFormat` qualifier +to tell Quarkus that this mapper is specific to XML serialization/deserialization. ++ +<2> Annotate the format mapper implementation with the `@PersistenceUnitExtension` qualifier +to tell Quarkus it should be used in the default persistence unit. ++ +For <>, use `@PersistenceUnitExtension("nameOfYourPU")` +<3> Implement `org.hibernate.type.format.FormatMapper`. + +[NOTE] +==== +Format mappers *must* have both `@PersistenceUnitExtension` and either `@JsonFormat` or `@XmlFormat` CDI qualifiers applied. + +Having multiple JSON (or XML) format mappers registered for the same persistence unit will result in an exception, because of the ambiguity. +==== diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/ClassNames.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/ClassNames.java index 0639230faa6f68..ab0e5cd4e2ea08 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/ClassNames.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/ClassNames.java @@ -57,6 +57,9 @@ private static DotName createConstant(String fqcn) { public static final DotName INTERCEPTOR = createConstant("org.hibernate.Interceptor"); public static final DotName STATEMENT_INSPECTOR = createConstant("org.hibernate.resource.jdbc.spi.StatementInspector"); + public static final DotName FORMAT_MAPPER = createConstant("org.hibernate.type.format.FormatMapper"); + public static final DotName JSON_FORMAT = createConstant("io.quarkus.hibernate.orm.JsonFormat"); + public static final DotName XML_FORMAT = createConstant("io.quarkus.hibernate.orm.XmlFormat"); public static final List GENERATORS = List.of( createConstant("org.hibernate.generator.internal.CurrentTimestampGeneration"), diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java index 63d691520a6e80..fc207f65444c32 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java @@ -61,7 +61,8 @@ public class HibernateOrmCdiProcessor { ClassNames.TENANT_RESOLVER, ClassNames.TENANT_CONNECTION_RESOLVER, ClassNames.INTERCEPTOR, - ClassNames.STATEMENT_INSPECTOR); + ClassNames.STATEMENT_INSPECTOR, + ClassNames.FORMAT_MAPPER); @BuildStep AnnotationsTransformerBuildItem convertJpaResourceAnnotationsToQualifier( @@ -247,11 +248,13 @@ void generateDataSourceBeans(HibernateOrmRecorder recorder, @BuildStep void registerAnnotations(BuildProducer additionalBeans, BuildProducer beanDefiningAnnotations) { - // add the @PersistenceUnit and @PersistenceUnitExtension classes + // add the @PersistenceUnit, @PersistenceUnitExtension, @JsonFormat and @XmlFormat classes // otherwise they won't be registered as qualifiers additionalBeans.produce(AdditionalBeanBuildItem.builder() .addBeanClasses(ClassNames.QUARKUS_PERSISTENCE_UNIT.toString(), - ClassNames.PERSISTENCE_UNIT_EXTENSION.toString()) + ClassNames.PERSISTENCE_UNIT_EXTENSION.toString(), + ClassNames.JSON_FORMAT.toString(), + ClassNames.XML_FORMAT.toString()) .build()); // Register the default scope for @PersistenceUnitExtension and make such beans unremovable by default diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java index 8a45d4ceca3c57..62e2d41c5f8d48 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java @@ -326,7 +326,7 @@ public void configurationDescriptorBuilding( hibernateOrmConfig.database.ormCompatibilityVersion, Collections.emptyMap()), null, jpaModel.getXmlMappings(persistenceXmlDescriptorBuildItem.getDescriptor().getName()), - false, true)); + false, true, capabilities)); } if (impliedPU.shouldGenerateImpliedBlockingPersistenceUnit()) { @@ -1118,7 +1118,7 @@ private static void producePersistenceUnitDescriptorFromConfig( persistenceUnitConfig.unsupportedProperties), persistenceUnitConfig.multitenantSchemaDatasource.orElse(null), xmlMappings, - false, false)); + false, false, capabilities)); } private static void collectDialectConfig(String persistenceUnitName, diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/PersistenceUnitDescriptorBuildItem.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/PersistenceUnitDescriptorBuildItem.java index 9400a67468d6c8..5693396c0bbba0 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/PersistenceUnitDescriptorBuildItem.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/PersistenceUnitDescriptorBuildItem.java @@ -2,12 +2,16 @@ import java.util.Collection; import java.util.List; +import java.util.Optional; import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor; import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.deployment.Capabilities; +import io.quarkus.deployment.Capability; import io.quarkus.hibernate.orm.runtime.boot.QuarkusPersistenceUnitDefinition; import io.quarkus.hibernate.orm.runtime.boot.xml.RecordableXmlMapping; +import io.quarkus.hibernate.orm.runtime.customized.FormatMapperKind; import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationStaticDescriptor; import io.quarkus.hibernate.orm.runtime.recording.RecordedConfig; @@ -30,12 +34,14 @@ public final class PersistenceUnitDescriptorBuildItem extends MultiBuildItem { private final List xmlMappings; private final boolean isReactive; private final boolean fromPersistenceXml; + private final Optional jsonMapper; + private final Optional xmlMapper; public PersistenceUnitDescriptorBuildItem(ParsedPersistenceXmlDescriptor descriptor, String configurationName, RecordedConfig config, String multiTenancySchemaDataSource, List xmlMappings, - boolean isReactive, boolean fromPersistenceXml) { + boolean isReactive, boolean fromPersistenceXml, Capabilities capabilities) { this.descriptor = descriptor; this.configurationName = configurationName; this.config = config; @@ -43,6 +49,8 @@ public PersistenceUnitDescriptorBuildItem(ParsedPersistenceXmlDescriptor descrip this.xmlMappings = xmlMappings; this.isReactive = isReactive; this.fromPersistenceXml = fromPersistenceXml; + this.jsonMapper = json(capabilities); + this.xmlMapper = xml(capabilities); } public Collection getManagedClassNames() { @@ -80,6 +88,23 @@ public boolean isFromPersistenceXml() { public QuarkusPersistenceUnitDefinition asOutputPersistenceUnitDefinition( List integrationStaticDescriptors) { return new QuarkusPersistenceUnitDefinition(descriptor, configurationName, config, - xmlMappings, isReactive, fromPersistenceXml, integrationStaticDescriptors); + xmlMappings, isReactive, fromPersistenceXml, jsonMapper, xmlMapper, integrationStaticDescriptors); + } + + private Optional json(Capabilities capabilities) { + if (capabilities.isPresent(Capability.JACKSON)) { + return Optional.of(FormatMapperKind.JACKSON); + } + if (capabilities.isPresent(Capability.JSONB)) { + return Optional.of(FormatMapperKind.JSONB); + } + return Optional.empty(); + } + + private Optional xml(Capabilities capabilities) { + if (capabilities.isPresent(Capability.JAXB)) { + return Optional.of(FormatMapperKind.JAXB); + } + return Optional.empty(); } } diff --git a/extensions/hibernate-orm/runtime/pom.xml b/extensions/hibernate-orm/runtime/pom.xml index 264d3f851db533..a9c86cf7c75b27 100644 --- a/extensions/hibernate-orm/runtime/pom.xml +++ b/extensions/hibernate-orm/runtime/pom.xml @@ -143,6 +143,21 @@ io.quarkus quarkus-caffeine + + io.quarkus + quarkus-jackson + true + + + io.quarkus + quarkus-jsonb + true + + + io.quarkus + quarkus-jaxb + true + diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/JsonFormat.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/JsonFormat.java new file mode 100644 index 00000000000000..54d036ba9f92b2 --- /dev/null +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/JsonFormat.java @@ -0,0 +1,37 @@ +package io.quarkus.hibernate.orm; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import jakarta.enterprise.util.AnnotationLiteral; +import jakarta.inject.Qualifier; + +import org.hibernate.type.format.FormatMapper; + +/** + * CDI qualifier for beans implementing a {@link FormatMapper}. + *

+ * This mapper will be used by Hibernate ORM for serialization and deserialization of JSON properties. + *

+ * Must be used in a combination with a {@link PersistenceUnitExtension} qualifier to define the persistence + * unit the mapper should be associated with. + */ +@Target({ TYPE, FIELD, METHOD, PARAMETER }) +@Retention(RUNTIME) +@Documented +@Qualifier +public @interface JsonFormat { + class Literal extends AnnotationLiteral implements JsonFormat { + public static JsonFormat INSTANCE = new Literal(); + + private Literal() { + } + } +} diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/XmlFormat.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/XmlFormat.java new file mode 100644 index 00000000000000..89cd4e0f1ed9ba --- /dev/null +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/XmlFormat.java @@ -0,0 +1,37 @@ +package io.quarkus.hibernate.orm; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import jakarta.enterprise.util.AnnotationLiteral; +import jakarta.inject.Qualifier; + +import org.hibernate.type.format.FormatMapper; + +/** + * CDI qualifier for beans implementing a {@link FormatMapper}. + *

+ * This mapper will be used by Hibernate ORM for serialization and deserialization of XML properties. + *

+ * Must be used in a combination with a {@link PersistenceUnitExtension} qualifier to define the persistence + * unit the mapper should be associated with. + */ +@Target({ TYPE, FIELD, METHOD, PARAMETER }) +@Retention(RUNTIME) +@Documented +@Qualifier +public @interface XmlFormat { + class Literal extends AnnotationLiteral implements XmlFormat { + public static XmlFormat INSTANCE = new Literal(); + + private Literal() { + } + } +} diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/PersistenceUnitUtil.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/PersistenceUnitUtil.java index caf85a8de72495..b91894c052e683 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/PersistenceUnitUtil.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/PersistenceUnitUtil.java @@ -1,5 +1,7 @@ package io.quarkus.hibernate.orm.runtime; +import java.lang.annotation.Annotation; +import java.util.Arrays; import java.util.Comparator; import java.util.Locale; @@ -22,8 +24,10 @@ public static boolean isDefaultPersistenceUnit(String name) { } public static InjectableInstance singleExtensionInstanceForPersistenceUnit(Class beanType, - String persistenceUnitName) { - InjectableInstance instance = extensionInstanceForPersistenceUnit(beanType, persistenceUnitName); + String persistenceUnitName, + Annotation... additionalQualifiers) { + InjectableInstance instance = extensionInstanceForPersistenceUnit(beanType, persistenceUnitName, + additionalQualifiers); if (instance.isAmbiguous()) { throw new IllegalStateException(String.format(Locale.ROOT, "Multiple instances of %1$s were found for persistence unit %2$s. " @@ -33,9 +37,15 @@ public static InjectableInstance singleExtensionInstanceForPersistenceUni return instance; } - public static InjectableInstance extensionInstanceForPersistenceUnit(Class beanType, String persistenceUnitName) { - return Arc.container().select(beanType, - new PersistenceUnitExtension.Literal(persistenceUnitName)); + public static InjectableInstance extensionInstanceForPersistenceUnit(Class beanType, String persistenceUnitName, + Annotation... additionalQualifiers) { + if (additionalQualifiers.length == 0) { + return Arc.container().select(beanType, new PersistenceUnitExtension.Literal(persistenceUnitName)); + } else { + Annotation[] qualifiers = Arrays.copyOf(additionalQualifiers, additionalQualifiers.length + 1); + qualifiers[additionalQualifiers.length] = new PersistenceUnitExtension.Literal(persistenceUnitName); + return Arc.container().select(beanType, qualifiers); + } } public static class PersistenceUnitNameComparator implements Comparator { diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootEntityManagerFactoryBuilder.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootEntityManagerFactoryBuilder.java index 52db112ee6e5f5..0e1d9307ad5805 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootEntityManagerFactoryBuilder.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootEntityManagerFactoryBuilder.java @@ -30,8 +30,11 @@ import org.hibernate.tool.schema.spi.CommandAcceptanceException; import org.hibernate.tool.schema.spi.DelayedDropRegistryNotAvailableImpl; import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator; +import org.hibernate.type.format.FormatMapper; import io.quarkus.arc.InjectableInstance; +import io.quarkus.hibernate.orm.JsonFormat; +import io.quarkus.hibernate.orm.XmlFormat; import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil; import io.quarkus.hibernate.orm.runtime.RuntimeSettings; import io.quarkus.hibernate.orm.runtime.migration.MultiTenancyStrategy; @@ -211,6 +214,17 @@ protected void populate(String persistenceUnitName, SessionFactoryOptionsBuilder if (!statementInspectorInstance.isUnsatisfied()) { options.applyStatementInspector(statementInspectorInstance.get()); } + + InjectableInstance jsonFormatMapper = PersistenceUnitUtil.singleExtensionInstanceForPersistenceUnit( + FormatMapper.class, persistenceUnitName, JsonFormat.Literal.INSTANCE); + if (!jsonFormatMapper.isUnsatisfied()) { + options.applyJsonFormatMapper(jsonFormatMapper.get()); + } + InjectableInstance xmlFormatMapper = PersistenceUnitUtil.singleExtensionInstanceForPersistenceUnit( + FormatMapper.class, persistenceUnitName, XmlFormat.Literal.INSTANCE); + if (!xmlFormatMapper.isUnsatisfied()) { + options.applyXmlFormatMapper(xmlFormatMapper.get()); + } } private static class ServiceRegistryCloser implements SessionFactoryObserver { diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java index 3eabdd8e7bc79c..33f24af8d789bd 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java @@ -399,6 +399,15 @@ private MergedSettings mergeSettings(QuarkusPersistenceUnitDefinition puDefiniti } } + // If there's any mapping lib that we can work with available we'll set the default mapper: + if (puDefinition.getJsonMapperCreator().isPresent()) { + cfg.put(AvailableSettings.JSON_FORMAT_MAPPER, puDefinition.getJsonMapperCreator().get().create()); + } + // If there's any mapping lib that we can work with available we'll set the default mapper: + if (puDefinition.getXmlMapperCreator().isPresent()) { + cfg.put(AvailableSettings.XML_FORMAT_MAPPER, puDefinition.getXmlMapperCreator().get().create()); + } + return mergedSettings; } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/QuarkusPersistenceUnitDefinition.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/QuarkusPersistenceUnitDefinition.java index a59c32123a15bd..ce7bf595a82ba1 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/QuarkusPersistenceUnitDefinition.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/QuarkusPersistenceUnitDefinition.java @@ -2,10 +2,12 @@ import java.util.List; import java.util.Objects; +import java.util.Optional; import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; import io.quarkus.hibernate.orm.runtime.boot.xml.RecordableXmlMapping; +import io.quarkus.hibernate.orm.runtime.customized.FormatMapperKind; import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationStaticDescriptor; import io.quarkus.hibernate.orm.runtime.recording.RecordedConfig; import io.quarkus.runtime.annotations.RecordableConstructor; @@ -21,12 +23,16 @@ public final class QuarkusPersistenceUnitDefinition { private final List xmlMappings; private final boolean isReactive; private final boolean fromPersistenceXml; + private final Optional jsonMapperCreator; + private final Optional xmlMapperCreator; private final List integrationStaticDescriptors; public QuarkusPersistenceUnitDefinition(PersistenceUnitDescriptor persistenceUnitDescriptor, String configurationName, RecordedConfig config, List xmlMappings, boolean isReactive, boolean fromPersistenceXml, + Optional jsonMapperCreator, + Optional xmlMapperCreator, List integrationStaticDescriptors) { Objects.requireNonNull(persistenceUnitDescriptor); Objects.requireNonNull(config); @@ -36,6 +42,8 @@ public QuarkusPersistenceUnitDefinition(PersistenceUnitDescriptor persistenceUni this.xmlMappings = xmlMappings; this.isReactive = isReactive; this.fromPersistenceXml = fromPersistenceXml; + this.jsonMapperCreator = jsonMapperCreator; + this.xmlMapperCreator = xmlMapperCreator; this.integrationStaticDescriptors = integrationStaticDescriptors; } @@ -45,6 +53,8 @@ public QuarkusPersistenceUnitDefinition(RuntimePersistenceUnitDescriptor actualH List xmlMappings, boolean reactive, boolean fromPersistenceXml, + Optional jsonMapperCreator, + Optional xmlMapperCreator, List integrationStaticDescriptors) { Objects.requireNonNull(actualHibernateDescriptor); Objects.requireNonNull(config); @@ -53,6 +63,8 @@ public QuarkusPersistenceUnitDefinition(RuntimePersistenceUnitDescriptor actualH this.xmlMappings = xmlMappings; this.isReactive = reactive; this.fromPersistenceXml = fromPersistenceXml; + this.jsonMapperCreator = jsonMapperCreator; + this.xmlMapperCreator = xmlMapperCreator; this.integrationStaticDescriptors = integrationStaticDescriptors; } @@ -81,6 +93,14 @@ public boolean isFromPersistenceXml() { return fromPersistenceXml; } + public Optional getJsonMapperCreator() { + return jsonMapperCreator; + } + + public Optional getXmlMapperCreator() { + return xmlMapperCreator; + } + public List getIntegrationStaticDescriptors() { return integrationStaticDescriptors; } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/customized/FormatMapperKind.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/customized/FormatMapperKind.java new file mode 100644 index 00000000000000..1dec3cf5413e46 --- /dev/null +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/customized/FormatMapperKind.java @@ -0,0 +1,38 @@ +package io.quarkus.hibernate.orm.runtime.customized; + +import jakarta.json.bind.Jsonb; + +import org.hibernate.type.format.FormatMapper; +import org.hibernate.type.format.jackson.JacksonJsonFormatMapper; +import org.hibernate.type.format.jakartajson.JsonBJsonFormatMapper; +import org.hibernate.type.format.jaxb.JaxbXmlFormatMapper; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.quarkus.arc.Arc; + +public enum FormatMapperKind { + JACKSON { + @Override + public FormatMapper create() { + // NOTE: we are not creating a Jackson based XML mapper since that one + // requires an additional lib (jackson-dataformat-xml-2.15.2) being available + // as well as an XmlMapper instance instead of an ObjectMapper... + return new JacksonJsonFormatMapper(Arc.container().instance(ObjectMapper.class).get()); + } + }, + JSONB { + @Override + public FormatMapper create() { + return new JsonBJsonFormatMapper(Arc.container().instance(Jsonb.class).get()); + } + }, + JAXB { + @Override + public FormatMapper create() { + return new JaxbXmlFormatMapper(); + } + }; + + public abstract FormatMapper create(); +} diff --git a/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java b/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java index 9dff2f96a0122f..d8c2130bf24459 100644 --- a/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java +++ b/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java @@ -40,6 +40,7 @@ import io.quarkus.datasource.deployment.spi.DefaultDataSourceDbKindBuildItem; import io.quarkus.datasource.runtime.DataSourceBuildTimeConfig; import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig; +import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.BuildSteps; @@ -131,6 +132,7 @@ public void buildReactivePersistenceUnit( ApplicationArchivesBuildItem applicationArchivesBuildItem, LaunchModeBuildItem launchMode, JpaModelBuildItem jpaModel, + Capabilities capabilities, BuildProducer systemProperties, BuildProducer nativeImageResources, BuildProducer hotDeploymentWatchedFiles, @@ -191,7 +193,7 @@ public void buildReactivePersistenceUnit( persistenceUnitConfig.unsupportedProperties), null, jpaModel.getXmlMappings(reactivePU.getName()), - true, false)); + true, false, capabilities)); } } diff --git a/extensions/jaxb/runtime/pom.xml b/extensions/jaxb/runtime/pom.xml index 6498c57c868e2b..591b9481a3e67d 100644 --- a/extensions/jaxb/runtime/pom.xml +++ b/extensions/jaxb/runtime/pom.xml @@ -53,6 +53,9 @@ javax.xml.bind:jaxb-api org.jboss.spec.javax.xml.bind:jboss-jaxb-api_2.3_spec + + io.quarkus.jaxb + diff --git a/extensions/jaxp/runtime/pom.xml b/extensions/jaxp/runtime/pom.xml index 3b8b0f44298e6e..c670dfaf44b4d1 100644 --- a/extensions/jaxp/runtime/pom.xml +++ b/extensions/jaxp/runtime/pom.xml @@ -24,6 +24,11 @@ io.quarkus quarkus-extension-maven-plugin + + + io.quarkus.jaxp + + maven-compiler-plugin diff --git a/integration-tests/jpa-postgresql-withxml/pom.xml b/integration-tests/jpa-postgresql-withxml/pom.xml index 5b0dd69565c626..ba5468c9d8c21c 100644 --- a/integration-tests/jpa-postgresql-withxml/pom.xml +++ b/integration-tests/jpa-postgresql-withxml/pom.xml @@ -10,7 +10,7 @@ 4.0.0 quarkus-integration-test-jpa-postgresql-withxml - Quarkus - Integration Tests - JPA - PostgreSQL + Quarkus - Integration Tests - JPA - PostgreSQL with XML Module that contains JPA related tests running with the PostgreSQL database @@ -26,6 +26,10 @@ io.quarkus quarkus-jdbc-postgresql + + io.quarkus + quarkus-jaxb + @@ -79,6 +83,19 @@ + + io.quarkus + quarkus-jaxb-deployment + ${project.version} + pom + test + + + * + * + + + diff --git a/integration-tests/jpa-postgresql-withxml/src/main/java/io/quarkus/it/jpa/postgresql/EntityWithXml.java b/integration-tests/jpa-postgresql-withxml/src/main/java/io/quarkus/it/jpa/postgresql/EntityWithXml.java new file mode 100644 index 00000000000000..470ce79c8f3657 --- /dev/null +++ b/integration-tests/jpa-postgresql-withxml/src/main/java/io/quarkus/it/jpa/postgresql/EntityWithXml.java @@ -0,0 +1,74 @@ +package io.quarkus.it.jpa.postgresql; + +import java.time.LocalDate; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlRootElement; +import jakarta.xml.bind.annotation.adapters.XmlAdapter; +import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +import io.quarkus.runtime.annotations.RegisterForReflection; + +@Entity +public class EntityWithXml { + @Id + @GeneratedValue + Long id; + + @JdbcTypeCode(SqlTypes.SQLXML) + ToBeSerializedWithDateTime xml; + + public EntityWithXml() { + } + + public EntityWithXml(ToBeSerializedWithDateTime data) { + this.xml = data; + } + + @Override + public String toString() { + return "EntityWithXml{" + + "id=" + id + + ", xml=" + xml + + '}'; + } + + @RegisterForReflection + @XmlRootElement + public static class ToBeSerializedWithDateTime { + @XmlElement + @XmlJavaTypeAdapter(value = LocalDateXmlAdapter.class) + LocalDate date; + + public ToBeSerializedWithDateTime() { + } + + public ToBeSerializedWithDateTime(LocalDate date) { + this.date = date; + } + + @Override + public String toString() { + return "ToBeSerializedWithDateTime{" + + "date=" + date + + '}'; + } + } + + @RegisterForReflection + public static class LocalDateXmlAdapter extends XmlAdapter { + public LocalDate unmarshal(String string) { + return string == null ? null : LocalDate.parse(string); + } + + public String marshal(LocalDate localDate) { + return localDate == null ? null : localDate.toString(); + } + } +} diff --git a/integration-tests/jpa-postgresql-withxml/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java b/integration-tests/jpa-postgresql-withxml/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java index e8f1a786615cc3..fa06314f454a1f 100644 --- a/integration-tests/jpa-postgresql-withxml/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java +++ b/integration-tests/jpa-postgresql-withxml/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java @@ -9,6 +9,7 @@ import java.sql.SQLException; import java.sql.SQLXML; import java.sql.Statement; +import java.time.LocalDate; import java.util.List; import java.util.UUID; @@ -34,6 +35,9 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import io.quarkus.hibernate.orm.PersistenceUnit; +import io.quarkus.it.jpa.postgresql.otherpu.EntityWithXmlOtherPU; + /** * First we run a smoke test for basic Hibernate ORM functionality, * then we specifically focus on supporting the PgSQLXML mapping abilities for XML types: @@ -44,6 +48,9 @@ public class JPAFunctionalityTestEndpoint extends HttpServlet { @Inject EntityManagerFactory entityManagerFactory; + @Inject + @PersistenceUnit("other") + EntityManagerFactory otherEntityManagerFactory; @Inject DataSource ds; @@ -51,7 +58,7 @@ public class JPAFunctionalityTestEndpoint extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { try { - doStuffWithHibernate(entityManagerFactory); + doStuffWithHibernate(entityManagerFactory, otherEntityManagerFactory); doStuffWithDatasource(); } catch (Exception e) { reportException("An error occurred while performing Hibernate operations", e, resp); @@ -123,7 +130,8 @@ private void deleteXmlSchema(Connection con) { /** * Lists the various operations we want to test for: */ - private static void doStuffWithHibernate(EntityManagerFactory entityManagerFactory) { + private static void doStuffWithHibernate(EntityManagerFactory entityManagerFactory, + EntityManagerFactory otherEntityManagerFactory) { //Store some well known Person instances we can then test on: storeTestPersons(entityManagerFactory); @@ -133,6 +141,8 @@ private static void doStuffWithHibernate(EntityManagerFactory entityManagerFacto //Try a JPA named query: verifyJPANamedQuery(entityManagerFactory); + + doXmlStuff(entityManagerFactory, otherEntityManagerFactory); } private static void verifyJPANamedQuery(final EntityManagerFactory emf) { @@ -210,6 +220,47 @@ private static String randomName() { return UUID.randomUUID().toString(); } + private static void doXmlStuff(EntityManagerFactory emf, EntityManagerFactory otherEmf) { + try (EntityManager em = emf.createEntityManager()) { + EntityTransaction transaction = em.getTransaction(); + transaction.begin(); + + EntityWithXml entity = new EntityWithXml( + new EntityWithXml.ToBeSerializedWithDateTime(LocalDate.of(2023, 7, 28))); + em.persist(entity); + transaction.commit(); + + transaction.begin(); + List entities = em + .createQuery("select e from EntityWithXml e", EntityWithXml.class) + .getResultList(); + if (entities.isEmpty()) { + throw new AssertionError("No entities with XML were found"); + } + transaction.commit(); + + transaction.begin(); + em.createQuery("delete from EntityWithXml").executeUpdate(); + transaction.commit(); + } + + try (EntityManager em = otherEmf.createEntityManager()) { + EntityTransaction transaction = em.getTransaction(); + transaction.begin(); + EntityWithXmlOtherPU otherPU = new EntityWithXmlOtherPU( + new EntityWithXmlOtherPU.ToBeSerializedWithDateTime(LocalDate.of(2023, 7, 28))); + em.persist(otherPU); + transaction.commit(); + throw new AssertionError( + "Our custom XML format mapper throws exceptions. So we were expecting commit to fail, but it did not!"); + } catch (Exception e) { + if (!(e.getCause() instanceof IllegalArgumentException) + && !e.getCause().getMessage().contains("I cannot convert anything to XML")) { + throw new AssertionError("Transaction failed for a different reason than expected.", e); + } + } + } + private void reportException(String errorMessage, final Exception e, final HttpServletResponse resp) throws IOException { final PrintWriter writer = resp.getWriter(); if (errorMessage != null) { diff --git a/integration-tests/jpa-postgresql-withxml/src/main/java/io/quarkus/it/jpa/postgresql/otherpu/EntityWithXmlOtherPU.java b/integration-tests/jpa-postgresql-withxml/src/main/java/io/quarkus/it/jpa/postgresql/otherpu/EntityWithXmlOtherPU.java new file mode 100644 index 00000000000000..c8f16813eca444 --- /dev/null +++ b/integration-tests/jpa-postgresql-withxml/src/main/java/io/quarkus/it/jpa/postgresql/otherpu/EntityWithXmlOtherPU.java @@ -0,0 +1,57 @@ + +package io.quarkus.it.jpa.postgresql.otherpu; + +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 io.quarkus.runtime.annotations.RegisterForReflection; + +@Entity +public class EntityWithXmlOtherPU { + @Id + @GeneratedValue + Long id; + + @JdbcTypeCode(SqlTypes.SQLXML) + ToBeSerializedWithDateTime xml; + + public EntityWithXmlOtherPU() { + } + + public EntityWithXmlOtherPU(ToBeSerializedWithDateTime data) { + this.xml = data; + } + + @Override + public String toString() { + return "EntityWithXmlOtherPU{" + + "id=" + id + + ", xml" + xml + + '}'; + } + + @RegisterForReflection + public static class ToBeSerializedWithDateTime { + LocalDate date; + + public ToBeSerializedWithDateTime() { + } + + public ToBeSerializedWithDateTime(LocalDate date) { + this.date = date; + } + + @Override + public String toString() { + return "ToBeSerializedWithDateTime{" + + "date=" + date + + '}'; + } + } +} diff --git a/integration-tests/jpa-postgresql-withxml/src/main/java/io/quarkus/it/jpa/postgresql/otherpu/XmlFormatMapper.java b/integration-tests/jpa-postgresql-withxml/src/main/java/io/quarkus/it/jpa/postgresql/otherpu/XmlFormatMapper.java new file mode 100644 index 00000000000000..8c9c57bc003ed1 --- /dev/null +++ b/integration-tests/jpa-postgresql-withxml/src/main/java/io/quarkus/it/jpa/postgresql/otherpu/XmlFormatMapper.java @@ -0,0 +1,23 @@ +package io.quarkus.it.jpa.postgresql.otherpu; + +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.format.FormatMapper; + +import io.quarkus.hibernate.orm.PersistenceUnitExtension; +import io.quarkus.hibernate.orm.XmlFormat; + +@XmlFormat +@PersistenceUnitExtension("other") +public class XmlFormatMapper implements FormatMapper { + + @Override + public T fromString(CharSequence charSequence, JavaType javaType, WrapperOptions wrapperOptions) { + throw new UnsupportedOperationException("I cannot convert anything from XML."); + } + + @Override + public String toString(T value, JavaType javaType, WrapperOptions wrapperOptions) { + throw new UnsupportedOperationException("I cannot convert anything to XML."); + } +} diff --git a/integration-tests/jpa-postgresql-withxml/src/main/resources/application.properties b/integration-tests/jpa-postgresql-withxml/src/main/resources/application.properties index b93311019b0499..c0ff6325328d00 100644 --- a/integration-tests/jpa-postgresql-withxml/src/main/resources/application.properties +++ b/integration-tests/jpa-postgresql-withxml/src/main/resources/application.properties @@ -3,6 +3,7 @@ quarkus.datasource.password=hibernate_orm_test quarkus.datasource.jdbc.url=${postgres.url} quarkus.datasource.jdbc.max-size=8 +quarkus.hibernate-orm.packages=io.quarkus.it.jpa.postgresql quarkus.hibernate-orm.database.generation=drop-and-create #Necessary for assertions in JPAFunctionalityInGraalITCase: @@ -10,3 +11,7 @@ quarkus.native.enable-reports=true #Useful to get some more insight in the trigger: quarkus.native.additional-build-args=-J-Dio.quarkus.jdbc.postgresql.graalvm.diagnostics=true + +# Define non-default PU so that we can configure a custom XML format mapper. The default PU is using the default mapper. +quarkus.hibernate-orm."other".datasource= +quarkus.hibernate-orm."other".packages=io.quarkus.it.jpa.postgresql.otherpu \ No newline at end of file diff --git a/integration-tests/jpa-postgresql/pom.xml b/integration-tests/jpa-postgresql/pom.xml index 980645fc1e6b57..15ce75578bbb93 100644 --- a/integration-tests/jpa-postgresql/pom.xml +++ b/integration-tests/jpa-postgresql/pom.xml @@ -26,6 +26,10 @@ io.quarkus quarkus-jdbc-postgresql + + io.quarkus + quarkus-jackson + @@ -89,6 +93,19 @@ + + io.quarkus + quarkus-jackson-deployment + ${project.version} + pom + test + + + * + * + + + diff --git a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/EntityWithJson.java b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/EntityWithJson.java new file mode 100644 index 00000000000000..e513a7d8b1d232 --- /dev/null +++ b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/EntityWithJson.java @@ -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) + ToBeSerializedWithDateTime json; + + public EntityWithJson() { + } + + public EntityWithJson(ToBeSerializedWithDateTime data) { + this.json = data; + } + + @Override + public String toString() { + return "EntityWithJson{" + + "id=" + id + + ", json=" + json + + '}'; + } + + @RegisterForReflection + public static class ToBeSerializedWithDateTime { + @JsonProperty + LocalDate date; + + public ToBeSerializedWithDateTime() { + } + + public ToBeSerializedWithDateTime(LocalDate date) { + this.date = date; + } + + @Override + public String toString() { + return "ToBeSerializedWithDateTime{" + + "date=" + date + + '}'; + } + } +} diff --git a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java index ac23341e763f4f..5f5f9e2991db8c 100644 --- a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java +++ b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.time.Duration; +import java.time.LocalDate; import java.util.List; import java.util.UUID; @@ -19,6 +20,9 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import io.quarkus.hibernate.orm.PersistenceUnit; +import io.quarkus.it.jpa.postgresql.otherpu.EntityWithJsonOtherPU; + /** * Various tests covering JPA functionality. All tests should work in both standard JVM and in native mode. */ @@ -27,11 +31,14 @@ public class JPAFunctionalityTestEndpoint extends HttpServlet { @Inject EntityManagerFactory entityManagerFactory; + @Inject + @PersistenceUnit("other") + EntityManagerFactory otherEntityManagerFactory; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { try { - doStuffWithHibernate(entityManagerFactory); + doStuffWithHibernate(entityManagerFactory, otherEntityManagerFactory); } catch (Exception e) { reportException("An error occurred while performing Hibernate operations", e, resp); } @@ -41,7 +48,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO /** * Lists the various operations we want to test for: */ - private static void doStuffWithHibernate(EntityManagerFactory entityManagerFactory) { + private static void doStuffWithHibernate(EntityManagerFactory entityManagerFactory, + EntityManagerFactory otherEntityManagerFactory) { //Cleanup any existing data: deleteAllPerson(entityManagerFactory); @@ -59,6 +67,9 @@ private static void doStuffWithHibernate(EntityManagerFactory entityManagerFacto // Try an entity using a UUID verifyUUIDEntity(entityManagerFactory); + + doJsonStuff(entityManagerFactory, otherEntityManagerFactory); + } private static void verifyJPANamedQuery(final EntityManagerFactory emf) { @@ -167,6 +178,47 @@ private static void verifyUUIDEntity(final EntityManagerFactory emf) { em.close(); } + private static void doJsonStuff(EntityManagerFactory emf, EntityManagerFactory otherEmf) { + try (EntityManager em = emf.createEntityManager()) { + EntityTransaction transaction = em.getTransaction(); + transaction.begin(); + + EntityWithJson entity = new EntityWithJson( + new EntityWithJson.ToBeSerializedWithDateTime(LocalDate.of(2023, 7, 28))); + em.persist(entity); + transaction.commit(); + + transaction.begin(); + List entities = em + .createQuery("select e from EntityWithJson e", EntityWithJson.class) + .getResultList(); + if (entities.isEmpty()) { + throw new AssertionError("No entities with json were found"); + } + transaction.commit(); + + transaction.begin(); + em.createQuery("delete from EntityWithJson").executeUpdate(); + transaction.commit(); + } + + try (EntityManager em = otherEmf.createEntityManager()) { + EntityTransaction transaction = em.getTransaction(); + transaction.begin(); + EntityWithJsonOtherPU otherPU = new EntityWithJsonOtherPU( + new EntityWithJsonOtherPU.ToBeSerializedWithDateTime(LocalDate.of(2023, 7, 28))); + em.persist(otherPU); + transaction.commit(); + throw new AssertionError( + "Default mapper cannot process date/time properties. So we were expecting commit to fail, but it did not!"); + } catch (Exception e) { + if (!(e.getCause() instanceof IllegalArgumentException) + && !e.getCause().getMessage().contains("I cannot convert anything to JSON")) { + throw new AssertionError("Transaction failed for a different reason than expected.", e); + } + } + } + private void reportException(String errorMessage, final Exception e, final HttpServletResponse resp) throws IOException { final PrintWriter writer = resp.getWriter(); if (errorMessage != null) { diff --git a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/otherpu/EntityWithJsonOtherPU.java b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/otherpu/EntityWithJsonOtherPU.java new file mode 100644 index 00000000000000..0793b93f3fcf66 --- /dev/null +++ b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/otherpu/EntityWithJsonOtherPU.java @@ -0,0 +1,60 @@ + +package io.quarkus.it.jpa.postgresql.otherpu; + +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 EntityWithJsonOtherPU { + @Id + @GeneratedValue + Long id; + + @JdbcTypeCode(SqlTypes.JSON) + ToBeSerializedWithDateTime json; + + public EntityWithJsonOtherPU() { + } + + public EntityWithJsonOtherPU(ToBeSerializedWithDateTime data) { + this.json = data; + } + + @Override + public String toString() { + return "EntityWithJsonOtherPU{" + + "id=" + id + + ", json=" + json + + '}'; + } + + @RegisterForReflection + public static class ToBeSerializedWithDateTime { + @JsonProperty + LocalDate date; + + public ToBeSerializedWithDateTime() { + } + + public ToBeSerializedWithDateTime(LocalDate date) { + this.date = date; + } + + @Override + public String toString() { + return "ToBeSerializedWithDateTime{" + + "date=" + date + + '}'; + } + } +} diff --git a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/otherpu/JsonFormatMapper.java b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/otherpu/JsonFormatMapper.java new file mode 100644 index 00000000000000..70925dc93bf48c --- /dev/null +++ b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/otherpu/JsonFormatMapper.java @@ -0,0 +1,23 @@ +package io.quarkus.it.jpa.postgresql.otherpu; + +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.format.FormatMapper; + +import io.quarkus.hibernate.orm.JsonFormat; +import io.quarkus.hibernate.orm.PersistenceUnitExtension; + +@JsonFormat +@PersistenceUnitExtension("other") +public class JsonFormatMapper implements FormatMapper { + + @Override + public T fromString(CharSequence charSequence, JavaType javaType, WrapperOptions wrapperOptions) { + throw new UnsupportedOperationException("I cannot convert anything from JSON."); + } + + @Override + public String toString(T value, JavaType javaType, WrapperOptions wrapperOptions) { + throw new UnsupportedOperationException("I cannot convert anything to JSON."); + } +} diff --git a/integration-tests/jpa-postgresql/src/main/resources/application.properties b/integration-tests/jpa-postgresql/src/main/resources/application.properties index c3f79713879977..90370fd6289842 100644 --- a/integration-tests/jpa-postgresql/src/main/resources/application.properties +++ b/integration-tests/jpa-postgresql/src/main/resources/application.properties @@ -3,8 +3,12 @@ quarkus.datasource.password=hibernate_orm_test quarkus.datasource.jdbc.url=${postgres.url} quarkus.datasource.jdbc.max-size=8 +quarkus.hibernate-orm.packages=io.quarkus.it.jpa.postgresql quarkus.hibernate-orm.database.generation=drop-and-create quarkus.hibernate-orm.database.generation.create-schemas=true +# Define non-default PU so that we can configure a custom JSON format mapper. The default PU is using the default mapper. +quarkus.hibernate-orm."other".datasource= +quarkus.hibernate-orm."other".packages=io.quarkus.it.jpa.postgresql.otherpu #Necessary for assertions in JPAFunctionalityInGraalITCase: quarkus.native.enable-reports=true \ No newline at end of file