mirror of
https://github.com/hibernate/hibernate-orm
synced 2025-02-17 08:35:13 +00:00
HHH-16160 Fix some XML related issues that came up
This commit is contained in:
parent
c5f5e10df4
commit
c02eae1d89
1
.idea/codeStyles/Project.xml
generated
1
.idea/codeStyles/Project.xml
generated
@ -12,6 +12,7 @@
|
||||
<option name="WHILE_ON_NEW_LINE" value="true" />
|
||||
<option name="CATCH_ON_NEW_LINE" value="true" />
|
||||
<option name="FINALLY_ON_NEW_LINE" value="true" />
|
||||
<option name="ALIGN_MULTILINE_BINARY_OPERATION" value="true" />
|
||||
<option name="SPACE_WITHIN_METHOD_CALL_PARENTHESES" value="true" />
|
||||
<option name="SPACE_WITHIN_IF_PARENTHESES" value="true" />
|
||||
<option name="SPACE_WITHIN_WHILE_PARENTHESES" value="true" />
|
||||
|
@ -659,8 +659,6 @@ disable_userland_proxy() {
|
||||
sudo service docker stop
|
||||
echo "Updating /etc/docker/daemon.json..."
|
||||
sudo bash -c "export docker_daemon_json='$docker_daemon_json'; echo \"\${docker_daemon_json/\}/,}\\\"userland-proxy\\\": false}\" > /etc/docker/daemon.json"
|
||||
echo "New docker daemon config:"
|
||||
cat /etc/docker/daemon.json
|
||||
echo "Starting docker..."
|
||||
sudo service docker start
|
||||
echo "Service status:"
|
||||
|
@ -93,6 +93,7 @@
|
||||
|
||||
import static org.hibernate.cfg.AvailableSettings.JPA_COMPLIANCE;
|
||||
import static org.hibernate.cfg.AvailableSettings.WRAPPER_ARRAY_HANDLING;
|
||||
import static org.hibernate.cfg.MappingSettings.XML_FORMAT_MAPPER_LEGACY_FORMAT;
|
||||
import static org.hibernate.engine.config.spi.StandardConverters.BOOLEAN;
|
||||
import static org.hibernate.internal.util.StringHelper.nullIfEmpty;
|
||||
|
||||
@ -652,6 +653,7 @@ public static class MetadataBuildingOptionsImpl
|
||||
private final String schemaCharset;
|
||||
private final boolean xmlMappingEnabled;
|
||||
private final boolean allowExtensionsInCdi;
|
||||
private final boolean xmlFormatMapperLegacyFormat;
|
||||
|
||||
public MetadataBuildingOptionsImpl(StandardServiceRegistry serviceRegistry) {
|
||||
this.serviceRegistry = serviceRegistry;
|
||||
@ -670,6 +672,7 @@ public MetadataBuildingOptionsImpl(StandardServiceRegistry serviceRegistry) {
|
||||
BOOLEAN,
|
||||
true
|
||||
);
|
||||
xmlFormatMapperLegacyFormat = configService.getSetting( XML_FORMAT_MAPPER_LEGACY_FORMAT, BOOLEAN, false );
|
||||
|
||||
implicitDiscriminatorsForJoinedInheritanceSupported = configService.getSetting(
|
||||
AvailableSettings.IMPLICIT_DISCRIMINATOR_COLUMNS_FOR_JOINED_SUBCLASS,
|
||||
@ -954,6 +957,11 @@ public boolean isAllowExtensionsInCdi() {
|
||||
return allowExtensionsInCdi;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isXmlFormatMapperLegacyFormatEnabled() {
|
||||
return xmlFormatMapperLegacyFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Yuck. This is needed because JPA lets users define "global building options"
|
||||
* in {@code orm.xml} mappings. Forget that there are generally multiple
|
||||
|
@ -166,6 +166,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
|
||||
private Object validatorFactoryReference;
|
||||
private FormatMapper jsonFormatMapper;
|
||||
private FormatMapper xmlFormatMapper;
|
||||
private final boolean xmlFormatMapperLegacyFormatEnabled;
|
||||
|
||||
// SessionFactory behavior
|
||||
private final boolean jpaBootstrap;
|
||||
@ -323,7 +324,8 @@ public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, Boo
|
||||
);
|
||||
this.xmlFormatMapper = determineXmlFormatMapper(
|
||||
configurationSettings.get( AvailableSettings.XML_FORMAT_MAPPER ),
|
||||
strategySelector
|
||||
strategySelector,
|
||||
this.xmlFormatMapperLegacyFormatEnabled = context.getMetadataBuildingOptions().isXmlFormatMapperLegacyFormatEnabled()
|
||||
);
|
||||
|
||||
this.sessionFactoryName = (String) configurationSettings.get( SESSION_FACTORY_NAME );
|
||||
@ -866,13 +868,13 @@ private static FormatMapper determineJsonFormatMapper(Object setting, StrategySe
|
||||
);
|
||||
}
|
||||
|
||||
private static FormatMapper determineXmlFormatMapper(Object setting, StrategySelector strategySelector) {
|
||||
private static FormatMapper determineXmlFormatMapper(Object setting, StrategySelector strategySelector, boolean legacyFormat) {
|
||||
return strategySelector.resolveDefaultableStrategy(
|
||||
FormatMapper.class,
|
||||
setting,
|
||||
(Callable<FormatMapper>) () -> {
|
||||
final FormatMapper jacksonFormatMapper = getXMLJacksonFormatMapperOrNull();
|
||||
return jacksonFormatMapper != null ? jacksonFormatMapper : new JaxbXmlFormatMapper();
|
||||
final FormatMapper jacksonFormatMapper = getXMLJacksonFormatMapperOrNull( legacyFormat );
|
||||
return jacksonFormatMapper != null ? jacksonFormatMapper : new JaxbXmlFormatMapper( legacyFormat );
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -1332,6 +1334,11 @@ public FormatMapper getXmlFormatMapper() {
|
||||
return xmlFormatMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isXmlFormatMapperLegacyFormatEnabled() {
|
||||
return xmlFormatMapperLegacyFormatEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPassProcedureParameterNames() {
|
||||
return passProcedureParameterNames;
|
||||
|
@ -182,4 +182,9 @@ public boolean isXmlMappingEnabled() {
|
||||
public boolean isAllowExtensionsInCdi() {
|
||||
return delegate.isAllowExtensionsInCdi();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isXmlFormatMapperLegacyFormatEnabled() {
|
||||
return delegate.isXmlFormatMapperLegacyFormatEnabled();
|
||||
}
|
||||
}
|
||||
|
@ -523,6 +523,11 @@ public FormatMapper getXmlFormatMapper() {
|
||||
return delegate.getXmlFormatMapper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isXmlFormatMapperLegacyFormatEnabled() {
|
||||
return delegate.isXmlFormatMapperLegacyFormatEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPassProcedureParameterNames() {
|
||||
return delegate.isPassProcedureParameterNames();
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.Incubating;
|
||||
import org.hibernate.TimeZoneStorageStrategy;
|
||||
import org.hibernate.boot.model.naming.ImplicitNamingStrategy;
|
||||
import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
|
||||
@ -144,6 +145,15 @@ default CollectionSemanticsResolver getPersistentCollectionRepresentationResolve
|
||||
*/
|
||||
boolean isMultiTenancyEnabled();
|
||||
|
||||
/**
|
||||
* Whether to use the legacy format for serializing/deserializing XML data.
|
||||
*
|
||||
* @since 7.0
|
||||
* @see org.hibernate.cfg.MappingSettings#XML_FORMAT_MAPPER_LEGACY_FORMAT
|
||||
*/
|
||||
@Incubating
|
||||
boolean isXmlFormatMapperLegacyFormatEnabled();
|
||||
|
||||
/**
|
||||
* @return the {@link TypeConfiguration} belonging to the {@link BootstrapContext}
|
||||
*/
|
||||
|
@ -571,6 +571,15 @@ default boolean isCollectionsInDefaultFetchGroupEnabled() {
|
||||
@Incubating
|
||||
FormatMapper getXmlFormatMapper();
|
||||
|
||||
/**
|
||||
* Whether to use the legacy format for serializing/deserializing XML data.
|
||||
*
|
||||
* @since 7.0
|
||||
* @see org.hibernate.cfg.MappingSettings#XML_FORMAT_MAPPER_LEGACY_FORMAT
|
||||
*/
|
||||
@Incubating
|
||||
boolean isXmlFormatMapperLegacyFormatEnabled();
|
||||
|
||||
/**
|
||||
* The default tenant identifier java type to use, in case no explicit tenant identifier property is defined.
|
||||
*
|
||||
|
@ -325,6 +325,17 @@ public interface MappingSettings {
|
||||
@Incubating
|
||||
String XML_FORMAT_MAPPER = "hibernate.type.xml_format_mapper";
|
||||
|
||||
/**
|
||||
* Specifies whether to use the legacy provider specific and non-portable XML format for collections and byte arrays
|
||||
* for XML serialization/deserialization.
|
||||
* <p>
|
||||
* {@code false} by default. This property only exists for backwards compatibility.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
String XML_FORMAT_MAPPER_LEGACY_FORMAT = "hibernate.type.xml_format_mapper.legacy_format";
|
||||
|
||||
/**
|
||||
* Configurable control over how to handle {@code Byte[]} and {@code Character[]} types
|
||||
* encountered in the application domain model. Allowable semantics are defined by
|
||||
|
@ -5,15 +5,16 @@
|
||||
package org.hibernate.dialect;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.Array;
|
||||
import java.sql.SQLException;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.hibernate.Internal;
|
||||
import org.hibernate.engine.spi.LazySessionWrapperOptions;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
@ -24,16 +25,22 @@
|
||||
import org.hibernate.metamodel.mapping.ValuedModelPart;
|
||||
import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping;
|
||||
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||
import org.hibernate.type.BasicPluralType;
|
||||
import org.hibernate.type.BasicType;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
import org.hibernate.type.descriptor.WrapperOptions;
|
||||
import org.hibernate.type.descriptor.java.BasicPluralJavaType;
|
||||
import org.hibernate.type.descriptor.java.IntegerJavaType;
|
||||
import org.hibernate.type.descriptor.java.EnumJavaType;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
import org.hibernate.type.descriptor.java.JdbcDateJavaType;
|
||||
import org.hibernate.type.descriptor.java.JdbcTimeJavaType;
|
||||
import org.hibernate.type.descriptor.java.JdbcTimestampJavaType;
|
||||
import org.hibernate.type.descriptor.java.OffsetDateTimeJavaType;
|
||||
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
|
||||
import org.hibernate.type.descriptor.jdbc.AggregateJdbcType;
|
||||
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
|
||||
import org.hibernate.type.descriptor.jdbc.JdbcType;
|
||||
import org.hibernate.type.descriptor.jdbc.XmlArrayJdbcType;
|
||||
|
||||
import static java.lang.Character.isLetter;
|
||||
import static java.lang.Character.isLetterOrDigit;
|
||||
@ -52,37 +59,10 @@ public class XmlHelper {
|
||||
public static final String ROOT_TAG = "e";
|
||||
private static final String START_TAG = "<" + ROOT_TAG + ">";
|
||||
private static final String END_TAG = "</" + ROOT_TAG + ">";
|
||||
|
||||
private static Object fromEscapedString(
|
||||
JdbcMapping jdbcMapping,
|
||||
String string,
|
||||
int start,
|
||||
int end) {
|
||||
final String unescaped = unescape( string, start, end );
|
||||
return fromString( jdbcMapping, unescaped, 0, unescaped.length() );
|
||||
}
|
||||
|
||||
private static Object fromString(
|
||||
JdbcMapping jdbcMapping,
|
||||
String string,
|
||||
int start,
|
||||
int end) {
|
||||
return jdbcMapping.getJdbcJavaType().fromEncodedString(
|
||||
string,
|
||||
start,
|
||||
end
|
||||
);
|
||||
}
|
||||
|
||||
private static Object fromRawObject(
|
||||
JdbcMapping jdbcMapping,
|
||||
Object raw,
|
||||
WrapperOptions options) {
|
||||
return jdbcMapping.getJdbcJavaType().wrap(
|
||||
raw,
|
||||
options
|
||||
);
|
||||
}
|
||||
private static final String NULL_TAG = "<" + ROOT_TAG + "/>";
|
||||
private static final String COLLECTION_START_TAG = "<Collection>";
|
||||
private static final String COLLECTION_END_TAG = "</Collection>";
|
||||
private static final String EMPTY_COLLECTION_TAG = "<Collection/>";
|
||||
|
||||
private static String unescape(String string, int start, int end) {
|
||||
final StringBuilder sb = new StringBuilder( end - start );
|
||||
@ -118,6 +98,16 @@ private static String unescape(String string, int start, int end) {
|
||||
i += 3;
|
||||
}
|
||||
break OUTER;
|
||||
case 'q':
|
||||
if ( i + 5 < end
|
||||
&& string.charAt( i + 2 ) == 'u'
|
||||
&& string.charAt( i + 3 ) == 'o'
|
||||
&& string.charAt( i + 4 ) == 't'
|
||||
&& string.charAt( i + 5 ) == ';' ) {
|
||||
sb.append( '"' );
|
||||
i += 5;
|
||||
}
|
||||
break OUTER;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException( "Illegal XML content: " + string.substring( start, end ) );
|
||||
@ -132,37 +122,60 @@ private static String unescape(String string, int start, int end) {
|
||||
private static Object fromString(
|
||||
EmbeddableMappingType embeddableMappingType,
|
||||
String string,
|
||||
boolean returnEmbeddable,
|
||||
WrapperOptions options,
|
||||
int selectableIndex,
|
||||
int start,
|
||||
int end) {
|
||||
final JdbcMapping jdbcMapping = embeddableMappingType.getJdbcValueSelectable( selectableIndex ).getJdbcMapping();
|
||||
switch ( jdbcMapping.getJdbcType().getDefaultSqlTypeCode() ) {
|
||||
case SqlTypes.BOOLEAN:
|
||||
case SqlTypes.BIT:
|
||||
int end) throws SQLException {
|
||||
final JdbcMapping jdbcMapping = embeddableMappingType.getJdbcValueSelectable( selectableIndex )
|
||||
.getJdbcMapping();
|
||||
return fromString(
|
||||
jdbcMapping.getMappedJavaType(),
|
||||
jdbcMapping.getJdbcJavaType(),
|
||||
jdbcMapping.getJdbcType(),
|
||||
string,
|
||||
returnEmbeddable,
|
||||
options,
|
||||
start,
|
||||
end
|
||||
);
|
||||
}
|
||||
|
||||
private static Object fromString(
|
||||
JavaType<?> javaType,
|
||||
JavaType<?> jdbcJavaType,
|
||||
JdbcType jdbcType,
|
||||
String string,
|
||||
boolean returnEmbeddable,
|
||||
WrapperOptions options,
|
||||
int start,
|
||||
int end) throws SQLException {
|
||||
switch ( jdbcType.getDefaultSqlTypeCode() ) {
|
||||
case SqlTypes.TINYINT:
|
||||
case SqlTypes.SMALLINT:
|
||||
case SqlTypes.INTEGER:
|
||||
if ( jdbcJavaType.getJavaTypeClass() == Boolean.class ) {
|
||||
return jdbcJavaType.wrap( Integer.parseInt( string, start, end, 10 ), options );
|
||||
}
|
||||
else if ( jdbcJavaType instanceof EnumJavaType<?> ) {
|
||||
return jdbcJavaType.wrap( Integer.parseInt( string, start, end, 10 ), options );
|
||||
}
|
||||
case SqlTypes.BOOLEAN:
|
||||
case SqlTypes.BIT:
|
||||
case SqlTypes.BIGINT:
|
||||
case SqlTypes.FLOAT:
|
||||
case SqlTypes.REAL:
|
||||
case SqlTypes.DOUBLE:
|
||||
case SqlTypes.DECIMAL:
|
||||
case SqlTypes.NUMERIC:
|
||||
Class<?> javaTypeClass = jdbcMapping.getMappedJavaType().getJavaTypeClass();
|
||||
if ( javaTypeClass.isEnum() ) {
|
||||
return javaTypeClass.getEnumConstants()
|
||||
[IntegerJavaType.INSTANCE.fromEncodedString( string, start, end )];
|
||||
}
|
||||
return fromString(
|
||||
jdbcMapping,
|
||||
case SqlTypes.UUID:
|
||||
return jdbcJavaType.fromEncodedString(
|
||||
string,
|
||||
start,
|
||||
end
|
||||
);
|
||||
case SqlTypes.DATE:
|
||||
return fromRawObject(
|
||||
jdbcMapping,
|
||||
return jdbcJavaType.wrap(
|
||||
JdbcDateJavaType.INSTANCE.fromEncodedString(
|
||||
string,
|
||||
start,
|
||||
@ -173,8 +186,7 @@ private static Object fromString(
|
||||
case SqlTypes.TIME:
|
||||
case SqlTypes.TIME_WITH_TIMEZONE:
|
||||
case SqlTypes.TIME_UTC:
|
||||
return fromRawObject(
|
||||
jdbcMapping,
|
||||
return jdbcJavaType.wrap(
|
||||
JdbcTimeJavaType.INSTANCE.fromEncodedString(
|
||||
string,
|
||||
start,
|
||||
@ -183,8 +195,7 @@ private static Object fromString(
|
||||
options
|
||||
);
|
||||
case SqlTypes.TIMESTAMP:
|
||||
return fromRawObject(
|
||||
jdbcMapping,
|
||||
return jdbcJavaType.wrap(
|
||||
JdbcTimestampJavaType.INSTANCE.fromEncodedString(
|
||||
string,
|
||||
start,
|
||||
@ -194,8 +205,7 @@ private static Object fromString(
|
||||
);
|
||||
case SqlTypes.TIMESTAMP_WITH_TIMEZONE:
|
||||
case SqlTypes.TIMESTAMP_UTC:
|
||||
return fromRawObject(
|
||||
jdbcMapping,
|
||||
return jdbcJavaType.wrap(
|
||||
OffsetDateTimeJavaType.INSTANCE.fromEncodedString(
|
||||
string,
|
||||
start,
|
||||
@ -207,19 +217,46 @@ private static Object fromString(
|
||||
case SqlTypes.VARBINARY:
|
||||
case SqlTypes.LONGVARBINARY:
|
||||
case SqlTypes.LONG32VARBINARY:
|
||||
case SqlTypes.UUID:
|
||||
return fromRawObject(
|
||||
jdbcMapping,
|
||||
Base64.getDecoder().decode( string.substring( start, end ) ),
|
||||
case SqlTypes.BLOB:
|
||||
case SqlTypes.MATERIALIZED_BLOB:
|
||||
return jdbcJavaType.wrap(
|
||||
PrimitiveByteArrayJavaType.INSTANCE.fromEncodedString(
|
||||
string,
|
||||
start,
|
||||
end
|
||||
),
|
||||
options
|
||||
);
|
||||
case SqlTypes.CHAR:
|
||||
case SqlTypes.NCHAR:
|
||||
case SqlTypes.VARCHAR:
|
||||
case SqlTypes.NVARCHAR:
|
||||
if ( jdbcJavaType.getJavaTypeClass() == Boolean.class && end == start + 1 ) {
|
||||
return jdbcJavaType.wrap( string.charAt( start ), options );
|
||||
}
|
||||
default:
|
||||
return fromEscapedString(
|
||||
jdbcMapping,
|
||||
string,
|
||||
start,
|
||||
end
|
||||
);
|
||||
if ( jdbcType instanceof AggregateJdbcType aggregateJdbcType ) {
|
||||
final Object[] subValues = aggregateJdbcType.extractJdbcValues(
|
||||
CharSequenceHelper.subSequence(
|
||||
string,
|
||||
start,
|
||||
end
|
||||
),
|
||||
options
|
||||
);
|
||||
if ( returnEmbeddable ) {
|
||||
final StructAttributeValues subAttributeValues = StructHelper.getAttributeValues(
|
||||
aggregateJdbcType.getEmbeddableMappingType(),
|
||||
subValues,
|
||||
options
|
||||
);
|
||||
final EmbeddableMappingType embeddableMappingType = aggregateJdbcType.getEmbeddableMappingType();
|
||||
return instantiate( embeddableMappingType, subAttributeValues, options.getSessionFactory() ) ;
|
||||
}
|
||||
return subValues;
|
||||
}
|
||||
final String unescaped = unescape( string, start, end );
|
||||
return jdbcJavaType.fromEncodedString( unescaped, 0, unescaped.length() );
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,6 +291,45 @@ public static <X> X fromString(
|
||||
return (X) array;
|
||||
}
|
||||
|
||||
public static <X> X arrayFromString(
|
||||
JavaType<X> javaType,
|
||||
XmlArrayJdbcType xmlArrayJdbcType,
|
||||
String string,
|
||||
WrapperOptions options) throws SQLException {
|
||||
if ( string == null ) {
|
||||
return null;
|
||||
}
|
||||
else if ( EMPTY_COLLECTION_TAG.equals( string ) ) {
|
||||
return javaType.wrap( Collections.emptyList(), options );
|
||||
}
|
||||
else if ( !string.startsWith( COLLECTION_START_TAG ) || !string.endsWith( COLLECTION_END_TAG ) ) {
|
||||
throw new IllegalArgumentException( "Illegal XML for array: " + string );
|
||||
}
|
||||
final JavaType<?> elementJavaType = ((BasicPluralJavaType<?>) javaType).getElementJavaType();
|
||||
final Class<?> preferredJavaTypeClass = xmlArrayJdbcType.getElementJdbcType().getPreferredJavaTypeClass( options );
|
||||
final JavaType<?> jdbcJavaType;
|
||||
if ( preferredJavaTypeClass == null || preferredJavaTypeClass == elementJavaType.getJavaTypeClass() ) {
|
||||
jdbcJavaType = elementJavaType;
|
||||
}
|
||||
else {
|
||||
jdbcJavaType = options.getSessionFactory().getTypeConfiguration().getJavaTypeRegistry()
|
||||
.resolveDescriptor( preferredJavaTypeClass );
|
||||
}
|
||||
final ArrayList<Object> arrayList = new ArrayList<>();
|
||||
final int end = fromArrayString(
|
||||
string,
|
||||
false,
|
||||
options,
|
||||
COLLECTION_START_TAG.length(),
|
||||
arrayList,
|
||||
elementJavaType,
|
||||
jdbcJavaType,
|
||||
xmlArrayJdbcType.getElementJdbcType()
|
||||
);
|
||||
assert end + COLLECTION_END_TAG.length() == string.length();
|
||||
return javaType.wrap( arrayList, options );
|
||||
}
|
||||
|
||||
private static int fromString(
|
||||
String string,
|
||||
List<Object> values,
|
||||
@ -365,6 +441,7 @@ private static int fromString(
|
||||
values[selectableMapping] = fromString(
|
||||
embeddableMappingType,
|
||||
string,
|
||||
returnEmbeddable,
|
||||
options,
|
||||
selectableMapping,
|
||||
contentStart,
|
||||
@ -377,63 +454,86 @@ private static int fromString(
|
||||
final SelectableMapping selectable = embeddableMappingType.getJdbcValueSelectable(
|
||||
selectableIndex
|
||||
);
|
||||
if ( !( selectable.getJdbcMapping().getJdbcType() instanceof AggregateJdbcType ) ) {
|
||||
final JdbcType jdbcType = selectable.getJdbcMapping().getJdbcType();
|
||||
if ( jdbcType instanceof AggregateJdbcType aggregateJdbcType ) {
|
||||
final EmbeddableMappingType subMappingType = aggregateJdbcType.getEmbeddableMappingType();
|
||||
final Object[] subValues;
|
||||
final int end;
|
||||
if ( aggregateJdbcType.getJdbcTypeCode() == SqlTypes.SQLXML || aggregateJdbcType.getDefaultSqlTypeCode() == SqlTypes.SQLXML ) {
|
||||
// If we stay in XML land, we can recurse instead
|
||||
subValues = new Object[subMappingType.getJdbcValueCount()];
|
||||
end = fromString(
|
||||
subMappingType,
|
||||
string,
|
||||
returnEmbeddable,
|
||||
options,
|
||||
subValues,
|
||||
i
|
||||
);
|
||||
}
|
||||
else {
|
||||
// Determine the end of the XML element
|
||||
while ( string.charAt( i ) != '<' ) {
|
||||
i++;
|
||||
}
|
||||
end = i;
|
||||
subValues = aggregateJdbcType.extractJdbcValues(
|
||||
CharSequenceHelper.subSequence(
|
||||
string,
|
||||
start,
|
||||
end
|
||||
),
|
||||
options
|
||||
);
|
||||
}
|
||||
if ( returnEmbeddable ) {
|
||||
final StructAttributeValues attributeValues = StructHelper.getAttributeValues(
|
||||
subMappingType,
|
||||
subValues,
|
||||
options
|
||||
);
|
||||
values[selectableIndex] = instantiate( subMappingType, attributeValues,
|
||||
options.getSessionFactory() );
|
||||
}
|
||||
else {
|
||||
values[selectableIndex] = subValues;
|
||||
}
|
||||
// The end is the start angle bracket for the end tag
|
||||
assert string.charAt( end ) == '<';
|
||||
assert string.charAt( end + 1 ) == '/';
|
||||
assert string.regionMatches( end + 2, tagName, 0, tagName.length() );
|
||||
i = end;
|
||||
}
|
||||
else if ( selectable.getJdbcMapping() instanceof BasicPluralType<?,?> pluralType ) {
|
||||
final BasicType<?> elementType = pluralType.getElementType();
|
||||
final ArrayList<Object> arrayList = new ArrayList<>();
|
||||
final int end = fromArrayString(
|
||||
string,
|
||||
returnEmbeddable,
|
||||
options,
|
||||
i,
|
||||
arrayList,
|
||||
elementType.getMappedJavaType(),
|
||||
elementType.getJdbcJavaType(),
|
||||
elementType.getJdbcType()
|
||||
);
|
||||
values[selectableIndex] = selectable.getJdbcMapping().getJdbcJavaType().wrap( arrayList, options );
|
||||
// The end is the start angle bracket for the end tag
|
||||
assert string.charAt( end ) == '<';
|
||||
assert string.charAt( end + 1 ) == '/';
|
||||
assert string.regionMatches( end + 2, tagName, 0, tagName.length() );
|
||||
i = end;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException(
|
||||
String.format(
|
||||
"XML starts sub-object for a non-aggregate type at index %d. Selectable [%s] is of type [%s]",
|
||||
i,
|
||||
selectable.getSelectableName(),
|
||||
selectable.getJdbcMapping().getJdbcType().getClass().getName()
|
||||
jdbcType.getClass().getName()
|
||||
)
|
||||
);
|
||||
}
|
||||
final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) selectable.getJdbcMapping().getJdbcType();
|
||||
final EmbeddableMappingType subMappingType = aggregateJdbcType.getEmbeddableMappingType();
|
||||
final Object[] subValues;
|
||||
final int end;
|
||||
if ( aggregateJdbcType.getJdbcTypeCode() == SqlTypes.SQLXML || aggregateJdbcType.getDefaultSqlTypeCode() == SqlTypes.SQLXML ) {
|
||||
// If we stay in XML land, we can recurse instead
|
||||
subValues = new Object[subMappingType.getJdbcValueCount()];
|
||||
end = fromString(
|
||||
subMappingType,
|
||||
string,
|
||||
returnEmbeddable,
|
||||
options,
|
||||
subValues,
|
||||
i
|
||||
);
|
||||
}
|
||||
else {
|
||||
// Determine the end of the XML element
|
||||
while ( string.charAt( i ) != '<' ) {
|
||||
i++;
|
||||
}
|
||||
end = i;
|
||||
subValues = aggregateJdbcType.extractJdbcValues(
|
||||
CharSequenceHelper.subSequence(
|
||||
string,
|
||||
start,
|
||||
end
|
||||
),
|
||||
options
|
||||
);
|
||||
}
|
||||
if ( returnEmbeddable ) {
|
||||
final StructAttributeValues attributeValues = StructHelper.getAttributeValues(
|
||||
subMappingType,
|
||||
subValues,
|
||||
options
|
||||
);
|
||||
values[selectableIndex] = instantiate( subMappingType, attributeValues, options.getSessionFactory() );
|
||||
}
|
||||
else {
|
||||
values[selectableIndex] = subValues;
|
||||
}
|
||||
// The end is the start angle bracket for the end tag
|
||||
assert string.charAt( end ) == '<';
|
||||
assert string.charAt( end + 1 ) == '/';
|
||||
assert string.regionMatches( end + 2, tagName, 0, tagName.length() );
|
||||
i = end;
|
||||
}
|
||||
// consume the whole closing tag
|
||||
i += tagName.length() + 2;
|
||||
@ -474,86 +574,240 @@ private static int fromString(
|
||||
throw new IllegalArgumentException( "XML not properly formed: " + string.substring( start ) );
|
||||
}
|
||||
|
||||
private static int fromArrayString(
|
||||
String string,
|
||||
boolean returnEmbeddable,
|
||||
WrapperOptions options,
|
||||
int start,
|
||||
ArrayList<Object> arrayList,
|
||||
JavaType<?> javaType,
|
||||
JavaType<?> jdbcJavaType,
|
||||
JdbcType jdbcType) throws SQLException {
|
||||
int tagNameStart = -1;
|
||||
int contentStart = -1;
|
||||
for ( int i = start; i < string.length(); i++ ) {
|
||||
final char c = string.charAt( i );
|
||||
switch ( c ) {
|
||||
case '<':
|
||||
if ( tagNameStart == -1 ) {
|
||||
if ( string.charAt( i + 1 ) == '/' ) {
|
||||
// This is the parent closing tag, so we stop here
|
||||
assert contentStart == -1;
|
||||
return i;
|
||||
}
|
||||
// A start tag
|
||||
tagNameStart = i + 1;
|
||||
}
|
||||
else {
|
||||
if ( string.charAt( i + 1 ) == '/' ) {
|
||||
// This is a closing tag
|
||||
if ( !string.regionMatches( i + 2, ROOT_TAG + ">", 0, ROOT_TAG.length() + 1 ) ) {
|
||||
throw new IllegalArgumentException( "XML not properly formed: " + string.substring( start ) );
|
||||
}
|
||||
arrayList.add( fromString(
|
||||
javaType,
|
||||
jdbcJavaType,
|
||||
jdbcType,
|
||||
string,
|
||||
returnEmbeddable,
|
||||
options,
|
||||
contentStart,
|
||||
i
|
||||
) );
|
||||
}
|
||||
else {
|
||||
// Nested tag
|
||||
if ( jdbcType instanceof AggregateJdbcType aggregateJdbcType ) {
|
||||
final EmbeddableMappingType embeddableMappingType = aggregateJdbcType.getEmbeddableMappingType();
|
||||
final Object[] array = new Object[embeddableMappingType.getJdbcValueCount() + ( embeddableMappingType.isPolymorphic() ? 1 : 0 )];
|
||||
final int end = fromString( embeddableMappingType, string, returnEmbeddable, options, array, contentStart );
|
||||
|
||||
if ( returnEmbeddable ) {
|
||||
final StructAttributeValues attributeValues = StructHelper.getAttributeValues( embeddableMappingType, array, options );
|
||||
arrayList.add( instantiate( embeddableMappingType, attributeValues, options.getSessionFactory() ) );
|
||||
}
|
||||
else {
|
||||
arrayList.add( array );
|
||||
}
|
||||
i = end + 1;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException( "XML not properly formed: " + string.substring( start ) );
|
||||
}
|
||||
}
|
||||
// consume the whole closing tag
|
||||
i += ROOT_TAG.length() + 2;
|
||||
tagNameStart = -1;
|
||||
contentStart = -1;
|
||||
}
|
||||
break;
|
||||
case '>':
|
||||
if ( contentStart == -1 ) {
|
||||
// The closing angle bracket of the start tag
|
||||
assert tagNameStart != -1;
|
||||
if ( !ROOT_TAG.equals( string.substring( tagNameStart, i ) ) ) {
|
||||
throw new IllegalArgumentException( "XML not properly formed: " + string.substring( start ) );
|
||||
}
|
||||
contentStart = i + 1;
|
||||
}
|
||||
else {
|
||||
// This must be a char in the content
|
||||
}
|
||||
break;
|
||||
case '/':
|
||||
if ( contentStart == -1 ) {
|
||||
// A shorthand tag encodes null
|
||||
// Also, skip the closing angle bracket
|
||||
arrayList.add( null );
|
||||
i++;
|
||||
tagNameStart = -1;
|
||||
assert string.charAt( i ) == '>';
|
||||
}
|
||||
else {
|
||||
// This must be a char in the content
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException( "XML not properly formed: " + string.substring( start ) );
|
||||
}
|
||||
|
||||
public static String toString(
|
||||
EmbeddableMappingType embeddableMappingType,
|
||||
Object value,
|
||||
WrapperOptions options) {
|
||||
WrapperOptions options) throws SQLException {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append( START_TAG );
|
||||
toString( embeddableMappingType, value, options, new XMLAppender( sb ) );
|
||||
toString( embeddableMappingType, embeddableMappingType.getValues( value ), options, new XMLAppender( sb ) );
|
||||
sb.append( END_TAG );
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String arrayToString(EmbeddableMappingType elementMappingType, Object[] values, WrapperOptions options) {
|
||||
if ( values.length == 0 ) {
|
||||
return EMPTY_COLLECTION_TAG;
|
||||
}
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final XMLAppender xmlAppender = new XMLAppender( sb );
|
||||
sb.append( COLLECTION_START_TAG );
|
||||
for ( Object value : values ) {
|
||||
if ( value == null ) {
|
||||
sb.append( NULL_TAG );
|
||||
}
|
||||
else {
|
||||
sb.append( START_TAG );
|
||||
toString( elementMappingType, elementMappingType.getValues( value ), options, xmlAppender );
|
||||
sb.append( END_TAG );
|
||||
}
|
||||
}
|
||||
sb.append( COLLECTION_END_TAG );
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String arrayToString(
|
||||
JavaType<?> elementJavaType,
|
||||
JdbcType elementJdbcType,
|
||||
Object[] values,
|
||||
WrapperOptions options) {
|
||||
if ( values.length == 0 ) {
|
||||
return EMPTY_COLLECTION_TAG;
|
||||
}
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final XMLAppender xmlAppender = new XMLAppender( sb );
|
||||
sb.append( COLLECTION_START_TAG );
|
||||
for ( Object value : values ) {
|
||||
if ( value == null ) {
|
||||
sb.append( NULL_TAG );
|
||||
}
|
||||
else {
|
||||
sb.append( START_TAG );
|
||||
//noinspection unchecked
|
||||
convertedBasicValueToString( xmlAppender, value, options, (JavaType<Object>) elementJavaType, elementJdbcType );
|
||||
sb.append( END_TAG );
|
||||
}
|
||||
}
|
||||
sb.append( COLLECTION_END_TAG );
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void toString(
|
||||
EmbeddableMappingType embeddableMappingType,
|
||||
Object value,
|
||||
@Nullable Object[] attributeValues,
|
||||
WrapperOptions options,
|
||||
XMLAppender sb) {
|
||||
final Object[] array = embeddableMappingType.getValues( value );
|
||||
for ( int i = 0; i < array.length; i++ ) {
|
||||
if ( array[i] == null ) {
|
||||
continue;
|
||||
}
|
||||
// Always append all the nodes, even if the value is null.
|
||||
// This is done in order to allow using xmlextract/xmlextractvalue
|
||||
// which fail if a XPath expression does not resolve to a result
|
||||
final int attributeCount = embeddableMappingType.getNumberOfAttributeMappings();
|
||||
for ( int i = 0; i < attributeCount; i++ ) {
|
||||
final Object attributeValue = attributeValues == null ? null : attributeValues[i];
|
||||
final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, i );
|
||||
if ( attributeMapping instanceof SelectableMapping ) {
|
||||
final SelectableMapping selectable = (SelectableMapping) attributeMapping;
|
||||
if ( attributeMapping instanceof SelectableMapping selectable ) {
|
||||
final String tagName = selectable.getSelectableName();
|
||||
sb.append( '<' );
|
||||
sb.append( tagName );
|
||||
sb.append( '>' );
|
||||
serializeValueTo( sb, selectable, array[i], options );
|
||||
sb.append( '<' );
|
||||
sb.append( '/' );
|
||||
sb.append( tagName );
|
||||
sb.append( '>' );
|
||||
if ( attributeValue == null ) {
|
||||
sb.append( "/>" );
|
||||
}
|
||||
else {
|
||||
sb.append( '>' );
|
||||
//noinspection unchecked
|
||||
convertedBasicValueToString(
|
||||
sb,
|
||||
selectable.getJdbcMapping().convertToRelationalValue( attributeValue ),
|
||||
options,
|
||||
(JavaType<Object>) selectable.getJdbcMapping().getJdbcJavaType(),
|
||||
selectable.getJdbcMapping().getJdbcType()
|
||||
);
|
||||
sb.append( "</" );
|
||||
sb.append( tagName );
|
||||
sb.append( '>' );
|
||||
}
|
||||
}
|
||||
else if ( attributeMapping instanceof EmbeddedAttributeMapping ) {
|
||||
final EmbeddableMappingType mappingType = (EmbeddableMappingType) attributeMapping.getMappedType();
|
||||
final SelectableMapping aggregateMapping = mappingType.getAggregateMapping();
|
||||
if ( aggregateMapping == null ) {
|
||||
toString(
|
||||
mappingType,
|
||||
array[i],
|
||||
options,
|
||||
sb
|
||||
);
|
||||
}
|
||||
else {
|
||||
final String tagName = aggregateMapping.getSelectableName();
|
||||
final String tagName = aggregateMapping == null ? null : aggregateMapping.getSelectableName();
|
||||
if ( tagName != null ) {
|
||||
sb.append( '<' );
|
||||
sb.append( tagName );
|
||||
sb.append( '>' );
|
||||
toString(
|
||||
mappingType,
|
||||
array[i],
|
||||
options,
|
||||
sb
|
||||
);
|
||||
sb.append( '<' );
|
||||
sb.append( '/' );
|
||||
}
|
||||
toString(
|
||||
mappingType,
|
||||
attributeValue == null ? null : mappingType.getValues( attributeValue ),
|
||||
options,
|
||||
sb
|
||||
);
|
||||
if ( tagName != null ) {
|
||||
sb.append( "</" );
|
||||
sb.append( tagName );
|
||||
sb.append( '>' );
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new UnsupportedOperationException( "Unsupported attribute mapping: " + attributeMapping );
|
||||
throw new UnsupportedOperationException( "Support for attribute mapping type not yet implemented: " + attributeMapping.getClass().getName() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void serializeValueTo(XMLAppender appender, SelectableMapping selectable, Object value, WrapperOptions options) {
|
||||
final JdbcMapping jdbcMapping = selectable.getJdbcMapping();
|
||||
//noinspection unchecked
|
||||
final JavaType<Object> jdbcJavaType = (JavaType<Object>) jdbcMapping.getJdbcJavaType();
|
||||
final Object relationalValue = jdbcMapping.convertToRelationalValue( value );
|
||||
switch ( jdbcMapping.getJdbcType().getDefaultSqlTypeCode() ) {
|
||||
private static void convertedBasicValueToString(
|
||||
XMLAppender appender,
|
||||
Object value,
|
||||
WrapperOptions options,
|
||||
JavaType<Object> jdbcJavaType,
|
||||
JdbcType jdbcType) {
|
||||
switch ( jdbcType.getDefaultSqlTypeCode() ) {
|
||||
case SqlTypes.TINYINT:
|
||||
case SqlTypes.SMALLINT:
|
||||
case SqlTypes.INTEGER:
|
||||
if ( relationalValue instanceof Boolean ) {
|
||||
if ( value instanceof Boolean ) {
|
||||
// BooleanJavaType has this as an implicit conversion
|
||||
appender.append( (Boolean) relationalValue ? '1' : '0' );
|
||||
appender.append( (Boolean) value ? '1' : '0' );
|
||||
break;
|
||||
}
|
||||
if ( value instanceof Enum ) {
|
||||
appender.appendSql( ((Enum<?>) value ).ordinal() );
|
||||
break;
|
||||
}
|
||||
case SqlTypes.BOOLEAN:
|
||||
@ -565,10 +819,11 @@ private static void serializeValueTo(XMLAppender appender, SelectableMapping sel
|
||||
case SqlTypes.DECIMAL:
|
||||
case SqlTypes.NUMERIC:
|
||||
case SqlTypes.DURATION:
|
||||
case SqlTypes.UUID:
|
||||
jdbcJavaType.appendEncodedString(
|
||||
appender,
|
||||
jdbcJavaType.unwrap(
|
||||
relationalValue,
|
||||
value,
|
||||
jdbcJavaType.getJavaTypeClass(),
|
||||
options
|
||||
)
|
||||
@ -578,20 +833,26 @@ private static void serializeValueTo(XMLAppender appender, SelectableMapping sel
|
||||
case SqlTypes.NCHAR:
|
||||
case SqlTypes.VARCHAR:
|
||||
case SqlTypes.NVARCHAR:
|
||||
if ( relationalValue instanceof Boolean ) {
|
||||
if ( value instanceof Boolean ) {
|
||||
// BooleanJavaType has this as an implicit conversion
|
||||
appender.append( (Boolean) relationalValue ? 'Y' : 'N' );
|
||||
appender.append( (Boolean) value ? 'Y' : 'N' );
|
||||
break;
|
||||
}
|
||||
case SqlTypes.LONGVARCHAR:
|
||||
case SqlTypes.LONGNVARCHAR:
|
||||
case SqlTypes.LONG32VARCHAR:
|
||||
case SqlTypes.LONG32NVARCHAR:
|
||||
case SqlTypes.CLOB:
|
||||
case SqlTypes.MATERIALIZED_CLOB:
|
||||
case SqlTypes.NCLOB:
|
||||
case SqlTypes.MATERIALIZED_NCLOB:
|
||||
case SqlTypes.ENUM:
|
||||
case SqlTypes.NAMED_ENUM:
|
||||
appender.startEscaping();
|
||||
jdbcJavaType.appendEncodedString(
|
||||
appender,
|
||||
jdbcJavaType.unwrap(
|
||||
relationalValue,
|
||||
value,
|
||||
jdbcJavaType.getJavaTypeClass(),
|
||||
options
|
||||
)
|
||||
@ -626,11 +887,49 @@ private static void serializeValueTo(XMLAppender appender, SelectableMapping sel
|
||||
case SqlTypes.VARBINARY:
|
||||
case SqlTypes.LONGVARBINARY:
|
||||
case SqlTypes.LONG32VARBINARY:
|
||||
case SqlTypes.UUID:
|
||||
appender.writeBase64( jdbcJavaType.unwrap( relationalValue, byte[].class, options ) );
|
||||
case SqlTypes.BLOB:
|
||||
case SqlTypes.MATERIALIZED_BLOB:
|
||||
appender.write( jdbcJavaType.unwrap( value, byte[].class, options ) );
|
||||
break;
|
||||
case SqlTypes.ARRAY:
|
||||
case SqlTypes.XML_ARRAY:
|
||||
final int length = Array.getLength( value );
|
||||
if ( length != 0 ) {
|
||||
//noinspection unchecked
|
||||
final JavaType<Object> elementJavaType = ( (BasicPluralJavaType<Object>) jdbcJavaType ).getElementJavaType();
|
||||
final JdbcType elementJdbcType = ( (ArrayJdbcType) jdbcType ).getElementJdbcType();
|
||||
|
||||
if ( elementJdbcType instanceof AggregateJdbcType aggregateJdbcType ) {
|
||||
final EmbeddableMappingType embeddableMappingType = aggregateJdbcType.getEmbeddableMappingType();
|
||||
for ( int i = 0; i < length; i++ ) {
|
||||
final Object arrayElement = Array.get( value, i );
|
||||
final Object[] arrayElementValues = arrayElement == null
|
||||
? null
|
||||
: embeddableMappingType.getValues( arrayElement );
|
||||
appender.append( START_TAG );
|
||||
toString( embeddableMappingType, arrayElementValues, options, appender );
|
||||
appender.append( END_TAG );
|
||||
}
|
||||
}
|
||||
else {
|
||||
for ( int i = 0; i < length; i++ ) {
|
||||
final Object arrayElement = Array.get( value, i );
|
||||
if ( arrayElement == null ) {
|
||||
appender.append( '<' );
|
||||
appender.append( ROOT_TAG );
|
||||
appender.append( "/>" );
|
||||
}
|
||||
else {
|
||||
appender.append( START_TAG );
|
||||
convertedBasicValueToString( appender, arrayElement, options, elementJavaType, elementJdbcType );
|
||||
appender.append( END_TAG );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException( "Unsupported JdbcType nested in struct: " + jdbcMapping.getJdbcType() );
|
||||
throw new UnsupportedOperationException( "Unsupported JdbcType nested in struct: " + jdbcType );
|
||||
}
|
||||
}
|
||||
|
||||
@ -641,7 +940,7 @@ private static int getSelectableMapping(
|
||||
if ( selectableIndex == -1 ) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format(
|
||||
"Could not find selectable [%s] in embeddable type [%s] for JSON processing.",
|
||||
"Could not find selectable [%s] in embeddable type [%s] for XML processing.",
|
||||
name,
|
||||
embeddableMappingType.getMappedJavaType().getJavaTypeClass().getName()
|
||||
)
|
||||
@ -677,7 +976,6 @@ private static class XMLAppender extends OutputStream implements SqlAppender {
|
||||
private final static char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
|
||||
private final StringBuilder sb;
|
||||
private boolean escape;
|
||||
private OutputStream base64OutputStream;
|
||||
|
||||
public XMLAppender(StringBuilder sb) {
|
||||
this.sb = sb;
|
||||
@ -792,22 +1090,14 @@ public void write(byte[] bytes, int off, int len) {
|
||||
sb.append( HEX_ARRAY[v & 0x0F] );
|
||||
}
|
||||
}
|
||||
|
||||
public void writeBase64(byte[] bytes) {
|
||||
if ( base64OutputStream == null ) {
|
||||
base64OutputStream = Base64.getEncoder().wrap( this );
|
||||
}
|
||||
try {
|
||||
base64OutputStream.write( bytes );
|
||||
}
|
||||
catch (IOException e) {
|
||||
// Should never happen
|
||||
throw new RuntimeException( e );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final CollectionTags DEFAULT = new CollectionTags( "Collection", ROOT_TAG );
|
||||
|
||||
public static CollectionTags determineCollectionTags(BasicPluralJavaType<?> pluralJavaType, SessionFactoryImplementor sessionFactory) {
|
||||
if ( !sessionFactory.getSessionFactoryOptions().isXmlFormatMapperLegacyFormatEnabled() ) {
|
||||
return DEFAULT;
|
||||
}
|
||||
//noinspection unchecked
|
||||
final JavaType<Object> javaType = (JavaType<Object>) pluralJavaType;
|
||||
final LazySessionWrapperOptions lazySessionWrapperOptions = new LazySessionWrapperOptions( sessionFactory );
|
||||
|
@ -48,10 +48,21 @@ public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) {
|
||||
return new DelayedStructJdbcType( this, structName );
|
||||
}
|
||||
}
|
||||
// prefer json by default for now
|
||||
final JdbcType descriptor = context.getJdbcType( SqlTypes.JSON );
|
||||
if ( descriptor != null ) {
|
||||
return descriptor;
|
||||
// When the column is mapped as XML array, the component type must be SQLXML
|
||||
if ( context.getExplicitJdbcTypeCode() != null && context.getExplicitJdbcTypeCode() == SqlTypes.XML_ARRAY
|
||||
// Also prefer XML is the Dialect prefers XML arrays
|
||||
|| context.getDialect().getPreferredSqlTypeCodeForArray() == SqlTypes.XML_ARRAY ) {
|
||||
final JdbcType descriptor = context.getJdbcType( SqlTypes.SQLXML );
|
||||
if ( descriptor != null ) {
|
||||
return descriptor;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Otherwise use json by default for now
|
||||
final JdbcType descriptor = context.getJdbcType( SqlTypes.JSON );
|
||||
if ( descriptor != null ) {
|
||||
return descriptor;
|
||||
}
|
||||
}
|
||||
throw new JdbcTypeRecommendationException(
|
||||
"Could not determine recommended JdbcType for `" + getTypeName() + "`"
|
||||
|
@ -111,6 +111,22 @@ public int resolveJdbcTypeCode(int jdbcTypeCode) {
|
||||
return delegate.resolveJdbcTypeCode( jdbcTypeCode );
|
||||
}
|
||||
|
||||
@Override
|
||||
@Incubating
|
||||
public boolean isXmlFormatMapperLegacyFormatEnabled() {
|
||||
return delegate.isXmlFormatMapperLegacyFormatEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preferJdbcDatetimeTypes() {
|
||||
return delegate.preferJdbcDatetimeTypes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPreferredSqlTypeCodeForArray(int elementSqlTypeCode) {
|
||||
return delegate.getPreferredSqlTypeCodeForArray( elementSqlTypeCode );
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeConfiguration getTypeConfiguration() {
|
||||
return delegate.getTypeConfiguration();
|
||||
|
@ -239,6 +239,17 @@ default boolean preferJdbcDatetimeTypes() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to use the legacy format for serializing/deserializing XML data.
|
||||
*
|
||||
* @since 7.0
|
||||
* @see org.hibernate.cfg.MappingSettings#XML_FORMAT_MAPPER_LEGACY_FORMAT
|
||||
*/
|
||||
@Incubating
|
||||
default boolean isXmlFormatMapperLegacyFormatEnabled() {
|
||||
return getCurrentBaseSqlTypeIndicators().isXmlFormatMapperLegacyFormatEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to the {@link TypeConfiguration} for access to various type system related registries.
|
||||
*/
|
||||
|
@ -10,10 +10,13 @@
|
||||
import java.sql.SQLException;
|
||||
import java.sql.SQLXML;
|
||||
|
||||
import org.hibernate.dialect.XmlHelper;
|
||||
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
import org.hibernate.type.descriptor.ValueBinder;
|
||||
import org.hibernate.type.descriptor.ValueExtractor;
|
||||
import org.hibernate.type.descriptor.WrapperOptions;
|
||||
import org.hibernate.type.descriptor.java.BasicPluralJavaType;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
|
||||
/**
|
||||
@ -65,19 +68,21 @@ protected <X> X fromString(String string, JavaType<X> javaType, WrapperOptions o
|
||||
//noinspection unchecked
|
||||
return (X) sqlxml;
|
||||
}
|
||||
return options.getSessionFactory().getFastSessionServices().getXmlFormatMapper().fromString(
|
||||
string,
|
||||
javaType,
|
||||
options
|
||||
);
|
||||
return XmlHelper.arrayFromString( javaType, this, string, options );
|
||||
}
|
||||
|
||||
protected <X> String toString(X value, JavaType<X> javaType, WrapperOptions options) {
|
||||
return options.getSessionFactory().getFastSessionServices().getXmlFormatMapper().toString(
|
||||
value,
|
||||
javaType,
|
||||
options
|
||||
);
|
||||
final JdbcType elementJdbcType = getElementJdbcType();
|
||||
final Object[] domainObjects = javaType.unwrap( value, Object[].class, options );
|
||||
if ( elementJdbcType instanceof XmlJdbcType xmlElementJdbcType ) {
|
||||
final EmbeddableMappingType embeddableMappingType = xmlElementJdbcType.getEmbeddableMappingType();
|
||||
return XmlHelper.arrayToString( embeddableMappingType, domainObjects, options );
|
||||
}
|
||||
else {
|
||||
assert !( elementJdbcType instanceof AggregateJdbcType );
|
||||
final JavaType<?> elementJavaType = ( (BasicPluralJavaType<?>) javaType ).getElementJavaType();
|
||||
return XmlHelper.arrayToString( elementJavaType, elementJdbcType, domainObjects, options );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -77,7 +77,7 @@ public Object[] extractJdbcValues(Object rawJdbcValue, WrapperOptions options) t
|
||||
return XmlHelper.fromString( embeddableMappingType, (String) rawJdbcValue, false, options );
|
||||
}
|
||||
|
||||
protected <X> String toString(X value, JavaType<X> javaType, WrapperOptions options) {
|
||||
protected <X> String toString(X value, JavaType<X> javaType, WrapperOptions options) throws SQLException {
|
||||
if ( embeddableMappingType != null ) {
|
||||
return XmlHelper.toString( embeddableMappingType, value, options );
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ public final class JacksonIntegration {
|
||||
private static final boolean JACKSON_XML_AVAILABLE = ableToLoadJacksonXMLMapper();
|
||||
private static final boolean JACKSON_JSON_AVAILABLE = ableToLoadJacksonJSONMapper();
|
||||
private static final JacksonXmlFormatMapper XML_FORMAT_MAPPER = JACKSON_XML_AVAILABLE ? new JacksonXmlFormatMapper() : null;
|
||||
private static final JacksonXmlFormatMapper XML_FORMAT_MAPPER_PORTABLE = JACKSON_XML_AVAILABLE ? new JacksonXmlFormatMapper( false ) : null;
|
||||
private static final JacksonJsonFormatMapper JSON_FORMAT_MAPPER = JACKSON_JSON_AVAILABLE ? new JacksonJsonFormatMapper() : null;
|
||||
|
||||
private JacksonIntegration() {
|
||||
@ -31,6 +32,10 @@ public static FormatMapper getXMLJacksonFormatMapperOrNull() {
|
||||
return XML_FORMAT_MAPPER;
|
||||
}
|
||||
|
||||
public static FormatMapper getXMLJacksonFormatMapperOrNull(boolean legacyFormat) {
|
||||
return legacyFormat ? XML_FORMAT_MAPPER : XML_FORMAT_MAPPER_PORTABLE;
|
||||
}
|
||||
|
||||
public static FormatMapper getJsonJacksonFormatMapperOrNull() {
|
||||
return JSON_FORMAT_MAPPER;
|
||||
}
|
||||
|
@ -5,11 +5,25 @@
|
||||
package org.hibernate.type.format.jackson;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
||||
import org.hibernate.type.descriptor.WrapperOptions;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
|
||||
import org.hibernate.type.format.FormatMapper;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
@ -22,6 +36,7 @@
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
|
||||
import org.hibernate.type.internal.ParameterizedTypeImpl;
|
||||
|
||||
/**
|
||||
* @author Christian Beikov
|
||||
@ -29,18 +44,24 @@
|
||||
public final class JacksonXmlFormatMapper implements FormatMapper {
|
||||
|
||||
public static final String SHORT_NAME = "jackson-xml";
|
||||
private boolean legacyFormat;
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public JacksonXmlFormatMapper() {
|
||||
this( createXmlMapper() );
|
||||
this( true );
|
||||
}
|
||||
|
||||
public JacksonXmlFormatMapper(boolean legacyFormat) {
|
||||
this( createXmlMapper( legacyFormat ) );
|
||||
this.legacyFormat = legacyFormat;
|
||||
}
|
||||
|
||||
public JacksonXmlFormatMapper(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
private static XmlMapper createXmlMapper() {
|
||||
private static XmlMapper createXmlMapper(boolean legacyFormat) {
|
||||
final XmlMapper xmlMapper = new XmlMapper();
|
||||
// needed to automatically find and register Jackson's jsr310 module for java.time support
|
||||
xmlMapper.findAndRegisterModules();
|
||||
@ -50,6 +71,10 @@ private static XmlMapper createXmlMapper() {
|
||||
// see: https://github.com/FasterXML/jackson-dataformat-xml/issues/344
|
||||
final SimpleModule module = new SimpleModule();
|
||||
module.addDeserializer( String[].class, new StringArrayDeserializer() );
|
||||
if ( !legacyFormat ) {
|
||||
module.addDeserializer( byte[].class, new ByteArrayDeserializer() );
|
||||
module.addSerializer( byte[].class, new ByteArraySerializer() );
|
||||
}
|
||||
xmlMapper.registerModule( module );
|
||||
return xmlMapper;
|
||||
}
|
||||
@ -60,6 +85,50 @@ public <T> T fromString(CharSequence charSequence, JavaType<T> javaType, Wrapper
|
||||
return (T) charSequence.toString();
|
||||
}
|
||||
try {
|
||||
if ( !legacyFormat ) {
|
||||
if ( Map.class.isAssignableFrom( javaType.getJavaTypeClass() ) ) {
|
||||
final Type keyType;
|
||||
final Type elementType;
|
||||
if ( javaType.getJavaType() instanceof ParameterizedType parameterizedType ) {
|
||||
keyType = parameterizedType.getActualTypeArguments()[0];
|
||||
elementType = parameterizedType.getActualTypeArguments()[1];
|
||||
}
|
||||
else {
|
||||
keyType = Object.class;
|
||||
elementType = Object.class;
|
||||
}
|
||||
final MapWrapper<?, ?> collectionWrapper = objectMapper.readValue(
|
||||
charSequence.toString(),
|
||||
objectMapper.constructType( new ParameterizedTypeImpl( MapWrapper.class,
|
||||
new Type[] {keyType, elementType}, null ) )
|
||||
);
|
||||
final Map<Object, Object> map = new LinkedHashMap<>( collectionWrapper.entry.size() );
|
||||
for ( EntryWrapper<?, ?> entry : collectionWrapper.entry ) {
|
||||
map.put( entry.key, entry.value );
|
||||
}
|
||||
return javaType.wrap( map, wrapperOptions );
|
||||
}
|
||||
else if ( Collection.class.isAssignableFrom( javaType.getJavaTypeClass() ) ) {
|
||||
final Type elementType = javaType.getJavaType() instanceof ParameterizedType parameterizedType
|
||||
? parameterizedType.getActualTypeArguments()[0]
|
||||
: Object.class;
|
||||
final CollectionWrapper<?> collectionWrapper = objectMapper.readValue(
|
||||
charSequence.toString(),
|
||||
objectMapper.constructType(
|
||||
new ParameterizedTypeImpl( CollectionWrapper.class, new Type[] {elementType},
|
||||
null ) )
|
||||
);
|
||||
return javaType.wrap( collectionWrapper.value, wrapperOptions );
|
||||
}
|
||||
else if ( javaType.getJavaTypeClass().isArray() ) {
|
||||
final CollectionWrapper<?> collectionWrapper = objectMapper.readValue(
|
||||
charSequence.toString(),
|
||||
objectMapper.constructType( new ParameterizedTypeImpl( CollectionWrapper.class,
|
||||
new Type[] {javaType.getJavaTypeClass().getComponentType()}, null ) )
|
||||
);
|
||||
return javaType.wrap( collectionWrapper.value, wrapperOptions );
|
||||
}
|
||||
}
|
||||
return objectMapper.readValue(
|
||||
charSequence.toString(),
|
||||
objectMapper.constructType( javaType.getJavaType() )
|
||||
@ -75,6 +144,60 @@ public <T> String toString(T value, JavaType<T> javaType, WrapperOptions wrapper
|
||||
if ( javaType.getJavaType() == String.class || javaType.getJavaType() == Object.class ) {
|
||||
return (String) value;
|
||||
}
|
||||
if ( !legacyFormat ) {
|
||||
if ( Map.class.isAssignableFrom( javaType.getJavaTypeClass() ) ) {
|
||||
final Type keyType;
|
||||
final Type elementType;
|
||||
if ( javaType.getJavaType() instanceof ParameterizedType parameterizedType ) {
|
||||
keyType = parameterizedType.getActualTypeArguments()[0];
|
||||
elementType = parameterizedType.getActualTypeArguments()[1];
|
||||
}
|
||||
else {
|
||||
keyType = Object.class;
|
||||
elementType = Object.class;
|
||||
}
|
||||
final MapWrapper<Object, Object> mapWrapper = new MapWrapper<>();
|
||||
for ( Map.Entry<?, ?> entry : ((Map<?, ?>) value).entrySet() ) {
|
||||
mapWrapper.entry.add( new EntryWrapper<>( entry.getKey(), entry.getValue() ) );
|
||||
}
|
||||
return writeValueAsString(
|
||||
mapWrapper,
|
||||
javaType,
|
||||
new ParameterizedTypeImpl( MapWrapper.class, new Type[] {keyType, elementType}, null )
|
||||
);
|
||||
}
|
||||
else if ( Collection.class.isAssignableFrom( javaType.getJavaTypeClass() ) ) {
|
||||
final Type elementType = javaType.getJavaType() instanceof ParameterizedType parameterizedType
|
||||
? parameterizedType.getActualTypeArguments()[0]
|
||||
: Object.class;
|
||||
return writeValueAsString(
|
||||
new CollectionWrapper<>( (Collection<?>) value ),
|
||||
javaType,
|
||||
new ParameterizedTypeImpl( CollectionWrapper.class, new Type[] {elementType}, null )
|
||||
);
|
||||
}
|
||||
else if ( javaType.getJavaTypeClass().isArray() ) {
|
||||
final CollectionWrapper<Object> collectionWrapper;
|
||||
if ( Object[].class.isAssignableFrom( javaType.getJavaTypeClass() ) ) {
|
||||
collectionWrapper = new CollectionWrapper<>( Arrays.asList( (Object[]) value ) );
|
||||
}
|
||||
else {
|
||||
// Primitive arrays get a special treatment
|
||||
final int length = Array.getLength( value );
|
||||
final List<Object> list = new ArrayList<>( length );
|
||||
for ( int i = 0; i < length; i++ ) {
|
||||
list.add( Array.get( value, i ) );
|
||||
}
|
||||
collectionWrapper = new CollectionWrapper<>( list );
|
||||
}
|
||||
return writeValueAsString(
|
||||
collectionWrapper,
|
||||
javaType,
|
||||
new ParameterizedTypeImpl( CollectionWrapper.class,
|
||||
new Type[] {javaType.getJavaTypeClass().getComponentType()}, null )
|
||||
);
|
||||
}
|
||||
}
|
||||
return writeValueAsString( value, javaType, javaType.getJavaType() );
|
||||
}
|
||||
|
||||
@ -87,6 +210,51 @@ private <T> String writeValueAsString(Object value, JavaType<T> javaType, Type t
|
||||
}
|
||||
}
|
||||
|
||||
@JacksonXmlRootElement(localName = "Collection")
|
||||
public static class CollectionWrapper<E> {
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
@JacksonXmlProperty(localName = "e")
|
||||
Collection<E> value;
|
||||
|
||||
public CollectionWrapper() {
|
||||
this.value = new ArrayList<>();
|
||||
}
|
||||
|
||||
public CollectionWrapper(Collection<E> value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
@JacksonXmlRootElement(localName = "Map")
|
||||
public static class MapWrapper<K, V> {
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
@JacksonXmlProperty(localName = "e")
|
||||
Collection<EntryWrapper<K, V>> entry;
|
||||
|
||||
public MapWrapper() {
|
||||
this.entry = new ArrayList<>();
|
||||
}
|
||||
|
||||
public MapWrapper(Collection<EntryWrapper<K, V>> entry) {
|
||||
this.entry = entry;
|
||||
}
|
||||
}
|
||||
|
||||
public static class EntryWrapper<K, V> {
|
||||
@JacksonXmlProperty(localName = "k")
|
||||
K key;
|
||||
@JacksonXmlProperty(localName = "v")
|
||||
V value;
|
||||
|
||||
public EntryWrapper() {
|
||||
}
|
||||
|
||||
public EntryWrapper(K key, V value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
private static class StringArrayDeserializer extends JsonDeserializer<String[]> {
|
||||
@Override
|
||||
public String[] deserialize(JsonParser jp, DeserializationContext deserializationContext) throws IOException {
|
||||
@ -100,4 +268,28 @@ public String[] deserialize(JsonParser jp, DeserializationContext deserializatio
|
||||
return result.toArray( String[]::new );
|
||||
}
|
||||
}
|
||||
|
||||
private static class ByteArrayDeserializer extends JsonDeserializer<byte[]> {
|
||||
@Override
|
||||
public byte[] deserialize(JsonParser jp, DeserializationContext deserializationContext) throws IOException {
|
||||
return PrimitiveByteArrayJavaType.INSTANCE.fromString( jp.getValueAsString() );
|
||||
}
|
||||
}
|
||||
|
||||
public static class ByteArraySerializer extends StdSerializer<byte[]> {
|
||||
|
||||
public ByteArraySerializer() {
|
||||
super( byte[].class );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty(SerializerProvider prov, byte[] value) {
|
||||
return value.length == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(byte[] value, JsonGenerator g, SerializerProvider provider) throws IOException {
|
||||
g.writeString( PrimitiveByteArrayJavaType.INSTANCE.toString( value ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,8 @@
|
||||
|
||||
import javax.xml.namespace.QName;
|
||||
|
||||
import jakarta.xml.bind.annotation.XmlElement;
|
||||
import org.hibernate.dialect.XmlHelper;
|
||||
import org.hibernate.internal.util.ReflectHelper;
|
||||
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||
import org.hibernate.sql.ast.spi.StringBuilderSqlAppender;
|
||||
@ -44,8 +46,27 @@
|
||||
public final class JaxbXmlFormatMapper implements FormatMapper {
|
||||
|
||||
public static final String SHORT_NAME = "jaxb";
|
||||
private final boolean legacyFormat;
|
||||
private final String collectionElementTagName;
|
||||
private final String mapKeyTagName;
|
||||
private final String mapValueTagName;
|
||||
|
||||
public JaxbXmlFormatMapper() {
|
||||
this( true );
|
||||
}
|
||||
|
||||
public JaxbXmlFormatMapper(boolean legacyFormat) {
|
||||
this.legacyFormat = legacyFormat;
|
||||
if ( legacyFormat ) {
|
||||
collectionElementTagName = "value";
|
||||
mapKeyTagName = "key";
|
||||
mapValueTagName = "value";
|
||||
}
|
||||
else {
|
||||
collectionElementTagName = XmlHelper.ROOT_TAG;
|
||||
mapKeyTagName = "k";
|
||||
mapValueTagName = "v";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -64,18 +85,27 @@ public <T> T fromString(CharSequence charSequence, JavaType<T> javaType, Wrapper
|
||||
final Type[] typeArguments = ( (ParameterizedType) javaType.getJavaType() ).getActualTypeArguments();
|
||||
keyClass = ReflectHelper.getClass( typeArguments[0] );
|
||||
valueClass = ReflectHelper.getClass( typeArguments[1] );
|
||||
context = JAXBContext.newInstance( MapWrapper.class, keyClass, valueClass );
|
||||
if ( legacyFormat ) {
|
||||
context = JAXBContext.newInstance( LegacyMapWrapper.class, keyClass, valueClass );
|
||||
}
|
||||
else {
|
||||
context = JAXBContext.newInstance( MapWrapper.class, EntryWrapper.class, keyClass, valueClass );
|
||||
}
|
||||
}
|
||||
else {
|
||||
keyClass = Object.class;
|
||||
valueClass = Object.class;
|
||||
context = JAXBContext.newInstance( MapWrapper.class );
|
||||
if ( legacyFormat ) {
|
||||
context = JAXBContext.newInstance( LegacyMapWrapper.class );
|
||||
}
|
||||
else {
|
||||
context = JAXBContext.newInstance( MapWrapper.class, EntryWrapper.class );
|
||||
}
|
||||
}
|
||||
final Unmarshaller unmarshaller = context.createUnmarshaller();
|
||||
final MapWrapper mapWrapper = (MapWrapper) unmarshaller
|
||||
final ManagedMapWrapper mapWrapper = (ManagedMapWrapper) unmarshaller
|
||||
.unmarshal( new StringReader( charSequence.toString() ) );
|
||||
final Collection<Object> elements = mapWrapper.elements;
|
||||
final Map<Object, Object> map = CollectionHelper.linkedMapOfSize( elements.size() >> 1 );
|
||||
final Map<Object, Object> map = CollectionHelper.linkedMapOfSize( mapWrapper.size() >> 1 );
|
||||
final JAXBIntrospector jaxbIntrospector = context.createJAXBIntrospector();
|
||||
final JAXBElementTransformer keyTransformer;
|
||||
final JAXBElementTransformer valueTransformer;
|
||||
@ -83,7 +113,7 @@ public <T> T fromString(CharSequence charSequence, JavaType<T> javaType, Wrapper
|
||||
keyTransformer = createTransformer(
|
||||
appender,
|
||||
keyClass,
|
||||
"key",
|
||||
mapKeyTagName,
|
||||
null,
|
||||
jaxbIntrospector,
|
||||
wrapperOptions
|
||||
@ -91,7 +121,7 @@ public <T> T fromString(CharSequence charSequence, JavaType<T> javaType, Wrapper
|
||||
valueTransformer = createTransformer(
|
||||
appender,
|
||||
( (BasicPluralJavaType<?>) javaType ).getElementJavaType(),
|
||||
"value",
|
||||
mapValueTagName,
|
||||
null,
|
||||
jaxbIntrospector,
|
||||
wrapperOptions
|
||||
@ -101,7 +131,7 @@ public <T> T fromString(CharSequence charSequence, JavaType<T> javaType, Wrapper
|
||||
keyTransformer = createTransformer(
|
||||
appender,
|
||||
keyClass,
|
||||
"key",
|
||||
mapKeyTagName,
|
||||
null,
|
||||
jaxbIntrospector,
|
||||
wrapperOptions
|
||||
@ -109,16 +139,26 @@ public <T> T fromString(CharSequence charSequence, JavaType<T> javaType, Wrapper
|
||||
valueTransformer = createTransformer(
|
||||
appender,
|
||||
valueClass,
|
||||
"value",
|
||||
mapValueTagName,
|
||||
null,
|
||||
jaxbIntrospector,
|
||||
wrapperOptions
|
||||
);
|
||||
}
|
||||
for ( final Iterator<Object> iterator = elements.iterator(); iterator.hasNext(); ) {
|
||||
final Object key = keyTransformer.fromJAXBElement( iterator.next(), unmarshaller );
|
||||
final Object value = valueTransformer.fromJAXBElement( iterator.next(), unmarshaller );
|
||||
map.put( key, value );
|
||||
if ( legacyFormat ) {
|
||||
final Collection<Object> elements = ( (LegacyMapWrapper) mapWrapper).elements;
|
||||
for ( final Iterator<Object> iterator = elements.iterator(); iterator.hasNext(); ) {
|
||||
final Object key = keyTransformer.fromJAXBElement( iterator.next(), unmarshaller );
|
||||
final Object value = valueTransformer.fromJAXBElement( iterator.next(), unmarshaller );
|
||||
map.put( key, value );
|
||||
}
|
||||
}
|
||||
else {
|
||||
for ( EntryWrapper entry : ((MapWrapper) mapWrapper).entries ) {
|
||||
final Object key = keyTransformer.fromXmlContent( entry.key );
|
||||
final Object value = valueTransformer.fromXmlContent( entry.value );
|
||||
map.put( key, value );
|
||||
}
|
||||
}
|
||||
return javaType.wrap( map, wrapperOptions );
|
||||
}
|
||||
@ -145,7 +185,7 @@ else if ( Collection.class.isAssignableFrom( javaType.getJavaTypeClass() ) ) {
|
||||
valueTransformer = createTransformer(
|
||||
appender,
|
||||
( (BasicPluralJavaType<?>) javaType ).getElementJavaType(),
|
||||
"value",
|
||||
collectionElementTagName,
|
||||
null,
|
||||
jaxbIntrospector,
|
||||
wrapperOptions
|
||||
@ -155,7 +195,7 @@ else if ( Collection.class.isAssignableFrom( javaType.getJavaTypeClass() ) ) {
|
||||
valueTransformer = createTransformer(
|
||||
appender,
|
||||
valueClass,
|
||||
"value",
|
||||
collectionElementTagName,
|
||||
null,
|
||||
jaxbIntrospector,
|
||||
wrapperOptions
|
||||
@ -180,7 +220,7 @@ else if ( javaType.getJavaTypeClass().isArray() ) {
|
||||
valueTransformer = createTransformer(
|
||||
appender,
|
||||
( (BasicPluralJavaType<?>) javaType ).getElementJavaType(),
|
||||
"value",
|
||||
collectionElementTagName,
|
||||
null,
|
||||
jaxbIntrospector,
|
||||
wrapperOptions
|
||||
@ -190,7 +230,7 @@ else if ( javaType.getJavaTypeClass().isArray() ) {
|
||||
valueTransformer = createTransformer(
|
||||
appender,
|
||||
valueClass,
|
||||
"value",
|
||||
collectionElementTagName,
|
||||
null,
|
||||
jaxbIntrospector,
|
||||
wrapperOptions
|
||||
@ -243,19 +283,28 @@ public <T> String toString(T value, JavaType<T> javaType, WrapperOptions wrapper
|
||||
final JAXBContext context;
|
||||
final Class<Object> keyClass;
|
||||
final Class<Object> valueClass;
|
||||
final MapWrapper mapWrapper = new MapWrapper();
|
||||
final Map<?, ?> map = (Map<?, ?>) value;
|
||||
if ( javaType.getJavaType() instanceof ParameterizedType ) {
|
||||
final Type[] typeArguments = ( (ParameterizedType) javaType.getJavaType() ).getActualTypeArguments();
|
||||
keyClass = ReflectHelper.getClass( typeArguments[0] );
|
||||
valueClass = ReflectHelper.getClass( typeArguments[1] );
|
||||
context = JAXBContext.newInstance( MapWrapper.class, keyClass, valueClass );
|
||||
if ( legacyFormat ) {
|
||||
context = JAXBContext.newInstance( LegacyMapWrapper.class, keyClass, valueClass );
|
||||
}
|
||||
else {
|
||||
context = JAXBContext.newInstance( MapWrapper.class, EntryWrapper.class, keyClass, valueClass );
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ( map.isEmpty() ) {
|
||||
keyClass = Object.class;
|
||||
valueClass = Object.class;
|
||||
context = JAXBContext.newInstance( MapWrapper.class );
|
||||
if ( legacyFormat ) {
|
||||
context = JAXBContext.newInstance( LegacyMapWrapper.class );
|
||||
}
|
||||
else {
|
||||
context = JAXBContext.newInstance( MapWrapper.class, EntryWrapper.class );
|
||||
}
|
||||
}
|
||||
else {
|
||||
final Map.Entry<?, ?> firstEntry = map.entrySet().iterator().next();
|
||||
@ -263,9 +312,15 @@ public <T> String toString(T value, JavaType<T> javaType, WrapperOptions wrapper
|
||||
keyClass = (Class<Object>) firstEntry.getKey().getClass();
|
||||
//noinspection unchecked
|
||||
valueClass = (Class<Object>) firstEntry.getValue().getClass();
|
||||
context = JAXBContext.newInstance( MapWrapper.class, keyClass, valueClass );
|
||||
if ( legacyFormat ) {
|
||||
context = JAXBContext.newInstance( LegacyMapWrapper.class, keyClass, valueClass );
|
||||
}
|
||||
else {
|
||||
context = JAXBContext.newInstance( MapWrapper.class, EntryWrapper.class, keyClass, valueClass );
|
||||
}
|
||||
}
|
||||
}
|
||||
final ManagedMapWrapper managedMapWrapper = legacyFormat ? new LegacyMapWrapper() : new MapWrapper();
|
||||
if ( !map.isEmpty() ) {
|
||||
Object exampleKey = null;
|
||||
Object exampleValue = null;
|
||||
@ -289,7 +344,7 @@ public <T> String toString(T value, JavaType<T> javaType, WrapperOptions wrapper
|
||||
final JAXBElementTransformer keyTransformer = createTransformer(
|
||||
appender,
|
||||
keyClass,
|
||||
"key",
|
||||
mapKeyTagName,
|
||||
exampleKey,
|
||||
jaxbIntrospector,
|
||||
wrapperOptions
|
||||
@ -297,17 +352,29 @@ public <T> String toString(T value, JavaType<T> javaType, WrapperOptions wrapper
|
||||
final JAXBElementTransformer valueTransformer = createTransformer(
|
||||
appender,
|
||||
valueClass,
|
||||
"value",
|
||||
mapValueTagName,
|
||||
exampleValue,
|
||||
jaxbIntrospector,
|
||||
wrapperOptions
|
||||
);
|
||||
for ( Map.Entry<?, ?> entry : map.entrySet() ) {
|
||||
mapWrapper.elements.add( keyTransformer.toJAXBElement( entry.getKey() ) );
|
||||
mapWrapper.elements.add( valueTransformer.toJAXBElement( entry.getValue() ) );
|
||||
if ( legacyFormat ) {
|
||||
final LegacyMapWrapper legacyMapWrapper = (LegacyMapWrapper) managedMapWrapper;
|
||||
for ( Map.Entry<?, ?> entry : map.entrySet() ) {
|
||||
legacyMapWrapper.elements.add( keyTransformer.toJAXBElement( entry.getKey() ) );
|
||||
legacyMapWrapper.elements.add( valueTransformer.toJAXBElement( entry.getValue() ) );
|
||||
}
|
||||
}
|
||||
else {
|
||||
final MapWrapper mapWrapper = (MapWrapper) managedMapWrapper;
|
||||
for ( Map.Entry<?, ?> entry : map.entrySet() ) {
|
||||
mapWrapper.entries.add( new EntryWrapper(
|
||||
(String) keyTransformer.toJAXBElement( entry.getKey() ).getValue(),
|
||||
(String) valueTransformer.toJAXBElement( entry.getValue() ).getValue()
|
||||
) );
|
||||
}
|
||||
}
|
||||
}
|
||||
createMarshaller( context ).marshal( mapWrapper, stringWriter );
|
||||
createMarshaller( context ).marshal( managedMapWrapper, stringWriter );
|
||||
}
|
||||
else if ( Collection.class.isAssignableFrom( javaType.getJavaTypeClass() ) ) {
|
||||
final JAXBContext context;
|
||||
@ -347,7 +414,7 @@ else if ( Collection.class.isAssignableFrom( javaType.getJavaTypeClass() ) ) {
|
||||
valueTransformer = createTransformer(
|
||||
appender,
|
||||
( (BasicPluralJavaType<?>) javaType ).getElementJavaType(),
|
||||
"value",
|
||||
collectionElementTagName,
|
||||
exampleValue,
|
||||
context.createJAXBIntrospector(),
|
||||
wrapperOptions
|
||||
@ -357,7 +424,7 @@ else if ( Collection.class.isAssignableFrom( javaType.getJavaTypeClass() ) ) {
|
||||
valueTransformer = createTransformer(
|
||||
appender,
|
||||
valueClass,
|
||||
"value",
|
||||
collectionElementTagName,
|
||||
exampleValue,
|
||||
context.createJAXBIntrospector(),
|
||||
wrapperOptions
|
||||
@ -389,7 +456,7 @@ else if ( javaType.getJavaTypeClass().isArray() ) {
|
||||
transformer = createTransformer(
|
||||
appender,
|
||||
( (BasicPluralJavaType<?>) javaType ).getElementJavaType(),
|
||||
"value",
|
||||
collectionElementTagName,
|
||||
exampleElement,
|
||||
context.createJAXBIntrospector(),
|
||||
wrapperOptions
|
||||
@ -399,7 +466,7 @@ else if ( javaType.getJavaTypeClass().isArray() ) {
|
||||
transformer = createTransformer(
|
||||
appender,
|
||||
valueClass,
|
||||
"value",
|
||||
collectionElementTagName,
|
||||
exampleElement,
|
||||
context.createJAXBIntrospector(),
|
||||
wrapperOptions
|
||||
@ -417,7 +484,7 @@ else if ( javaType.getJavaTypeClass().isArray() ) {
|
||||
final JavaTypeJAXBElementTransformer transformer = new JavaTypeJAXBElementTransformer(
|
||||
appender,
|
||||
( (BasicPluralJavaType<?>) javaType ).getElementJavaType(),
|
||||
"value"
|
||||
collectionElementTagName
|
||||
);
|
||||
for ( int i = 0; i < length; i++ ) {
|
||||
list.add( transformer.toJAXBElement( Array.get( value, i ) ) );
|
||||
@ -500,18 +567,61 @@ private Marshaller createMarshaller(JAXBContext context) throws JAXBException {
|
||||
return marshaller;
|
||||
}
|
||||
|
||||
public static interface ManagedMapWrapper {
|
||||
int size();
|
||||
}
|
||||
|
||||
@XmlRootElement(name = "Map")
|
||||
public static class MapWrapper {
|
||||
public static class LegacyMapWrapper implements ManagedMapWrapper {
|
||||
@XmlAnyElement
|
||||
Collection<Object> elements;
|
||||
|
||||
public MapWrapper() {
|
||||
public LegacyMapWrapper() {
|
||||
this.elements = new ArrayList<>();
|
||||
}
|
||||
|
||||
public MapWrapper(Collection<Object> elements) {
|
||||
public LegacyMapWrapper(Collection<Object> elements) {
|
||||
this.elements = elements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return elements.size();
|
||||
}
|
||||
}
|
||||
|
||||
@XmlRootElement(name = "Map")
|
||||
public static class MapWrapper implements ManagedMapWrapper {
|
||||
@XmlElement(name = "e")
|
||||
Collection<EntryWrapper> entries;
|
||||
|
||||
public MapWrapper() {
|
||||
this.entries = new ArrayList<>();
|
||||
}
|
||||
|
||||
public MapWrapper(Collection<EntryWrapper> elements) {
|
||||
this.entries = elements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return entries.size();
|
||||
}
|
||||
}
|
||||
|
||||
public static class EntryWrapper {
|
||||
@XmlElement(name = "k", nillable = true)
|
||||
String key;
|
||||
@XmlElement(name = "v", nillable = true)
|
||||
String value;
|
||||
|
||||
public EntryWrapper() {
|
||||
}
|
||||
|
||||
public EntryWrapper(String key, String value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
@XmlRootElement(name = "Collection")
|
||||
@ -531,6 +641,7 @@ public CollectionWrapper(Collection<Object> elements) {
|
||||
private static interface JAXBElementTransformer {
|
||||
JAXBElement<?> toJAXBElement(Object o);
|
||||
Object fromJAXBElement(Object element, Unmarshaller unmarshaller) throws JAXBException;
|
||||
Object fromXmlContent(String content);
|
||||
}
|
||||
|
||||
private static class SimpleJAXBElementTransformer implements JAXBElementTransformer {
|
||||
@ -560,6 +671,11 @@ public Object fromJAXBElement(Object element, Unmarshaller unmarshaller) throws
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fromXmlContent(String content) {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
private static class JavaTypeJAXBElementTransformer implements JAXBElementTransformer {
|
||||
@ -593,8 +709,13 @@ public JAXBElement<?> toJAXBElement(Object o) {
|
||||
|
||||
@Override
|
||||
public Object fromJAXBElement(Object element, Unmarshaller unmarshaller) throws JAXBException {
|
||||
final String value = unmarshaller.unmarshal( (Node) element, String.class ).getValue();
|
||||
final String value = element == null ? null : unmarshaller.unmarshal( (Node) element, String.class ).getValue();
|
||||
return value == null ? null : elementJavaType.fromEncodedString( value, 0, value.length() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fromXmlContent(String content) {
|
||||
return content == null ? null : elementJavaType.fromEncodedString( content, 0, content.length() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -488,6 +488,19 @@ public boolean preferJdbcDatetimeTypes() {
|
||||
&& sessionFactory.getSessionFactoryOptions().isPreferJdbcDatetimeTypesInNativeQueriesEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isXmlFormatMapperLegacyFormatEnabled() {
|
||||
if ( metadataBuildingContext != null ) {
|
||||
return metadataBuildingContext.getBuildingOptions().isXmlFormatMapperLegacyFormatEnabled();
|
||||
}
|
||||
else if ( sessionFactory != null ) {
|
||||
return sessionFactory.getSessionFactoryOptions().isXmlFormatMapperLegacyFormatEnabled();
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Scope(TypeConfiguration typeConfiguration) {
|
||||
this.typeConfiguration = typeConfiguration;
|
||||
}
|
||||
|
@ -193,6 +193,7 @@ public void testNodeBuilderXmlTableObject(SessionFactoryScope scope) {
|
||||
}
|
||||
|
||||
@Test
|
||||
@SkipForDialect(dialectClass = SybaseASEDialect.class, reason = "Sybase doesn't support such xpath expressions directly in xmltable. We could emulate that through generating xmlextract calls though")
|
||||
public void testCorrelateXmlTable(SessionFactoryScope scope) {
|
||||
scope.inSession( em -> {
|
||||
final String query = """
|
||||
@ -202,10 +203,10 @@ public void testCorrelateXmlTable(SessionFactoryScope scope) {
|
||||
t.theString,
|
||||
t.theBoolean
|
||||
from XmlHolder e join lateral xmltable('/Map' passing e.xml columns
|
||||
theInt Integer,
|
||||
theFloat Float,
|
||||
theString String,
|
||||
theBoolean Boolean
|
||||
theInt Integer path 'e[k/text()="theInt"]/v',
|
||||
theFloat Float path 'e[k/text()="theFloat"]/v',
|
||||
theString String path 'e[k/text()="theString"]/v',
|
||||
theBoolean Boolean path 'e[k/text()="theBoolean"]/v'
|
||||
) t
|
||||
""";
|
||||
List<Tuple> resultList = em.createQuery( query, Tuple.class ).getResultList();
|
||||
|
@ -0,0 +1,325 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
* Copyright Red Hat Inc. and Hibernate Authors
|
||||
*/
|
||||
package org.hibernate.orm.test.mapping.type.format;
|
||||
|
||||
import org.hibernate.testing.orm.domain.StandardDomainModel;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScopeAware;
|
||||
import org.hibernate.type.descriptor.WrapperOptions;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
|
||||
import org.hibernate.type.format.FormatMapper;
|
||||
import org.hibernate.type.format.jackson.JacksonXmlFormatMapper;
|
||||
import org.hibernate.type.format.jaxb.JaxbXmlFormatMapper;
|
||||
import org.hibernate.type.internal.ParameterizedTypeImpl;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@DomainModel(standardModels = StandardDomainModel.LIBRARY)
|
||||
@SessionFactory
|
||||
public class XmlFormatterTest implements SessionFactoryScopeAware {
|
||||
|
||||
private SessionFactoryScope scope;
|
||||
|
||||
@Override
|
||||
public void injectSessionFactoryScope(SessionFactoryScope scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
private static Stream<Arguments> formatMappers() {
|
||||
return Stream.of( new JaxbXmlFormatMapper( false ), new JacksonXmlFormatMapper( false ) )
|
||||
.map( Arguments::of );
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("formatMappers")
|
||||
public void testCollection(FormatMapper formatMapper) {
|
||||
assertCollection( List.of(), Integer.class, formatMapper );
|
||||
assertCollection( Arrays.asList( new Integer[]{ null } ), Integer.class, formatMapper );
|
||||
assertCollection( List.of( "Abc" ), String.class, formatMapper );
|
||||
assertCollection( List.of( 123 ), Integer.class, formatMapper );
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("formatMappers")
|
||||
public void testArray(FormatMapper formatMapper) {
|
||||
assertArray( new int[0], formatMapper );
|
||||
assertArray( new String[]{ null }, formatMapper );
|
||||
assertArray( new String[]{ "Abc" }, formatMapper );
|
||||
assertArray( new int[]{ 123 }, formatMapper );
|
||||
assertArray( new Integer[]{ 123 }, formatMapper );
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("formatMappers")
|
||||
public void testByteArray(FormatMapper formatMapper) {
|
||||
assertArray( new byte[0][0], formatMapper );
|
||||
assertArray( new byte[][]{ new byte[]{ 1 } }, formatMapper );
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("formatMappers")
|
||||
public void testMap(FormatMapper formatMapper) {
|
||||
assertMap( Map.of(), Integer.class, Integer.class, formatMapper );
|
||||
assertMap( new HashMap<>(){{ put(null, "Abc"); }}, Integer.class, String.class, formatMapper );
|
||||
assertMap( new HashMap<>(){{ put(123, null); }}, Integer.class, String.class, formatMapper );
|
||||
assertMap( Map.of( 123, "Abc" ), Integer.class, String.class, formatMapper );
|
||||
}
|
||||
|
||||
private void assertCollection(List<Object> values, Type elementType, FormatMapper formatMapper) {
|
||||
assertXmlEquals( expectedCollectionString( values ), collectionToString( values, elementType, formatMapper ) );
|
||||
}
|
||||
|
||||
private void assertArray(Object values, FormatMapper formatMapper) {
|
||||
assertXmlEquals( expectedArrayString( values ), arrayToString( values, formatMapper ) );
|
||||
}
|
||||
|
||||
private void assertMap(Map<?, ?> values, Type keyType, Type elementType, FormatMapper formatMapper) {
|
||||
assertXmlEquals( expectedMapString( values ), mapToString( values, keyType, elementType, formatMapper ) );
|
||||
}
|
||||
|
||||
private String expectedArrayString(Object values) {
|
||||
if ( values instanceof Object[] array ) {
|
||||
return expectedCollectionString( Arrays.asList( array ) );
|
||||
}
|
||||
else {
|
||||
final int length = Array.getLength( values );
|
||||
final ArrayList<Object> list = new ArrayList<>( length );
|
||||
for ( int i = 0; i < length; i++ ) {
|
||||
list.add( Array.get( values, i ) );
|
||||
}
|
||||
return expectedCollectionString( list );
|
||||
}
|
||||
}
|
||||
|
||||
private String expectedCollectionString(Collection<?> values) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append( "<Collection" );
|
||||
if ( values.isEmpty() ) {
|
||||
sb.append( "/>" );
|
||||
}
|
||||
else {
|
||||
sb.append( ">" );
|
||||
for ( Object value : values ) {
|
||||
if ( value == null ) {
|
||||
sb.append( "<e xsi:nil=\"true\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"/>" );
|
||||
}
|
||||
else {
|
||||
sb.append( "<e>" );
|
||||
if ( value instanceof byte[] bytes ) {
|
||||
sb.append( PrimitiveByteArrayJavaType.INSTANCE.toString( bytes ) );
|
||||
}
|
||||
else {
|
||||
sb.append( value );
|
||||
}
|
||||
sb.append( "</e>" );
|
||||
}
|
||||
}
|
||||
sb.append( "</Collection>" );
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String expectedMapString(Map<?, ?> values) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append( "<Map" );
|
||||
if ( values.isEmpty() ) {
|
||||
sb.append( "/>" );
|
||||
}
|
||||
else {
|
||||
sb.append( ">" );
|
||||
for ( Map.Entry<?, ?> entry : values.entrySet() ) {
|
||||
final Object key = entry.getKey();
|
||||
final Object value = entry.getValue();
|
||||
sb.append( "<e>" );
|
||||
|
||||
if ( key == null ) {
|
||||
sb.append( "<k xsi:nil=\"true\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"/>" );
|
||||
}
|
||||
else {
|
||||
sb.append( "<k>" );
|
||||
if ( key instanceof byte[] bytes ) {
|
||||
sb.append( PrimitiveByteArrayJavaType.INSTANCE.toString( bytes ) );
|
||||
}
|
||||
else {
|
||||
sb.append( key );
|
||||
}
|
||||
sb.append( "</k>" );
|
||||
}
|
||||
if ( value == null ) {
|
||||
sb.append( "<v xsi:nil=\"true\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"/>" );
|
||||
}
|
||||
else {
|
||||
sb.append( "<v>" );
|
||||
if ( value instanceof byte[] bytes ) {
|
||||
sb.append( PrimitiveByteArrayJavaType.INSTANCE.toString( bytes ) );
|
||||
}
|
||||
else {
|
||||
sb.append( value );
|
||||
}
|
||||
sb.append( "</v>" );
|
||||
}
|
||||
sb.append( "</e>" );
|
||||
}
|
||||
sb.append( "</Map>" );
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String collectionToString(Collection<?> value, Type elementType, FormatMapper formatMapper) {
|
||||
final JavaType<Object> javaType = scope.getSessionFactory().getTypeConfiguration().getJavaTypeRegistry()
|
||||
.resolveDescriptor( new ParameterizedTypeImpl( Collection.class, new Type[] {elementType}, null ) );
|
||||
final WrapperOptions wrapperOptions = scope.getSessionFactory().getWrapperOptions();
|
||||
final String actualValue = formatMapper.toString(
|
||||
value,
|
||||
javaType,
|
||||
wrapperOptions
|
||||
);
|
||||
assertXmlEquals(
|
||||
actualValue,
|
||||
formatMapper.toString(
|
||||
formatMapper.fromString( actualValue, javaType, wrapperOptions ),
|
||||
javaType,
|
||||
wrapperOptions
|
||||
)
|
||||
);
|
||||
return actualValue;
|
||||
}
|
||||
|
||||
private String arrayToString(Object value, FormatMapper formatMapper) {
|
||||
final JavaType<Object> javaType = scope.getSessionFactory().getTypeConfiguration().getJavaTypeRegistry()
|
||||
.resolveDescriptor( value.getClass() );
|
||||
final WrapperOptions wrapperOptions = scope.getSessionFactory().getWrapperOptions();
|
||||
final String actualValue = formatMapper.toString(
|
||||
value,
|
||||
javaType,
|
||||
wrapperOptions
|
||||
);
|
||||
assertXmlEquals(
|
||||
actualValue,
|
||||
formatMapper.toString(
|
||||
formatMapper.fromString( actualValue, javaType, wrapperOptions ),
|
||||
javaType,
|
||||
wrapperOptions
|
||||
)
|
||||
);
|
||||
return actualValue;
|
||||
}
|
||||
|
||||
private String mapToString(Map<?, ?> value, Type keyType, Type elementType, FormatMapper formatMapper) {
|
||||
final JavaType<Object> javaType = scope.getSessionFactory().getTypeConfiguration().getJavaTypeRegistry()
|
||||
.resolveDescriptor( new ParameterizedTypeImpl( Map.class, new Type[] {keyType, elementType}, null ) );
|
||||
final WrapperOptions wrapperOptions = scope.getSessionFactory().getWrapperOptions();
|
||||
final String actualValue = formatMapper.toString(
|
||||
value,
|
||||
javaType,
|
||||
wrapperOptions
|
||||
);
|
||||
assertXmlEquals(
|
||||
actualValue,
|
||||
formatMapper.toString(
|
||||
formatMapper.fromString( actualValue, javaType, wrapperOptions ),
|
||||
javaType,
|
||||
wrapperOptions
|
||||
)
|
||||
);
|
||||
return actualValue;
|
||||
}
|
||||
|
||||
private void assertXmlEquals(String expected, String actual) {
|
||||
final Document expectedDoc = parseXml( xmlNormalize( expected ) );
|
||||
final Document actualDoc = parseXml( xmlNormalize( actual ) );
|
||||
normalize( expectedDoc );
|
||||
normalize( actualDoc );
|
||||
assertEquals( toXml( expectedDoc ).trim(), toXml( actualDoc ).trim() );
|
||||
}
|
||||
|
||||
private void normalize(Document document) {
|
||||
normalize( document.getChildNodes() );
|
||||
}
|
||||
|
||||
private void normalize(NodeList childNodes) {
|
||||
for ( int i = 0; i < childNodes.getLength(); i++ ) {
|
||||
final Node childNode = childNodes.item( i );
|
||||
if ( childNode.getNodeType() == Node.ELEMENT_NODE ) {
|
||||
normalize( childNode.getChildNodes() );
|
||||
}
|
||||
else if ( childNode.getNodeType() == Node.TEXT_NODE ) {
|
||||
if ( childNode.getNodeValue().isBlank() ) {
|
||||
childNode.getParentNode().removeChild( childNode );
|
||||
}
|
||||
else {
|
||||
childNode.setNodeValue( childNode.getNodeValue().trim() );
|
||||
}
|
||||
}
|
||||
else if ( childNode.getNodeType() == Node.COMMENT_NODE ) {
|
||||
childNode.setNodeValue( childNode.getNodeValue().trim() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String xmlNormalize(String doc) {
|
||||
final String prefix = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
|
||||
return doc.startsWith( "<?xml" ) ? doc : prefix + doc;
|
||||
}
|
||||
|
||||
private static Document parseXml(String document) {
|
||||
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
try {
|
||||
final DocumentBuilder db = dbf.newDocumentBuilder();
|
||||
return db.parse( new InputSource( new StringReader( document ) ) );
|
||||
}
|
||||
catch (ParserConfigurationException | IOException | SAXException e) {
|
||||
throw new RuntimeException( e );
|
||||
}
|
||||
}
|
||||
|
||||
private static String toXml(Document document) {
|
||||
final TransformerFactory tf = TransformerFactory.newInstance();
|
||||
try {
|
||||
final Transformer transformer = tf.newTransformer();
|
||||
transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "yes");
|
||||
transformer.setOutputProperty( OutputKeys.INDENT, "yes" );
|
||||
final StringWriter writer = new StringWriter();
|
||||
transformer.transform( new DOMSource( document ), new StreamResult( writer ) );
|
||||
return writer.toString();
|
||||
}
|
||||
catch (TransformerException e) {
|
||||
throw new RuntimeException( e );
|
||||
}
|
||||
}
|
||||
}
|
@ -436,6 +436,11 @@ public boolean isPreferNativeEnumTypesEnabled() {
|
||||
return MetadataBuildingContext.super.isPreferNativeEnumTypesEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isXmlFormatMapperLegacyFormatEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FastSessionServices getFastSessionServices() {
|
||||
throw new UnsupportedOperationException("operation not supported");
|
||||
|
Loading…
x
Reference in New Issue
Block a user