Skip to content

Commit c33cb99

Browse files
committed
Warn users about the use of built-in Quarkus' format mappers
1 parent 8535990 commit c33cb99

File tree

14 files changed

+257
-22
lines changed

14 files changed

+257
-22
lines changed

extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfig.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil;
88
import io.quarkus.hibernate.orm.runtime.config.DatabaseOrmCompatibilityVersion;
9+
import io.quarkus.hibernate.orm.runtime.customized.BuiltinFormatMapperBehaviour;
910
import io.quarkus.runtime.annotations.ConfigDocMapKey;
1011
import io.quarkus.runtime.annotations.ConfigDocSection;
1112
import io.quarkus.runtime.annotations.ConfigGroup;
@@ -53,6 +54,13 @@ public interface HibernateOrmConfig {
5354
@ConfigDocSection
5455
HibernateOrmConfigDatabase database();
5556

57+
/**
58+
* JSON/XML mapping related configuration.
59+
*/
60+
@Deprecated(since = "3.24", forRemoval = true)
61+
@ConfigDocSection
62+
HibernateOrmConfigMapping mapping();
63+
5664
/**
5765
* Configuration for persistence units.
5866
*/
@@ -210,4 +218,24 @@ interface HibernateOrmConfigDevUI {
210218
@WithDefault("false")
211219
boolean allowHql();
212220
}
221+
222+
@ConfigGroup
223+
interface HibernateOrmConfigMapping {
224+
225+
/**
226+
* Mapping format.
227+
*/
228+
HibernateOrmConfigMappingFormat format();
229+
230+
@ConfigGroup
231+
interface HibernateOrmConfigMappingFormat {
232+
/**
233+
* How the default JSON/XML format mappers are configured.
234+
*
235+
* @deprecated Only available to mitigate migration from the Quarkus preconfigured current mappers
236+
*/
237+
@WithDefault("warn")
238+
BuiltinFormatMapperBehaviour global();
239+
}
240+
}
213241
}

extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -324,8 +324,9 @@ public void configurationDescriptorBuilding(
324324
.filter(i -> i.isDefault())
325325
.findFirst();
326326
collectDialectConfigForPersistenceXml(puName, xmlDescriptor);
327-
Optional<FormatMapperKind> jsonMapper = jsonMapperKind(capabilities);
328-
Optional<FormatMapperKind> xmlMapper = xmlMapperKind(capabilities);
327+
Optional<FormatMapperKind> jsonMapper = jsonMapperKind(capabilities,
328+
hibernateOrmConfig.mapping().format().global());
329+
Optional<FormatMapperKind> xmlMapper = xmlMapperKind(capabilities, hibernateOrmConfig.mapping().format().global());
329330
jsonMapper.flatMap(FormatMapperKind::requiredBeanType)
330331
.ifPresent(type -> unremovableBeans.produce(UnremovableBeanBuildItem.beanClassNames(type)));
331332
xmlMapper.flatMap(FormatMapperKind::requiredBeanType)
@@ -341,7 +342,8 @@ public void configurationDescriptorBuilding(
341342
getMultiTenancyStrategy(
342343
Optional.ofNullable(persistenceXmlDescriptorBuildItem.getDescriptor()
343344
.getProperties().getProperty("hibernate.multiTenancy"))), //FIXME this property is meaningless in Hibernate ORM 6
344-
hibernateOrmConfig.database().ormCompatibilityVersion(), Collections.emptyMap()),
345+
hibernateOrmConfig.database().ormCompatibilityVersion(),
346+
hibernateOrmConfig.mapping().format().global(), Collections.emptyMap()),
345347
null,
346348
jpaModel.getXmlMappings(persistenceXmlDescriptorBuildItem.getDescriptor().getName()),
347349
true, isHibernateValidatorPresent(capabilities), jsonMapper, xmlMapper));
@@ -948,8 +950,8 @@ private static void producePersistenceUnitDescriptorFromConfig(
948950
configureSqlLoadScript(persistenceUnitName, persistenceUnitConfig, applicationArchivesBuildItem, launchMode,
949951
nativeImageResources, hotDeploymentWatchedFiles, descriptor);
950952

951-
Optional<FormatMapperKind> jsonMapper = jsonMapperKind(capabilities);
952-
Optional<FormatMapperKind> xmlMapper = xmlMapperKind(capabilities);
953+
Optional<FormatMapperKind> jsonMapper = jsonMapperKind(capabilities, hibernateOrmConfig.mapping().format().global());
954+
Optional<FormatMapperKind> xmlMapper = xmlMapperKind(capabilities, hibernateOrmConfig.mapping().format().global());
953955
jsonMapper.flatMap(FormatMapperKind::requiredBeanType)
954956
.ifPresent(type -> unremovableBeans.produce(UnremovableBeanBuildItem.beanClassNames(type)));
955957
xmlMapper.flatMap(FormatMapperKind::requiredBeanType)
@@ -963,6 +965,7 @@ private static void producePersistenceUnitDescriptorFromConfig(
963965
persistenceUnitConfig.dialect().dialect(),
964966
multiTenancyStrategy,
965967
hibernateOrmConfig.database().ormCompatibilityVersion(),
968+
hibernateOrmConfig.mapping().format().global(),
966969
persistenceUnitConfig.unsupportedProperties()),
967970
persistenceUnitConfig.multitenantSchemaDatasource().orElse(null),
968971
xmlMappings,

extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/util/HibernateProcessorUtil.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import io.quarkus.hibernate.orm.deployment.spi.DatabaseKindDialectBuildItem;
4040
import io.quarkus.hibernate.orm.runtime.HibernateOrmRuntimeConfig;
4141
import io.quarkus.hibernate.orm.runtime.boot.QuarkusPersistenceUnitDescriptor;
42+
import io.quarkus.hibernate.orm.runtime.customized.BuiltinFormatMapperBehaviour;
4243
import io.quarkus.hibernate.orm.runtime.customized.FormatMapperKind;
4344
import io.quarkus.runtime.LaunchMode;
4445
import io.quarkus.runtime.configuration.ConfigurationException;
@@ -58,7 +59,10 @@ public static boolean hasEntities(JpaModelBuildItem jpaModel) {
5859
return !jpaModel.getEntityClassNames().isEmpty();
5960
}
6061

61-
public static Optional<FormatMapperKind> jsonMapperKind(Capabilities capabilities) {
62+
public static Optional<FormatMapperKind> jsonMapperKind(Capabilities capabilities, BuiltinFormatMapperBehaviour behaviour) {
63+
if (BuiltinFormatMapperBehaviour.IGNORE.equals(behaviour)) {
64+
return Optional.empty();
65+
}
6266
if (capabilities.isPresent(Capability.JACKSON)) {
6367
return Optional.of(FormatMapperKind.JACKSON);
6468
} else if (capabilities.isPresent(Capability.JSONB)) {
@@ -68,7 +72,10 @@ public static Optional<FormatMapperKind> jsonMapperKind(Capabilities capabilitie
6872
}
6973
}
7074

71-
public static Optional<FormatMapperKind> xmlMapperKind(Capabilities capabilities) {
75+
public static Optional<FormatMapperKind> xmlMapperKind(Capabilities capabilities, BuiltinFormatMapperBehaviour behaviour) {
76+
if (BuiltinFormatMapperBehaviour.IGNORE.equals(behaviour)) {
77+
return Optional.empty();
78+
}
7279
return capabilities.isPresent(Capability.JAXB)
7380
? Optional.of(FormatMapperKind.JAXB)
7481
: Optional.empty();

extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ private EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull(String
191191
throw new IllegalStateException(
192192
"Attempting to boot a deactivated Hibernate ORM persistence unit");
193193
}
194+
194195
RuntimeSettings runtimeSettings = buildRuntimeSettings(persistenceUnitName, recordedState, puConfig);
195196

196197
StandardServiceRegistry standardServiceRegistry = rewireMetadataAndExtractServiceRegistry(persistenceUnitName,
@@ -205,7 +206,8 @@ private EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull(String
205206
standardServiceRegistry /* Mostly ignored! (yet needs to match) */,
206207
runtimeSettings,
207208
validatorFactory, cdiBeanManager, recordedState.getMultiTenancyStrategy(),
208-
true);
209+
true,
210+
recordedState.getBuildTimeSettings().getSource().getBuiltinFormatMapperBehaviour());
209211
}
210212

211213
log.debug("Found no matching persistence units");

extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/JPAConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public JPAConfig(HibernateOrmRuntimeConfig hibernateOrmRuntimeConfig) {
4646

4747
void startAll() {
4848
List<CompletableFuture<?>> start = new ArrayList<>();
49-
//by using a dedicated thread for starting up the PR,
49+
//by using a dedicated thread for starting up the PU,
5050
//we work around https://github.com/quarkusio/quarkus/issues/17304 to some extent
5151
//as the main thread is now no longer polluted with ThreadLocals by default
5252
//this is not a complete fix, but will help as long as the test methods

extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootEntityManagerFactoryBuilder.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import io.quarkus.hibernate.orm.XmlFormat;
3636
import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil;
3737
import io.quarkus.hibernate.orm.runtime.RuntimeSettings;
38+
import io.quarkus.hibernate.orm.runtime.customized.BuiltinFormatMapperBehaviour;
3839
import io.quarkus.hibernate.orm.runtime.migration.MultiTenancyStrategy;
3940
import io.quarkus.hibernate.orm.runtime.observers.QuarkusSessionFactoryObserverForDbVersionCheck;
4041
import io.quarkus.hibernate.orm.runtime.observers.SessionFactoryObserverForNamedQueryValidation;
@@ -50,6 +51,7 @@ public class FastBootEntityManagerFactoryBuilder implements EntityManagerFactory
5051
private final RuntimeSettings runtimeSettings;
5152
private final Object validatorFactory;
5253
private final Object cdiBeanManager;
54+
private final BuiltinFormatMapperBehaviour builtinFormatMapperBehaviour;
5355

5456
protected final MultiTenancyStrategy multiTenancyStrategy;
5557
protected final boolean shouldApplySchemaMigration;
@@ -58,7 +60,8 @@ public FastBootEntityManagerFactoryBuilder(
5860
QuarkusPersistenceUnitDescriptor puDescriptor,
5961
PrevalidatedQuarkusMetadata metadata,
6062
StandardServiceRegistry standardServiceRegistry, RuntimeSettings runtimeSettings, Object validatorFactory,
61-
Object cdiBeanManager, MultiTenancyStrategy multiTenancyStrategy, boolean shouldApplySchemaMigration) {
63+
Object cdiBeanManager, MultiTenancyStrategy multiTenancyStrategy, boolean shouldApplySchemaMigration,
64+
BuiltinFormatMapperBehaviour builtinFormatMapperBehaviour) {
6265
this.puDescriptor = puDescriptor;
6366
this.metadata = metadata;
6467
this.standardServiceRegistry = standardServiceRegistry;
@@ -67,6 +70,7 @@ public FastBootEntityManagerFactoryBuilder(
6770
this.cdiBeanManager = cdiBeanManager;
6871
this.multiTenancyStrategy = multiTenancyStrategy;
6972
this.shouldApplySchemaMigration = shouldApplySchemaMigration;
73+
this.builtinFormatMapperBehaviour = builtinFormatMapperBehaviour;
7074
}
7175

7276
@Override
@@ -211,11 +215,15 @@ protected void populate(String persistenceUnitName, SessionFactoryOptionsBuilder
211215
FormatMapper.class, persistenceUnitName, JsonFormat.Literal.INSTANCE);
212216
if (!jsonFormatMapper.isUnsatisfied()) {
213217
options.applyJsonFormatMapper(jsonFormatMapper.get());
218+
} else {
219+
builtinFormatMapperBehaviour.jsonApply(metadata(), persistenceUnitName);
214220
}
215221
InjectableInstance<FormatMapper> xmlFormatMapper = PersistenceUnitUtil.singleExtensionInstanceForPersistenceUnit(
216222
FormatMapper.class, persistenceUnitName, XmlFormat.Literal.INSTANCE);
217223
if (!xmlFormatMapper.isUnsatisfied()) {
218224
options.applyXmlFormatMapper(xmlFormatMapper.get());
225+
} else {
226+
builtinFormatMapperBehaviour.xmlApply(metadata(), persistenceUnitName);
219227
}
220228
}
221229

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package io.quarkus.hibernate.orm.runtime.customized;
2+
3+
import java.util.List;
4+
import java.util.Locale;
5+
import java.util.Set;
6+
7+
import org.hibernate.boot.spi.MetadataImplementor;
8+
import org.hibernate.mapping.Collection;
9+
import org.hibernate.mapping.Column;
10+
import org.hibernate.mapping.PersistentClass;
11+
import org.hibernate.mapping.Property;
12+
import org.hibernate.type.SqlTypes;
13+
import org.jboss.logging.Logger;
14+
15+
import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil;
16+
17+
public enum BuiltinFormatMapperBehaviour {
18+
/**
19+
* The Quarkus preconfigured mappers are ignored and if there is no user provided one,
20+
* Hibernate ORM will create a mapper according to its own rules.
21+
*/
22+
IGNORE {
23+
@Override
24+
protected void action(String puName, String type) {
25+
}
26+
},
27+
/**
28+
* Currently the default one, uses a Quarkus preconfigured format mappers. If a format mapper operation is invoked a
29+
* warning is logged.
30+
*/
31+
WARN {
32+
@Override
33+
protected void action(String puName, String type) {
34+
LOGGER.warn(message(puName, type));
35+
}
36+
},
37+
/**
38+
* If there is no user provided format mapper, a Quarkus preconfigured one will fail at runtime.
39+
* Will become the default in the future versions of Quarkus.
40+
*/
41+
FAIL {
42+
@Override
43+
protected void action(String puName, String type) {
44+
throw new IllegalStateException(message(puName, type));
45+
}
46+
};
47+
48+
private static final Logger LOGGER = Logger.getLogger(BuiltinFormatMapperBehaviour.class);
49+
private static final String TYPE_JSON = "JSON";
50+
private static final String TYPE_XML = "XML";
51+
52+
private static String message(String puName, String type) {
53+
return String.format(Locale.ROOT,
54+
"Persistence unit [%1$s] uses Quarkus' main formatting facilities for %2$s columns in the database. "
55+
+ "\nAs these facilities are primarily meant for REST endpoints, and they might have been customized for such use, "
56+
+ "this may lead to undesired behavior, up to and including data loss. "
57+
+ "\nTo address this:"
58+
+ "\n\t- If the application does not customize the %2$s serialization/deserialization, set \"quarkus.hibernate-orm.mapping.format.global=ignore\". This will be the default in future versions of Quarkus. "
59+
+ "\n\t- Otherwise, define a custom `FormatMapper` bean annotated with "
60+
+ (TYPE_JSON.equals(type) ? "@JsonFormat" : "@XmlFormat")
61+
+ " and @PersistenceUnitExtension"
62+
+ (PersistenceUnitUtil.isDefaultPersistenceUnit(puName) ? "" : "(\"%1$s\")")
63+
+ "to address your database serialization/deserialization needs."
64+
+ "\nSee the migration guide for more details and how to proceed.",
65+
puName, type);
66+
}
67+
68+
public static boolean hasJsonProperties(MetadataImplementor metadata) {
69+
return hasXxxProperties(metadata, Set.of(SqlTypes.JSON, SqlTypes.JSON_ARRAY));
70+
}
71+
72+
public static boolean hasXmlProperties(MetadataImplementor metadata) {
73+
return hasXxxProperties(metadata, Set.of(SqlTypes.SQLXML, SqlTypes.XML_ARRAY));
74+
}
75+
76+
private static boolean hasXxxProperties(MetadataImplementor metadata, Set<Integer> sqlTypeCodes) {
77+
for (PersistentClass persistentClass : metadata.getEntityBindings()) {
78+
for (Property property : persistentClass.getProperties()) {
79+
List<Column> columns = property.getColumns();
80+
if (columns.isEmpty()) {
81+
if (property.getValue() instanceof Collection c) {
82+
columns = c.getElement().getColumns();
83+
}
84+
}
85+
for (Column column : columns) {
86+
if (sqlTypeCodes.contains(column.getSqlTypeCode(metadata))) {
87+
return true;
88+
}
89+
}
90+
}
91+
}
92+
return false;
93+
}
94+
95+
public void jsonApply(MetadataImplementor metadata, String puName) {
96+
if (hasJsonProperties(metadata)) {
97+
action(puName, TYPE_JSON);
98+
}
99+
}
100+
101+
public void xmlApply(MetadataImplementor metadata, String puName) {
102+
if (hasXmlProperties(metadata)) {
103+
action(puName, TYPE_XML);
104+
}
105+
}
106+
107+
protected abstract void action(String puName, String type);
108+
}

extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/recording/RecordedConfig.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.util.Optional;
66

77
import io.quarkus.hibernate.orm.runtime.config.DatabaseOrmCompatibilityVersion;
8+
import io.quarkus.hibernate.orm.runtime.customized.BuiltinFormatMapperBehaviour;
89
import io.quarkus.hibernate.orm.runtime.migration.MultiTenancyStrategy;
910
import io.quarkus.runtime.annotations.RecordableConstructor;
1011

@@ -19,12 +20,14 @@ public class RecordedConfig {
1920
private final MultiTenancyStrategy multiTenancyStrategy;
2021
private final Map<String, String> quarkusConfigUnsupportedProperties;
2122
private final DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion;
23+
private final BuiltinFormatMapperBehaviour builtinFormatMapperBehaviour;
2224

2325
@RecordableConstructor
2426
public RecordedConfig(Optional<String> dataSource, Optional<String> dbKind,
2527
Optional<String> dbVersion, Optional<String> explicitDialect,
2628
MultiTenancyStrategy multiTenancyStrategy,
2729
DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion,
30+
BuiltinFormatMapperBehaviour builtinFormatMapperBehaviour,
2831
Map<String, String> quarkusConfigUnsupportedProperties) {
2932
Objects.requireNonNull(dataSource);
3033
Objects.requireNonNull(dbKind);
@@ -35,8 +38,9 @@ public RecordedConfig(Optional<String> dataSource, Optional<String> dbKind,
3538
this.dbVersion = dbVersion;
3639
this.explicitDialect = explicitDialect;
3740
this.multiTenancyStrategy = multiTenancyStrategy;
38-
this.quarkusConfigUnsupportedProperties = quarkusConfigUnsupportedProperties;
3941
this.databaseOrmCompatibilityVersion = databaseOrmCompatibilityVersion;
42+
this.builtinFormatMapperBehaviour = builtinFormatMapperBehaviour;
43+
this.quarkusConfigUnsupportedProperties = quarkusConfigUnsupportedProperties;
4044
}
4145

4246
public Optional<String> getDataSource() {
@@ -59,11 +63,15 @@ public MultiTenancyStrategy getMultiTenancyStrategy() {
5963
return multiTenancyStrategy;
6064
}
6165

62-
public Map<String, String> getQuarkusConfigUnsupportedProperties() {
63-
return quarkusConfigUnsupportedProperties;
64-
}
65-
6666
public DatabaseOrmCompatibilityVersion getDatabaseOrmCompatibilityVersion() {
6767
return databaseOrmCompatibilityVersion;
6868
}
69+
70+
public BuiltinFormatMapperBehaviour getBuiltinFormatMapperBehaviour() {
71+
return builtinFormatMapperBehaviour;
72+
}
73+
74+
public Map<String, String> getQuarkusConfigUnsupportedProperties() {
75+
return quarkusConfigUnsupportedProperties;
76+
}
6977
}

extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,8 @@ public void buildReactivePersistenceUnit(
183183
launchMode.getLaunchMode(),
184184
systemProperties, nativeImageResources, hotDeploymentWatchedFiles, dbKindDialectBuildItems);
185185

186-
Optional<FormatMapperKind> jsonMapper = jsonMapperKind(capabilities);
187-
Optional<FormatMapperKind> xmlMapper = xmlMapperKind(capabilities);
186+
Optional<FormatMapperKind> jsonMapper = jsonMapperKind(capabilities, hibernateOrmConfig.mapping().format().global());
187+
Optional<FormatMapperKind> xmlMapper = xmlMapperKind(capabilities, hibernateOrmConfig.mapping().format().global());
188188
jsonMapper.flatMap(FormatMapperKind::requiredBeanType)
189189
.ifPresent(type -> unremovableBeans.produce(UnremovableBeanBuildItem.beanClassNames(type)));
190190
xmlMapper.flatMap(FormatMapperKind::requiredBeanType)
@@ -202,6 +202,7 @@ public void buildReactivePersistenceUnit(
202202
persistenceUnitConfig.dialect().dialect(),
203203
io.quarkus.hibernate.orm.runtime.migration.MultiTenancyStrategy.NONE,
204204
hibernateOrmConfig.database().ormCompatibilityVersion(),
205+
hibernateOrmConfig.mapping().format().global(),
205206
persistenceUnitConfig.unsupportedProperties()),
206207
null,
207208
jpaModel.getXmlMappings(reactivePU.getName()),

0 commit comments

Comments
 (0)