mirror of
https://github.com/hibernate/hibernate-orm
synced 2025-03-03 16:29:13 +00:00
HHH-15473 Don't confuse property initialization for collection initialization in tests
Checking for property initialization was acceptable and pretty much the only way to perform assertions before we fixed HHH-14620, but now that initializing a property doesn't imply initializing the collection, it's just plain wrong: * If you expect the collection *not* to be initialized, then checking that that the property is not initialized is too strict: the assertion could fail because the property is initialized while the collection is not initialized. * If you expect the collection to be initialized, then checking that that the property is initialized is not enough: the assertion could pass because the property is initialized while the collection is not initialized. Besides, we can safely call the getter to test the collection directly with Hibernate.isInitialized(entity.getCollection()) since a call to the getter is not supposed to trigger collection initialization.
This commit is contained in:
parent
560722dfaa
commit
febfd9d4b8
@ -72,7 +72,7 @@ public void testManagedWithUninitializedAssociation() {
|
|||||||
.setParameter( "name", "PARENT" )
|
.setParameter( "name", "PARENT" )
|
||||||
.uniqueResult();
|
.uniqueResult();
|
||||||
checkInterceptor( loadedParent, false );
|
checkInterceptor( loadedParent, false );
|
||||||
assertFalse( Hibernate.isPropertyInitialized( loadedParent, "children" ) );
|
assertFalse( Hibernate.isInitialized( loadedParent.getChildren() ) );
|
||||||
s.delete( loadedParent );
|
s.delete( loadedParent );
|
||||||
} );
|
} );
|
||||||
// If the lazy relation is not fetch on cascade there is a constraint violation on commit
|
// If the lazy relation is not fetch on cascade there is a constraint violation on commit
|
||||||
@ -87,8 +87,8 @@ public void testManagedWithInitializedAssociation() {
|
|||||||
.setParameter( "name", "PARENT" )
|
.setParameter( "name", "PARENT" )
|
||||||
.uniqueResult();
|
.uniqueResult();
|
||||||
checkInterceptor( loadedParent, false );
|
checkInterceptor( loadedParent, false );
|
||||||
loadedParent.getChildren();
|
loadedParent.getChildren().size();
|
||||||
assertTrue( Hibernate.isPropertyInitialized( loadedParent, "children" ) );
|
assertTrue( Hibernate.isInitialized( loadedParent.getChildren() ) );
|
||||||
s.delete( loadedParent );
|
s.delete( loadedParent );
|
||||||
} );
|
} );
|
||||||
// If the lazy relation is not fetch on cascade there is a constraint violation on commit
|
// If the lazy relation is not fetch on cascade there is a constraint violation on commit
|
||||||
@ -101,7 +101,7 @@ public void testDetachedWithUninitializedAssociation() {
|
|||||||
return s.get( Parent.class, originalParent.getId() );
|
return s.get( Parent.class, originalParent.getId() );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
assertFalse( Hibernate.isPropertyInitialized( detachedParent, "children" ) );
|
assertFalse( Hibernate.isInitialized( detachedParent.getChildren() ) );
|
||||||
|
|
||||||
checkInterceptor( detachedParent, false );
|
checkInterceptor( detachedParent, false );
|
||||||
|
|
||||||
@ -117,14 +117,12 @@ public void testDetachedWithUninitializedAssociation() {
|
|||||||
public void testDetachedWithInitializedAssociation() {
|
public void testDetachedWithInitializedAssociation() {
|
||||||
final Parent detachedParent = doInHibernate( this::sessionFactory, s -> {
|
final Parent detachedParent = doInHibernate( this::sessionFactory, s -> {
|
||||||
Parent parent = s.get( Parent.class, originalParent.getId() );
|
Parent parent = s.get( Parent.class, originalParent.getId() );
|
||||||
assertFalse( Hibernate.isPropertyInitialized( parent, "children" ) );
|
|
||||||
|
|
||||||
// initialize collection before detaching
|
// initialize collection before detaching
|
||||||
parent.getChildren();
|
parent.getChildren().size();
|
||||||
return parent;
|
return parent;
|
||||||
} );
|
} );
|
||||||
|
|
||||||
assertTrue( Hibernate.isPropertyInitialized( detachedParent, "children" ) );
|
assertTrue( Hibernate.isInitialized( detachedParent.getChildren() ) );
|
||||||
|
|
||||||
checkInterceptor( detachedParent, false );
|
checkInterceptor( detachedParent, false );
|
||||||
|
|
||||||
@ -192,7 +190,7 @@ void setId(Long id) {
|
|||||||
|
|
||||||
@OneToMany( mappedBy = "parent", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}, fetch = FetchType.LAZY )
|
@OneToMany( mappedBy = "parent", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}, fetch = FetchType.LAZY )
|
||||||
List<Child> getChildren() {
|
List<Child> getChildren() {
|
||||||
return Collections.unmodifiableList( children );
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setChildren(List<Child> children) {
|
void setChildren(List<Child> children) {
|
||||||
|
@ -153,8 +153,8 @@ public void testMergeDetachedEnhancedEntityWithUninitializedOneToMany() {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// address should not be initialized
|
// address should not be initialized in order to reproduce the problem
|
||||||
assertFalse( Hibernate.isPropertyInitialized( detachedPerson, "addresses" ) );
|
assertFalse( Hibernate.isInitialized( detachedPerson.getAddresses() ) );
|
||||||
detachedPerson.setName( "newName" );
|
detachedPerson.setName( "newName" );
|
||||||
|
|
||||||
Person mergedPerson = TransactionUtil.doInHibernate(
|
Person mergedPerson = TransactionUtil.doInHibernate(
|
||||||
@ -163,8 +163,8 @@ public void testMergeDetachedEnhancedEntityWithUninitializedOneToMany() {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// address should be initialized
|
// address still shouldn't be initialized: there's no reason for it to be initialized by a merge.
|
||||||
assertTrue( Hibernate.isPropertyInitialized( mergedPerson, "addresses" ) );
|
assertFalse( Hibernate.isInitialized( detachedPerson.getAddresses() ) );
|
||||||
assertEquals( "newName", mergedPerson.getName() );
|
assertEquals( "newName", mergedPerson.getName() );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,8 +179,8 @@ public void testDeleteEnhancedEntityWithUninitializedOneToMany() {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// address should not be initialized
|
// address should not be initialized in order to reproduce the problem
|
||||||
assertFalse( Hibernate.isPropertyInitialized( detachedPerson, "addresses" ) );
|
assertFalse( Hibernate.isInitialized( detachedPerson.getAddresses() ) );
|
||||||
|
|
||||||
// deleting detachedPerson should result in detachedPerson.address being initialized,
|
// deleting detachedPerson should result in detachedPerson.address being initialized,
|
||||||
// so that the DELETE operation can be cascaded to it.
|
// so that the DELETE operation can be cascaded to it.
|
||||||
|
@ -19,15 +19,11 @@
|
|||||||
import jakarta.persistence.OneToMany;
|
import jakarta.persistence.OneToMany;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
import org.hibernate.Hibernate;
|
||||||
import org.hibernate.boot.internal.SessionFactoryBuilderImpl;
|
import org.hibernate.boot.internal.SessionFactoryBuilderImpl;
|
||||||
import org.hibernate.boot.internal.SessionFactoryOptionsBuilder;
|
import org.hibernate.boot.internal.SessionFactoryOptionsBuilder;
|
||||||
import org.hibernate.boot.spi.SessionFactoryBuilderService;
|
import org.hibernate.boot.spi.SessionFactoryBuilderService;
|
||||||
import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor;
|
|
||||||
import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor;
|
|
||||||
import org.hibernate.cfg.Configuration;
|
import org.hibernate.cfg.Configuration;
|
||||||
import org.hibernate.collection.spi.PersistentCollection;
|
|
||||||
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
|
|
||||||
import org.hibernate.engine.spi.SelfDirtinessTracker;
|
|
||||||
|
|
||||||
import org.hibernate.testing.TestForIssue;
|
import org.hibernate.testing.TestForIssue;
|
||||||
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
|
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
|
||||||
@ -38,7 +34,6 @@
|
|||||||
|
|
||||||
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Christian Beikov
|
* @author Christian Beikov
|
||||||
@ -83,11 +78,8 @@ public void test() {
|
|||||||
doInJPA( this::sessionFactory, entityManager -> {
|
doInJPA( this::sessionFactory, entityManager -> {
|
||||||
StringsEntity entity = entityManager.find( StringsEntity.class, 1L );
|
StringsEntity entity = entityManager.find( StringsEntity.class, 1L );
|
||||||
entityManager.flush();
|
entityManager.flush();
|
||||||
BytecodeLazyAttributeInterceptor interceptor = (BytecodeLazyAttributeInterceptor) ( (PersistentAttributeInterceptable) entity )
|
assertFalse( Hibernate.isInitialized( entity.someStrings ) );
|
||||||
.$$_hibernate_getInterceptor();
|
assertFalse( Hibernate.isInitialized( entity.someStringEntities ) );
|
||||||
assertTrue( interceptor.hasAnyUninitializedAttributes() );
|
|
||||||
assertFalse( interceptor.isAttributeLoaded( "someStrings" ) );
|
|
||||||
assertFalse( interceptor.isAttributeLoaded( "someStringEntities" ) );
|
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
import static org.hamcrest.CoreMatchers.not;
|
import static org.hamcrest.CoreMatchers.not;
|
||||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hibernate.Hibernate.isPropertyInitialized;
|
import static org.hibernate.Hibernate.isInitialized;
|
||||||
import static org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils.checkDirtyTracking;
|
import static org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils.checkDirtyTracking;
|
||||||
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
|
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
@ -70,7 +70,7 @@ public void testDetach() {
|
|||||||
|
|
||||||
assertThat( parent, notNullValue() );
|
assertThat( parent, notNullValue() );
|
||||||
assertThat( parent, not( instanceOf( HibernateProxy.class ) ) );
|
assertThat( parent, not( instanceOf( HibernateProxy.class ) ) );
|
||||||
assertFalse( isPropertyInitialized( parent, "children" ) );
|
assertFalse( isInitialized( parent.children ) );
|
||||||
checkDirtyTracking( parent );
|
checkDirtyTracking( parent );
|
||||||
|
|
||||||
s.detach( parent );
|
s.detach( parent );
|
||||||
@ -99,7 +99,7 @@ public void testRefresh() {
|
|||||||
|
|
||||||
assertThat( parent, notNullValue() );
|
assertThat( parent, notNullValue() );
|
||||||
assertThat( parent, not( instanceOf( HibernateProxy.class ) ) );
|
assertThat( parent, not( instanceOf( HibernateProxy.class ) ) );
|
||||||
assertFalse( isPropertyInitialized( parent, "children" ) );
|
assertFalse( isInitialized( parent.children ) );
|
||||||
checkDirtyTracking( parent );
|
checkDirtyTracking( parent );
|
||||||
|
|
||||||
s.refresh( parent );
|
s.refresh( parent );
|
||||||
|
@ -49,6 +49,7 @@
|
|||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Andrea Boriero
|
* @author Andrea Boriero
|
||||||
@ -117,8 +118,6 @@ public void updateSimpleField() {
|
|||||||
.getMappingMetamodel()
|
.getMappingMetamodel()
|
||||||
.getEntityDescriptor( Child.class.getName() );
|
.getEntityDescriptor( Child.class.getName() );
|
||||||
|
|
||||||
final int relativesAttributeIndex = childPersister.getEntityMetamodel().getPropertyIndex( "relatives" );
|
|
||||||
|
|
||||||
inTransaction(
|
inTransaction(
|
||||||
session -> {
|
session -> {
|
||||||
stats.clear();
|
stats.clear();
|
||||||
@ -127,7 +126,6 @@ public void updateSimpleField() {
|
|||||||
final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) loadedChild;
|
final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) loadedChild;
|
||||||
final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor();
|
final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor();
|
||||||
MatcherAssert.assertThat( interceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) );
|
MatcherAssert.assertThat( interceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) );
|
||||||
final EnhancementAsProxyLazinessInterceptor proxyInterceptor = (EnhancementAsProxyLazinessInterceptor) interceptor;
|
|
||||||
|
|
||||||
loadedChild.setName( updatedName );
|
loadedChild.setName( updatedName );
|
||||||
|
|
||||||
@ -139,21 +137,15 @@ public void updateSimpleField() {
|
|||||||
assertEquals( 1, stats.getPrepareStatementCount() );
|
assertEquals( 1, stats.getPrepareStatementCount() );
|
||||||
|
|
||||||
final EntityEntry entry = session.getPersistenceContext().getEntry( loadedChild );
|
final EntityEntry entry = session.getPersistenceContext().getEntry( loadedChild );
|
||||||
assertThat(
|
assertFalse( Hibernate.isInitialized( loadedChild.getRelatives() ) );
|
||||||
entry.getLoadedState()[ relativesAttributeIndex ],
|
|
||||||
is( LazyPropertyInitializer.UNFETCHED_PROPERTY )
|
|
||||||
);
|
|
||||||
|
|
||||||
// force a flush - the relatives collection should still be UNFETCHED_PROPERTY afterwards
|
// force a flush - the relatives collection should still be uninitialized afterwards
|
||||||
session.flush();
|
session.flush();
|
||||||
|
|
||||||
final EntityEntry updatedEntry = session.getPersistenceContext().getEntry( loadedChild );
|
final EntityEntry updatedEntry = session.getPersistenceContext().getEntry( loadedChild );
|
||||||
assertThat( updatedEntry, sameInstance( entry ) );
|
assertThat( updatedEntry, sameInstance( entry ) );
|
||||||
|
|
||||||
assertThat(
|
assertFalse( Hibernate.isInitialized( loadedChild.getRelatives() ) );
|
||||||
entry.getLoadedState()[ relativesAttributeIndex ],
|
|
||||||
is( LazyPropertyInitializer.UNFETCHED_PROPERTY )
|
|
||||||
);
|
|
||||||
|
|
||||||
session.getEventListenerManager();
|
session.getEventListenerManager();
|
||||||
}
|
}
|
||||||
@ -164,11 +156,7 @@ public void updateSimpleField() {
|
|||||||
Child loadedChild = session.load( Child.class, lastChildID );
|
Child loadedChild = session.load( Child.class, lastChildID );
|
||||||
assertThat( loadedChild.getName(), is( updatedName ) );
|
assertThat( loadedChild.getName(), is( updatedName ) );
|
||||||
|
|
||||||
final EntityEntry entry = session.getPersistenceContext().getEntry( loadedChild );
|
assertFalse( Hibernate.isInitialized( loadedChild.getRelatives() ) );
|
||||||
assertThat(
|
|
||||||
entry.getLoadedState()[ relativesAttributeIndex ],
|
|
||||||
is( LazyPropertyInitializer.UNFETCHED_PROPERTY )
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.hibernate.Hibernate.isInitialized;
|
||||||
import static org.hibernate.Hibernate.isPropertyInitialized;
|
import static org.hibernate.Hibernate.isPropertyInitialized;
|
||||||
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
|
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@ -106,28 +107,27 @@ public void testClearedSession() {
|
|||||||
// first load the store, making sure collection is not initialized
|
// first load the store, making sure collection is not initialized
|
||||||
Store store = s.get( Store.class, 1L );
|
Store store = s.get( Store.class, 1L );
|
||||||
assertNotNull( store );
|
assertNotNull( store );
|
||||||
assertFalse( isPropertyInitialized( store, "inventories" ) );
|
assertFalse( isInitialized( store.getInventories() ) );
|
||||||
assertEquals( 1, sessionFactory().getStatistics().getSessionOpenCount() );
|
assertEquals( 1, sessionFactory().getStatistics().getSessionOpenCount() );
|
||||||
assertEquals( 0, sessionFactory().getStatistics().getSessionCloseCount() );
|
assertEquals( 0, sessionFactory().getStatistics().getSessionCloseCount() );
|
||||||
|
|
||||||
// then clear session and try to initialize collection
|
// then clear session and try to initialize collection
|
||||||
s.clear();
|
s.clear();
|
||||||
assertNotNull( store );
|
assertNotNull( store );
|
||||||
assertFalse( isPropertyInitialized( store, "inventories" ) );
|
assertFalse( isInitialized( store.getInventories() ) );
|
||||||
store.getInventories().size();
|
store.getInventories().size();
|
||||||
assertTrue( isPropertyInitialized( store, "inventories" ) );
|
assertTrue( isInitialized( store.getInventories() ) );
|
||||||
|
|
||||||
// the extra Sessions are the temp Sessions needed to perform the init:
|
// the extra Session is the temp Sessions needed to perform the collection init (since it's lazy)
|
||||||
// first the entity, then the collection (since it's lazy)
|
assertEquals( 2, sessionFactory().getStatistics().getSessionOpenCount() );
|
||||||
assertEquals( 3, sessionFactory().getStatistics().getSessionOpenCount() );
|
assertEquals( 1, sessionFactory().getStatistics().getSessionCloseCount() );
|
||||||
assertEquals( 2, sessionFactory().getStatistics().getSessionCloseCount() );
|
|
||||||
|
|
||||||
// clear Session again. The collection should still be recognized as initialized from above
|
// clear Session again. The collection should still be recognized as initialized from above
|
||||||
s.clear();
|
s.clear();
|
||||||
assertNotNull( store );
|
assertNotNull( store );
|
||||||
assertTrue( isPropertyInitialized( store, "inventories" ) );
|
assertTrue( isInitialized( store.getInventories() ) );
|
||||||
assertEquals( 3, sessionFactory().getStatistics().getSessionOpenCount() );
|
assertEquals( 2, sessionFactory().getStatistics().getSessionOpenCount() );
|
||||||
assertEquals( 2, sessionFactory().getStatistics().getSessionCloseCount() );
|
assertEquals( 1, sessionFactory().getStatistics().getSessionCloseCount() );
|
||||||
|
|
||||||
// lets clear the Session again and this time reload the Store
|
// lets clear the Session again and this time reload the Store
|
||||||
s.clear();
|
s.clear();
|
||||||
@ -136,23 +136,22 @@ public void testClearedSession() {
|
|||||||
assertNotNull( store );
|
assertNotNull( store );
|
||||||
|
|
||||||
// collection should be back to uninitialized since we have a new entity instance
|
// collection should be back to uninitialized since we have a new entity instance
|
||||||
assertFalse( isPropertyInitialized( store, "inventories" ) );
|
assertFalse( isInitialized( store.getInventories() ) );
|
||||||
|
assertEquals( 2, sessionFactory().getStatistics().getSessionOpenCount() );
|
||||||
|
assertEquals( 1, sessionFactory().getStatistics().getSessionCloseCount() );
|
||||||
|
store.getInventories().size();
|
||||||
|
assertTrue( isInitialized( store.getInventories() ) );
|
||||||
|
|
||||||
|
// the extra Session is the temp Sessions needed to perform the collection init (since it's lazy)
|
||||||
assertEquals( 3, sessionFactory().getStatistics().getSessionOpenCount() );
|
assertEquals( 3, sessionFactory().getStatistics().getSessionOpenCount() );
|
||||||
assertEquals( 2, sessionFactory().getStatistics().getSessionCloseCount() );
|
assertEquals( 2, sessionFactory().getStatistics().getSessionCloseCount() );
|
||||||
store.getInventories().size();
|
|
||||||
assertTrue( isPropertyInitialized( store, "inventories" ) );
|
|
||||||
|
|
||||||
// the extra Sessions are the temp Sessions needed to perform the init:
|
|
||||||
// first the entity, then the collection (since it's lazy)
|
|
||||||
assertEquals( 5, sessionFactory().getStatistics().getSessionOpenCount() );
|
|
||||||
assertEquals( 4, sessionFactory().getStatistics().getSessionCloseCount() );
|
|
||||||
|
|
||||||
// clear Session again. The collection should still be recognized as initialized from above
|
// clear Session again. The collection should still be recognized as initialized from above
|
||||||
s.clear();
|
s.clear();
|
||||||
assertNotNull( store );
|
assertNotNull( store );
|
||||||
assertTrue( isPropertyInitialized( store, "inventories" ) );
|
assertTrue( isInitialized( store.getInventories() ) );
|
||||||
assertEquals( 5, sessionFactory().getStatistics().getSessionOpenCount() );
|
assertEquals( 3, sessionFactory().getStatistics().getSessionOpenCount() );
|
||||||
assertEquals( 4, sessionFactory().getStatistics().getSessionCloseCount() );
|
assertEquals( 2, sessionFactory().getStatistics().getSessionCloseCount() );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,7 +201,7 @@ Inventory addInventoryProduct(Product product) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<Inventory> getInventories() {
|
public List<Inventory> getInventories() {
|
||||||
return Collections.unmodifiableList( inventories );
|
return inventories;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user