HHH-12675 - Respect inverse property for JoinedSubclassEntityPersister
This commit is contained in:
parent
4bafeeecae
commit
b25bfd79f4
|
@ -101,6 +101,8 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
|
||||||
|
|
||||||
private final boolean[] subclassTableSequentialSelect;
|
private final boolean[] subclassTableSequentialSelect;
|
||||||
private final boolean[] subclassTableIsLazyClosure;
|
private final boolean[] subclassTableIsLazyClosure;
|
||||||
|
private final boolean[] isInverseSubclassTable;
|
||||||
|
private final boolean[] isNullableSubclassTable;
|
||||||
|
|
||||||
// subclass discrimination works by assigning particular
|
// subclass discrimination works by assigning particular
|
||||||
// values to certain combinations of null primary key
|
// values to certain combinations of null primary key
|
||||||
|
@ -123,6 +125,7 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
|
||||||
private final int coreTableSpan;
|
private final int coreTableSpan;
|
||||||
// only contains values for SecondaryTables, ie. not tables part of the "coreTableSpan"
|
// only contains values for SecondaryTables, ie. not tables part of the "coreTableSpan"
|
||||||
private final boolean[] isNullableTable;
|
private final boolean[] isNullableTable;
|
||||||
|
private final boolean[] isInverseTable;
|
||||||
|
|
||||||
//INITIALIZATION:
|
//INITIALIZATION:
|
||||||
|
|
||||||
|
@ -235,20 +238,21 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
|
||||||
|
|
||||||
//Span of the tableNames directly mapped by this entity and super-classes, if any
|
//Span of the tableNames directly mapped by this entity and super-classes, if any
|
||||||
coreTableSpan = tableNames.size();
|
coreTableSpan = tableNames.size();
|
||||||
|
tableSpan = persistentClass.getJoinClosureSpan() + coreTableSpan;
|
||||||
|
|
||||||
isNullableTable = new boolean[persistentClass.getJoinClosureSpan()];
|
isNullableTable = new boolean[tableSpan];
|
||||||
|
isInverseTable = new boolean[tableSpan];
|
||||||
|
|
||||||
int tableIndex = 0;
|
|
||||||
Iterator joinItr = persistentClass.getJoinClosureIterator();
|
Iterator joinItr = persistentClass.getJoinClosureIterator();
|
||||||
while ( joinItr.hasNext() ) {
|
for ( int tableIndex = 0; joinItr.hasNext(); tableIndex++ ) {
|
||||||
Join join = (Join) joinItr.next();
|
Join join = (Join) joinItr.next();
|
||||||
|
|
||||||
isNullableTable[tableIndex++] = join.isOptional() ||
|
isNullableTable[tableIndex] = join.isOptional() ||
|
||||||
creationContext.getSessionFactory()
|
creationContext.getSessionFactory()
|
||||||
.getSessionFactoryOptions()
|
.getSessionFactoryOptions()
|
||||||
.getJpaCompliance()
|
.getJpaCompliance()
|
||||||
.isJpaCacheComplianceEnabled();
|
.isJpaCacheComplianceEnabled();
|
||||||
|
isInverseTable[tableIndex] = join.isInverse();
|
||||||
|
|
||||||
Table table = join.getTable();
|
Table table = join.getTable();
|
||||||
final String tableName = determineTableName( table, jdbcEnvironment );
|
final String tableName = determineTableName( table, jdbcEnvironment );
|
||||||
|
@ -285,6 +289,8 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
|
||||||
ArrayList<Boolean> isConcretes = new ArrayList<Boolean>();
|
ArrayList<Boolean> isConcretes = new ArrayList<Boolean>();
|
||||||
ArrayList<Boolean> isDeferreds = new ArrayList<Boolean>();
|
ArrayList<Boolean> isDeferreds = new ArrayList<Boolean>();
|
||||||
ArrayList<Boolean> isLazies = new ArrayList<Boolean>();
|
ArrayList<Boolean> isLazies = new ArrayList<Boolean>();
|
||||||
|
ArrayList<Boolean> isInverses = new ArrayList<Boolean>();
|
||||||
|
ArrayList<Boolean> isNullables = new ArrayList<Boolean>();
|
||||||
|
|
||||||
keyColumns = new ArrayList<String[]>();
|
keyColumns = new ArrayList<String[]>();
|
||||||
tItr = persistentClass.getSubclassTableClosureIterator();
|
tItr = persistentClass.getSubclassTableClosureIterator();
|
||||||
|
@ -293,6 +299,8 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
|
||||||
isConcretes.add( persistentClass.isClassOrSuperclassTable( tab ) );
|
isConcretes.add( persistentClass.isClassOrSuperclassTable( tab ) );
|
||||||
isDeferreds.add( Boolean.FALSE );
|
isDeferreds.add( Boolean.FALSE );
|
||||||
isLazies.add( Boolean.FALSE );
|
isLazies.add( Boolean.FALSE );
|
||||||
|
isInverses.add( Boolean.FALSE );
|
||||||
|
isNullables.add( Boolean.FALSE );
|
||||||
final String tableName = determineTableName( tab, jdbcEnvironment );
|
final String tableName = determineTableName( tab, jdbcEnvironment );
|
||||||
subclassTableNames.add( tableName );
|
subclassTableNames.add( tableName );
|
||||||
String[] key = new String[idColumnSpan];
|
String[] key = new String[idColumnSpan];
|
||||||
|
@ -311,6 +319,13 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
|
||||||
|
|
||||||
isConcretes.add( persistentClass.isClassOrSuperclassTable( joinTable ) );
|
isConcretes.add( persistentClass.isClassOrSuperclassTable( joinTable ) );
|
||||||
isDeferreds.add( join.isSequentialSelect() );
|
isDeferreds.add( join.isSequentialSelect() );
|
||||||
|
isInverses.add( join.isInverse() );
|
||||||
|
isNullables.add(
|
||||||
|
join.isOptional() || creationContext.getSessionFactory()
|
||||||
|
.getSessionFactoryOptions()
|
||||||
|
.getJpaCompliance()
|
||||||
|
.isJpaCacheComplianceEnabled()
|
||||||
|
);
|
||||||
isLazies.add( join.isLazy() );
|
isLazies.add( join.isLazy() );
|
||||||
|
|
||||||
String joinTableName = determineTableName( joinTable, jdbcEnvironment );
|
String joinTableName = determineTableName( joinTable, jdbcEnvironment );
|
||||||
|
@ -328,6 +343,8 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
|
||||||
isClassOrSuperclassTable = ArrayHelper.toBooleanArray( isConcretes );
|
isClassOrSuperclassTable = ArrayHelper.toBooleanArray( isConcretes );
|
||||||
subclassTableSequentialSelect = ArrayHelper.toBooleanArray( isDeferreds );
|
subclassTableSequentialSelect = ArrayHelper.toBooleanArray( isDeferreds );
|
||||||
subclassTableIsLazyClosure = ArrayHelper.toBooleanArray( isLazies );
|
subclassTableIsLazyClosure = ArrayHelper.toBooleanArray( isLazies );
|
||||||
|
isInverseSubclassTable = ArrayHelper.toBooleanArray( isInverses );
|
||||||
|
isNullableSubclassTable = ArrayHelper.toBooleanArray( isNullables );
|
||||||
|
|
||||||
constraintOrderedTableNames = new String[naturalOrderSubclassTableNameClosure.length];
|
constraintOrderedTableNames = new String[naturalOrderSubclassTableNameClosure.length];
|
||||||
constraintOrderedKeyColumnNames = new String[naturalOrderSubclassTableNameClosure.length][];
|
constraintOrderedKeyColumnNames = new String[naturalOrderSubclassTableNameClosure.length][];
|
||||||
|
@ -347,7 +364,6 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
|
||||||
* tableNames -> CLIENT, PERSON
|
* tableNames -> CLIENT, PERSON
|
||||||
*/
|
*/
|
||||||
|
|
||||||
tableSpan = naturalOrderTableNames.length;
|
|
||||||
this.tableNames = reverse( naturalOrderTableNames, coreTableSpan );
|
this.tableNames = reverse( naturalOrderTableNames, coreTableSpan );
|
||||||
tableKeyColumns = reverse( naturalOrderTableKeyColumns, coreTableSpan );
|
tableKeyColumns = reverse( naturalOrderTableKeyColumns, coreTableSpan );
|
||||||
tableKeyColumnReaders = reverse( naturalOrderTableKeyColumnReaders, coreTableSpan );
|
tableKeyColumnReaders = reverse( naturalOrderTableKeyColumnReaders, coreTableSpan );
|
||||||
|
@ -374,6 +390,8 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
|
||||||
PersistentClass pc = persistentClass;
|
PersistentClass pc = persistentClass;
|
||||||
int jk = coreTableSpan - 1;
|
int jk = coreTableSpan - 1;
|
||||||
while ( pc != null ) {
|
while ( pc != null ) {
|
||||||
|
isNullableTable[jk] = false;
|
||||||
|
isInverseTable[jk] = false;
|
||||||
customSQLInsert[jk] = pc.getCustomSQLInsert();
|
customSQLInsert[jk] = pc.getCustomSQLInsert();
|
||||||
insertCallable[jk] = customSQLInsert[jk] != null && pc.isCustomInsertCallable();
|
insertCallable[jk] = customSQLInsert[jk] != null && pc.isCustomInsertCallable();
|
||||||
insertResultCheckStyles[jk] = pc.getCustomSQLInsertCheckStyle() == null
|
insertResultCheckStyles[jk] = pc.getCustomSQLInsertCheckStyle() == null
|
||||||
|
@ -404,6 +422,13 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
|
||||||
while ( joinItr.hasNext() ) {
|
while ( joinItr.hasNext() ) {
|
||||||
Join join = (Join) joinItr.next();
|
Join join = (Join) joinItr.next();
|
||||||
|
|
||||||
|
isInverseTable[j] = join.isInverse();
|
||||||
|
isNullableTable[j] = join.isOptional()
|
||||||
|
|| creationContext.getSessionFactory()
|
||||||
|
.getSessionFactoryOptions()
|
||||||
|
.getJpaCompliance()
|
||||||
|
.isJpaCacheComplianceEnabled();
|
||||||
|
|
||||||
customSQLInsert[j] = join.getCustomSQLInsert();
|
customSQLInsert[j] = join.getCustomSQLInsert();
|
||||||
insertCallable[j] = customSQLInsert[j] != null && join.isCustomInsertCallable();
|
insertCallable[j] = customSQLInsert[j] != null && join.isCustomInsertCallable();
|
||||||
insertResultCheckStyles[j] = join.getCustomSQLInsertCheckStyle() == null
|
insertResultCheckStyles[j] = join.getCustomSQLInsertCheckStyle() == null
|
||||||
|
@ -723,11 +748,14 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected boolean isNullableTable(int j) {
|
protected boolean isNullableTable(int j) {
|
||||||
if ( j < coreTableSpan ) {
|
return isNullableTable[j];
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
return isNullableTable[j - coreTableSpan];
|
@Override
|
||||||
|
protected boolean isInverseTable(int j) {
|
||||||
|
return isInverseTable[j];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isSubclassTableSequentialSelect(int j) {
|
protected boolean isSubclassTableSequentialSelect(int j) {
|
||||||
|
@ -744,6 +772,16 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
|
||||||
return subclassTableNameClosure[subclassPropertyTableNumberClosure[i]];
|
return subclassTableNameClosure[subclassPropertyTableNumberClosure[i]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isInverseSubclassTable(int j) {
|
||||||
|
return isInverseSubclassTable[j];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isNullableSubclassTable(int j) {
|
||||||
|
return isNullableSubclassTable[j];
|
||||||
|
}
|
||||||
|
|
||||||
public Type getDiscriminatorType() {
|
public Type getDiscriminatorType() {
|
||||||
return discriminatorType;
|
return discriminatorType;
|
||||||
}
|
}
|
||||||
|
|
|
@ -258,13 +258,10 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
|
||||||
if ( join.isSequentialSelect() && !persistentClass.isClassOrSuperclassJoin( join ) ) {
|
if ( join.isSequentialSelect() && !persistentClass.isClassOrSuperclassJoin( join ) ) {
|
||||||
hasDeferred = true;
|
hasDeferred = true;
|
||||||
}
|
}
|
||||||
subclassTables.add(
|
|
||||||
join.getTable().getQualifiedName(
|
String joinTableName = determineTableName( join.getTable(), jdbcEnvironment );
|
||||||
factory.getDialect(),
|
subclassTables.add( joinTableName );
|
||||||
factory.getSettings().getDefaultCatalogName(),
|
|
||||||
factory.getSettings().getDefaultSchemaName()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
Iterator iter = join.getKey().getColumnIterator();
|
Iterator iter = join.getKey().getColumnIterator();
|
||||||
String[] keyCols = new String[join.getKey().getColumnSpan()];
|
String[] keyCols = new String[join.getKey().getColumnSpan()];
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
|
@ -14,14 +14,17 @@ import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Emmanuel Bernard
|
* @author Emmanuel Bernard
|
||||||
*/
|
*/
|
||||||
public class JoinedSubclassAndSecondaryTable extends BaseCoreFunctionalTestCase {
|
public class JoinedSubclassAndSecondaryTable extends BaseCoreFunctionalTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSecondaryTableAndJoined() throws Exception {
|
public void testSecondaryTableAndJoined() {
|
||||||
Session s = openSession();
|
Session s = openSession();
|
||||||
Transaction tx = s.beginTransaction();
|
Transaction tx = s.beginTransaction();
|
||||||
SwimmingPool sp = new SwimmingPool();
|
SwimmingPool sp = new SwimmingPool();
|
||||||
|
@ -29,7 +32,7 @@ public class JoinedSubclassAndSecondaryTable extends BaseCoreFunctionalTestCase
|
||||||
s.flush();
|
s.flush();
|
||||||
s.clear();
|
s.clear();
|
||||||
|
|
||||||
long rowCount = getTableRowCount( s );
|
long rowCount = getTableRowCount( s, "POOL_ADDRESS" );
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"The address table is marked as optional. For null values no database row should be created",
|
"The address table is marked as optional. For null values no database row should be created",
|
||||||
0,
|
0,
|
||||||
|
@ -37,7 +40,7 @@ public class JoinedSubclassAndSecondaryTable extends BaseCoreFunctionalTestCase
|
||||||
);
|
);
|
||||||
|
|
||||||
SwimmingPool sp2 = (SwimmingPool) s.get( SwimmingPool.class, sp.getId() );
|
SwimmingPool sp2 = (SwimmingPool) s.get( SwimmingPool.class, sp.getId() );
|
||||||
assertEquals( sp.getAddress(), null );
|
assertNull( sp.getAddress() );
|
||||||
|
|
||||||
PoolAddress address = new PoolAddress();
|
PoolAddress address = new PoolAddress();
|
||||||
address.setAddress( "Park Avenue" );
|
address.setAddress( "Park Avenue" );
|
||||||
|
@ -47,24 +50,63 @@ public class JoinedSubclassAndSecondaryTable extends BaseCoreFunctionalTestCase
|
||||||
s.clear();
|
s.clear();
|
||||||
|
|
||||||
sp2 = (SwimmingPool) s.get( SwimmingPool.class, sp.getId() );
|
sp2 = (SwimmingPool) s.get( SwimmingPool.class, sp.getId() );
|
||||||
rowCount = getTableRowCount( s );
|
rowCount = getTableRowCount( s, "POOL_ADDRESS" );
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"Now we should have a row in the pool address table ",
|
"Now we should have a row in the pool address table ",
|
||||||
1,
|
1,
|
||||||
rowCount
|
rowCount
|
||||||
);
|
);
|
||||||
assertFalse( sp2.getAddress() == null );
|
assertNotNull( sp2.getAddress() );
|
||||||
assertEquals( sp2.getAddress().getAddress(), "Park Avenue" );
|
assertEquals( sp2.getAddress().getAddress(), "Park Avenue" );
|
||||||
|
|
||||||
tx.rollback();
|
tx.rollback();
|
||||||
s.close();
|
s.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private long getTableRowCount(Session s) {
|
@Test
|
||||||
|
public void testSecondaryTableAndJoinedInverse() throws Exception {
|
||||||
|
Session s = openSession();
|
||||||
|
Transaction tx = s.beginTransaction();
|
||||||
|
SwimmingPool sp = new SwimmingPool();
|
||||||
|
s.persist( sp );
|
||||||
|
s.flush();
|
||||||
|
s.clear();
|
||||||
|
|
||||||
|
long rowCount = getTableRowCount( s, "POOL_ADDRESS_2" );
|
||||||
|
assertEquals(
|
||||||
|
"The address table is marked as optional. For null values no database row should be created",
|
||||||
|
0,
|
||||||
|
rowCount
|
||||||
|
);
|
||||||
|
|
||||||
|
SwimmingPool sp2 = (SwimmingPool) s.get( SwimmingPool.class, sp.getId() );
|
||||||
|
assertNull( sp.getSecondaryAddress() );
|
||||||
|
|
||||||
|
PoolAddress address = new PoolAddress();
|
||||||
|
address.setAddress( "Park Avenue" );
|
||||||
|
sp2.setSecondaryAddress( address );
|
||||||
|
|
||||||
|
s.flush();
|
||||||
|
s.clear();
|
||||||
|
|
||||||
|
sp2 = (SwimmingPool) s.get( SwimmingPool.class, sp.getId() );
|
||||||
|
rowCount = getTableRowCount( s, "POOL_ADDRESS_2" );
|
||||||
|
assertEquals(
|
||||||
|
"Now we should have a row in the pool address table ",
|
||||||
|
0,
|
||||||
|
rowCount
|
||||||
|
);
|
||||||
|
assertNull( sp2.getSecondaryAddress() );
|
||||||
|
|
||||||
|
tx.rollback();
|
||||||
|
s.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getTableRowCount(Session s, String tableName) {
|
||||||
// the type returned for count(*) in a native query depends on the dialect
|
// the type returned for count(*) in a native query depends on the dialect
|
||||||
// Oracle returns Types.NUMERIC, which is mapped to BigDecimal;
|
// Oracle returns Types.NUMERIC, which is mapped to BigDecimal;
|
||||||
// H2 returns Types.BIGINT, which is mapped to BigInteger;
|
// H2 returns Types.BIGINT, which is mapped to BigInteger;
|
||||||
Object retVal = s.createSQLQuery( "select count(*) from POOL_ADDRESS" ).uniqueResult();
|
Object retVal = s.createSQLQuery( "select count(*) from " + tableName ).uniqueResult();
|
||||||
assertTrue( Number.class.isInstance( retVal ) );
|
assertTrue( Number.class.isInstance( retVal ) );
|
||||||
return ( ( Number ) retVal ).longValue();
|
return ( ( Number ) retVal ).longValue();
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,10 @@
|
||||||
|
|
||||||
//$Id$
|
//$Id$
|
||||||
package org.hibernate.test.annotations.inheritance.joined;
|
package org.hibernate.test.annotations.inheritance.joined;
|
||||||
|
import org.hibernate.annotations.Tables;
|
||||||
|
|
||||||
|
import javax.persistence.AttributeOverride;
|
||||||
|
import javax.persistence.Column;
|
||||||
import javax.persistence.Embedded;
|
import javax.persistence.Embedded;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.GeneratedValue;
|
import javax.persistence.GeneratedValue;
|
||||||
|
@ -14,14 +18,21 @@ import javax.persistence.Id;
|
||||||
import javax.persistence.Inheritance;
|
import javax.persistence.Inheritance;
|
||||||
import javax.persistence.InheritanceType;
|
import javax.persistence.InheritanceType;
|
||||||
import javax.persistence.SecondaryTable;
|
import javax.persistence.SecondaryTable;
|
||||||
|
import javax.persistence.SecondaryTables;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Emmanuel Bernard
|
* @author Emmanuel Bernard
|
||||||
*/
|
*/
|
||||||
@Entity
|
@Entity
|
||||||
@Inheritance(strategy = InheritanceType.JOINED)
|
@Inheritance(strategy = InheritanceType.JOINED)
|
||||||
@SecondaryTable(name="POOL_ADDRESS")
|
@SecondaryTables({
|
||||||
@org.hibernate.annotations.Table(appliesTo="POOL_ADDRESS", optional=true)
|
@SecondaryTable(name="POOL_ADDRESS"),
|
||||||
|
@SecondaryTable(name="POOL_ADDRESS_2")
|
||||||
|
})
|
||||||
|
@Tables({
|
||||||
|
@org.hibernate.annotations.Table(appliesTo="POOL_ADDRESS", optional=true),
|
||||||
|
@org.hibernate.annotations.Table(appliesTo="POOL_ADDRESS_2", optional=true, inverse = true)
|
||||||
|
})
|
||||||
public class Pool {
|
public class Pool {
|
||||||
@Id @GeneratedValue
|
@Id @GeneratedValue
|
||||||
private Integer id;
|
private Integer id;
|
||||||
|
@ -29,6 +40,10 @@ public class Pool {
|
||||||
@Embedded
|
@Embedded
|
||||||
private PoolAddress address;
|
private PoolAddress address;
|
||||||
|
|
||||||
|
@Embedded
|
||||||
|
@AttributeOverride(name = "address", column = @Column(table = "POOL_ADDRESS_2"))
|
||||||
|
private PoolAddress secondaryAddress;
|
||||||
|
|
||||||
public PoolAddress getAddress() {
|
public PoolAddress getAddress() {
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
@ -37,6 +52,14 @@ public class Pool {
|
||||||
this.address = address;
|
this.address = address;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PoolAddress getSecondaryAddress() {
|
||||||
|
return secondaryAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSecondaryAddress(PoolAddress secondaryAddress) {
|
||||||
|
this.secondaryAddress = secondaryAddress;
|
||||||
|
}
|
||||||
|
|
||||||
public Integer getId() {
|
public Integer getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue