HHH-9042 - Envers fails with @Converter and AttributeConverter
This commit is contained in:
parent
180e714b7c
commit
22730624fc
|
@ -1695,4 +1695,9 @@ public interface CoreMessageLogger extends BasicLogger {
|
||||||
@LogMessage(level = DEBUG)
|
@LogMessage(level = DEBUG)
|
||||||
@Message(value = "Creating pooled optimizer (lo) with [incrementSize=%s; returnClass=%s]", id = 467)
|
@Message(value = "Creating pooled optimizer (lo) with [incrementSize=%s; returnClass=%s]", id = 467)
|
||||||
void creatingPooledLoOptimizer(int incrementSize, String name);
|
void creatingPooledLoOptimizer(int incrementSize, String name);
|
||||||
|
|
||||||
|
@LogMessage(level = WARN)
|
||||||
|
@Message(value = "Unable to interpret type [%s] as an AttributeConverter due to an exception : %s", id = 468)
|
||||||
|
void logBadHbmAttributeConverterType(String type, String exceptionMessage);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,8 +31,10 @@ import java.util.Properties;
|
||||||
import javax.persistence.AttributeConverter;
|
import javax.persistence.AttributeConverter;
|
||||||
|
|
||||||
import org.hibernate.FetchMode;
|
import org.hibernate.FetchMode;
|
||||||
|
import org.hibernate.HibernateException;
|
||||||
import org.hibernate.MappingException;
|
import org.hibernate.MappingException;
|
||||||
import org.hibernate.annotations.common.reflection.XProperty;
|
import org.hibernate.annotations.common.reflection.XProperty;
|
||||||
|
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
|
||||||
import org.hibernate.boot.spi.MetadataImplementor;
|
import org.hibernate.boot.spi.MetadataImplementor;
|
||||||
import org.hibernate.cfg.AttributeConverterDefinition;
|
import org.hibernate.cfg.AttributeConverterDefinition;
|
||||||
import org.hibernate.cfg.AvailableSettings;
|
import org.hibernate.cfg.AvailableSettings;
|
||||||
|
@ -44,6 +46,8 @@ import org.hibernate.id.IdentifierGenerator;
|
||||||
import org.hibernate.id.IdentityGenerator;
|
import org.hibernate.id.IdentityGenerator;
|
||||||
import org.hibernate.id.PersistentIdentifierGenerator;
|
import org.hibernate.id.PersistentIdentifierGenerator;
|
||||||
import org.hibernate.id.factory.IdentifierGeneratorFactory;
|
import org.hibernate.id.factory.IdentifierGeneratorFactory;
|
||||||
|
import org.hibernate.internal.CoreLogging;
|
||||||
|
import org.hibernate.internal.CoreMessageLogger;
|
||||||
import org.hibernate.internal.util.ReflectHelper;
|
import org.hibernate.internal.util.ReflectHelper;
|
||||||
import org.hibernate.type.Type;
|
import org.hibernate.type.Type;
|
||||||
import org.hibernate.type.descriptor.converter.AttributeConverterSqlTypeDescriptorAdapter;
|
import org.hibernate.type.descriptor.converter.AttributeConverterSqlTypeDescriptorAdapter;
|
||||||
|
@ -55,14 +59,12 @@ import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
|
||||||
import org.hibernate.type.descriptor.sql.SqlTypeDescriptorRegistry;
|
import org.hibernate.type.descriptor.sql.SqlTypeDescriptorRegistry;
|
||||||
import org.hibernate.usertype.DynamicParameterizedType;
|
import org.hibernate.usertype.DynamicParameterizedType;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Any value that maps to columns.
|
* Any value that maps to columns.
|
||||||
* @author Gavin King
|
* @author Gavin King
|
||||||
*/
|
*/
|
||||||
public class SimpleValue implements KeyValue {
|
public class SimpleValue implements KeyValue {
|
||||||
private static final Logger log = Logger.getLogger( SimpleValue.class );
|
private static final CoreMessageLogger log = CoreLogging.messageLogger( SimpleValue.class );
|
||||||
|
|
||||||
public static final String DEFAULT_ID_GEN_STRATEGY = "assigned";
|
public static final String DEFAULT_ID_GEN_STRATEGY = "assigned";
|
||||||
|
|
||||||
|
@ -96,6 +98,7 @@ public class SimpleValue implements KeyValue {
|
||||||
return metadata;
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean isCascadeDeleteEnabled() {
|
public boolean isCascadeDeleteEnabled() {
|
||||||
return cascadeDeleteEnabled;
|
return cascadeDeleteEnabled;
|
||||||
}
|
}
|
||||||
|
@ -113,7 +116,8 @@ public class SimpleValue implements KeyValue {
|
||||||
public void addFormula(Formula formula) {
|
public void addFormula(Formula formula) {
|
||||||
columns.add(formula);
|
columns.add(formula);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean hasFormula() {
|
public boolean hasFormula() {
|
||||||
Iterator iter = getColumnIterator();
|
Iterator iter = getColumnIterator();
|
||||||
while ( iter.hasNext() ) {
|
while ( iter.hasNext() ) {
|
||||||
|
@ -123,27 +127,51 @@ public class SimpleValue implements KeyValue {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int getColumnSpan() {
|
public int getColumnSpan() {
|
||||||
return columns.size();
|
return columns.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Iterator<Selectable> getColumnIterator() {
|
public Iterator<Selectable> getColumnIterator() {
|
||||||
return columns.iterator();
|
return columns.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List getConstraintColumns() {
|
public List getConstraintColumns() {
|
||||||
return columns;
|
return columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTypeName() {
|
public String getTypeName() {
|
||||||
return typeName;
|
return typeName;
|
||||||
}
|
}
|
||||||
public void setTypeName(String type) {
|
|
||||||
this.typeName = type;
|
public void setTypeName(String typeName) {
|
||||||
|
if ( typeName != null && typeName.startsWith( AttributeConverterTypeAdapter.NAME_PREFIX ) ) {
|
||||||
|
final String converterClassName = typeName.substring( AttributeConverterTypeAdapter.NAME_PREFIX.length() );
|
||||||
|
final ClassLoaderService cls = getMetadata().getMetadataBuildingOptions()
|
||||||
|
.getServiceRegistry()
|
||||||
|
.getService( ClassLoaderService.class );
|
||||||
|
try {
|
||||||
|
final Class<AttributeConverter> converterClass = cls.classForName( converterClassName );
|
||||||
|
attributeConverterDefinition = new AttributeConverterDefinition( converterClass.newInstance(), false );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.logBadHbmAttributeConverterType( typeName, e.getMessage() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.typeName = typeName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTable(Table table) {
|
public void setTable(Table table) {
|
||||||
this.table = table;
|
this.table = table;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void createForeignKey() throws MappingException {}
|
public void createForeignKey() throws MappingException {}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void createForeignKeyOfEntity(String entityName) {
|
public void createForeignKeyOfEntity(String entityName) {
|
||||||
if ( !hasFormula() && !"none".equals(getForeignKeyName())) {
|
if ( !hasFormula() && !"none".equals(getForeignKeyName())) {
|
||||||
ForeignKey fk = table.createForeignKey( getForeignKeyName(), getConstraintColumns(), entityName );
|
ForeignKey fk = table.createForeignKey( getForeignKeyName(), getConstraintColumns(), entityName );
|
||||||
|
@ -151,6 +179,7 @@ public class SimpleValue implements KeyValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public IdentifierGenerator createIdentifierGenerator(
|
public IdentifierGenerator createIdentifierGenerator(
|
||||||
IdentifierGeneratorFactory identifierGeneratorFactory,
|
IdentifierGeneratorFactory identifierGeneratorFactory,
|
||||||
Dialect dialect,
|
Dialect dialect,
|
||||||
|
@ -445,13 +474,15 @@ public class SimpleValue implements KeyValue {
|
||||||
|
|
||||||
// todo : cache the AttributeConverterTypeAdapter in case that AttributeConverter is applied multiple times.
|
// todo : cache the AttributeConverterTypeAdapter in case that AttributeConverter is applied multiple times.
|
||||||
|
|
||||||
final String name = String.format(
|
final String name = AttributeConverterTypeAdapter.NAME_PREFIX + attributeConverterDefinition.getAttributeConverter().getClass().getName();
|
||||||
|
final String description = String.format(
|
||||||
"BasicType adapter for AttributeConverter<%s,%s>",
|
"BasicType adapter for AttributeConverter<%s,%s>",
|
||||||
entityAttributeJavaType.getSimpleName(),
|
entityAttributeJavaType.getSimpleName(),
|
||||||
databaseColumnJavaType.getSimpleName()
|
databaseColumnJavaType.getSimpleName()
|
||||||
);
|
);
|
||||||
return new AttributeConverterTypeAdapter(
|
return new AttributeConverterTypeAdapter(
|
||||||
name,
|
name,
|
||||||
|
description,
|
||||||
attributeConverterDefinition.getAttributeConverter(),
|
attributeConverterDefinition.getAttributeConverter(),
|
||||||
sqlTypeDescriptorAdapter,
|
sqlTypeDescriptorAdapter,
|
||||||
entityAttributeJavaType,
|
entityAttributeJavaType,
|
||||||
|
|
|
@ -39,7 +39,10 @@ import org.jboss.logging.Logger;
|
||||||
public class AttributeConverterTypeAdapter<T> extends AbstractSingleColumnStandardBasicType<T> {
|
public class AttributeConverterTypeAdapter<T> extends AbstractSingleColumnStandardBasicType<T> {
|
||||||
private static final Logger log = Logger.getLogger( AttributeConverterTypeAdapter.class );
|
private static final Logger log = Logger.getLogger( AttributeConverterTypeAdapter.class );
|
||||||
|
|
||||||
|
public static final String NAME_PREFIX = "converted::";
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
private final Class modelType;
|
private final Class modelType;
|
||||||
private final Class jdbcType;
|
private final Class jdbcType;
|
||||||
|
@ -47,6 +50,7 @@ public class AttributeConverterTypeAdapter<T> extends AbstractSingleColumnStanda
|
||||||
|
|
||||||
public AttributeConverterTypeAdapter(
|
public AttributeConverterTypeAdapter(
|
||||||
String name,
|
String name,
|
||||||
|
String description,
|
||||||
AttributeConverter<? extends T,?> attributeConverter,
|
AttributeConverter<? extends T,?> attributeConverter,
|
||||||
SqlTypeDescriptor sqlTypeDescriptorAdapter,
|
SqlTypeDescriptor sqlTypeDescriptorAdapter,
|
||||||
Class modelType,
|
Class modelType,
|
||||||
|
@ -54,6 +58,7 @@ public class AttributeConverterTypeAdapter<T> extends AbstractSingleColumnStanda
|
||||||
JavaTypeDescriptor<T> entityAttributeJavaTypeDescriptor) {
|
JavaTypeDescriptor<T> entityAttributeJavaTypeDescriptor) {
|
||||||
super( sqlTypeDescriptorAdapter, entityAttributeJavaTypeDescriptor );
|
super( sqlTypeDescriptorAdapter, entityAttributeJavaTypeDescriptor );
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
this.description = description;
|
||||||
this.modelType = modelType;
|
this.modelType = modelType;
|
||||||
this.jdbcType = jdbcType;
|
this.jdbcType = jdbcType;
|
||||||
this.attributeConverter = attributeConverter;
|
this.attributeConverter = attributeConverter;
|
||||||
|
@ -77,4 +82,9 @@ public class AttributeConverterTypeAdapter<T> extends AbstractSingleColumnStanda
|
||||||
public AttributeConverter<? extends T,?> getAttributeConverter() {
|
public AttributeConverter<? extends T,?> getAttributeConverter() {
|
||||||
return attributeConverter;
|
return attributeConverter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* Copyright (c) 2015, Red Hat Inc. or third-party contributors as
|
||||||
|
* indicated by the @author tags or express copyright attribution
|
||||||
|
* statements applied by the authors. All third-party contributions are
|
||||||
|
* distributed under license by Red Hat Inc.
|
||||||
|
*
|
||||||
|
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||||
|
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||||
|
* Lesser General Public License, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||||
|
* for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this distribution; if not, write to:
|
||||||
|
* Free Software Foundation, Inc.
|
||||||
|
* 51 Franklin Street, Fifth Floor
|
||||||
|
* Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.hibernate.envers.test.entities.converter;
|
||||||
|
|
||||||
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.SessionFactory;
|
||||||
|
import org.hibernate.boot.Metadata;
|
||||||
|
import org.hibernate.boot.MetadataSources;
|
||||||
|
import org.hibernate.boot.internal.MetadataImpl;
|
||||||
|
import org.hibernate.boot.registry.StandardServiceRegistry;
|
||||||
|
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
|
||||||
|
import org.hibernate.cfg.AvailableSettings;
|
||||||
|
import org.hibernate.envers.test.AbstractEnversTest;
|
||||||
|
import org.hibernate.mapping.PersistentClass;
|
||||||
|
|
||||||
|
import org.hibernate.testing.TestForIssue;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class BasicModelingTest extends AbstractEnversTest {
|
||||||
|
@Test
|
||||||
|
@TestForIssue( jiraKey = "HHH-9042" )
|
||||||
|
public void testMetamodelBuilding() {
|
||||||
|
StandardServiceRegistry ssr = new StandardServiceRegistryBuilder()
|
||||||
|
.applySetting( AvailableSettings.HBM2DDL_AUTO, "create-drop" )
|
||||||
|
.build();
|
||||||
|
try {
|
||||||
|
Metadata metadata = new MetadataSources( ssr )
|
||||||
|
.addAttributeConverter( SexConverter.class )
|
||||||
|
.addAnnotatedClass( Person.class )
|
||||||
|
.buildMetadata();
|
||||||
|
|
||||||
|
( (MetadataImpl) metadata ).validate();
|
||||||
|
|
||||||
|
PersistentClass personBinding = metadata.getEntityBinding( Person.class.getName() );
|
||||||
|
assertNotNull( personBinding );
|
||||||
|
|
||||||
|
PersistentClass personAuditBinding = metadata.getEntityBinding( Person.class.getName() + "_AUD" );
|
||||||
|
assertNotNull( personAuditBinding );
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
StandardServiceRegistryBuilder.destroy( ssr );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* Copyright (c) 2015, Red Hat Inc. or third-party contributors as
|
||||||
|
* indicated by the @author tags or express copyright attribution
|
||||||
|
* statements applied by the authors. All third-party contributions are
|
||||||
|
* distributed under license by Red Hat Inc.
|
||||||
|
*
|
||||||
|
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||||
|
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||||
|
* Lesser General Public License, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||||
|
* for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this distribution; if not, write to:
|
||||||
|
* Free Software Foundation, Inc.
|
||||||
|
* 51 Franklin Street, Fifth Floor
|
||||||
|
* Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.hibernate.envers.test.entities.converter;
|
||||||
|
|
||||||
|
import javax.persistence.Convert;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.GenericGenerator;
|
||||||
|
import org.hibernate.envers.Audited;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Audited
|
||||||
|
public class Person {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue( generator = "increment" )
|
||||||
|
@GenericGenerator( name = "increment", strategy="increment" )
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Convert(converter = SexConverter.class)
|
||||||
|
private Sex sex;
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* Copyright (c) 2015, Red Hat Inc. or third-party contributors as
|
||||||
|
* indicated by the @author tags or express copyright attribution
|
||||||
|
* statements applied by the authors. All third-party contributions are
|
||||||
|
* distributed under license by Red Hat Inc.
|
||||||
|
*
|
||||||
|
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||||
|
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||||
|
* Lesser General Public License, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||||
|
* for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this distribution; if not, write to:
|
||||||
|
* Free Software Foundation, Inc.
|
||||||
|
* 51 Franklin Street, Fifth Floor
|
||||||
|
* Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.hibernate.envers.test.entities.converter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public enum Sex {
|
||||||
|
MALE,
|
||||||
|
FEMALE;
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* Copyright (c) 2015, Red Hat Inc. or third-party contributors as
|
||||||
|
* indicated by the @author tags or express copyright attribution
|
||||||
|
* statements applied by the authors. All third-party contributions are
|
||||||
|
* distributed under license by Red Hat Inc.
|
||||||
|
*
|
||||||
|
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||||
|
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||||
|
* Lesser General Public License, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||||
|
* for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this distribution; if not, write to:
|
||||||
|
* Free Software Foundation, Inc.
|
||||||
|
* 51 Franklin Street, Fifth Floor
|
||||||
|
* Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.hibernate.envers.test.entities.converter;
|
||||||
|
|
||||||
|
import javax.persistence.AttributeConverter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class SexConverter implements AttributeConverter<Sex, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String convertToDatabaseColumn(Sex attribute) {
|
||||||
|
if (attribute == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (attribute) {
|
||||||
|
case MALE: {
|
||||||
|
return "M";
|
||||||
|
}
|
||||||
|
case FEMALE: {
|
||||||
|
return "F";
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new IllegalArgumentException( "Unexpected Sex model value [" + attribute + "]" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Sex convertToEntityAttribute(String dbData) {
|
||||||
|
if (dbData == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( "M".equals( dbData ) ) {
|
||||||
|
return Sex.MALE;
|
||||||
|
}
|
||||||
|
else if ( "F".equals( dbData ) ) {
|
||||||
|
return Sex.FEMALE;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException( "Unexpected Sex db value [" + dbData + "]" );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue