HHH-11186 - Add examples for all Hibernate annotations
Document default entity listeners, @ExcludeDefaultListeners, and @ExcludeSuperclassListeners
This commit is contained in:
parent
43f74be58e
commit
9c53bfdd73
|
@ -182,14 +182,14 @@ See the <<chapters/domain/basic_types.adoc#basic-enums-Enumerated, `@Enumerated`
|
||||||
|
|
||||||
The http://docs.oracle.com/javaee/7/api/javax/persistence/ExcludeDefaultListeners.html[`@ExcludeDefaultListeners`] annotation is used to specify that the current annotated entity skips the invocation of any default listener.
|
The http://docs.oracle.com/javaee/7/api/javax/persistence/ExcludeDefaultListeners.html[`@ExcludeDefaultListeners`] annotation is used to specify that the current annotated entity skips the invocation of any default listener.
|
||||||
|
|
||||||
//TODO: Add example
|
See the <<chapters/events/Events.adoc#events-exclude-default-listener, Exclude default entity listeners>> section for more info.
|
||||||
|
|
||||||
[[annotations-jpa-excludesuperclasslisteners]]
|
[[annotations-jpa-excludesuperclasslisteners]]
|
||||||
==== `@ExcludeSuperclassListeners`
|
==== `@ExcludeSuperclassListeners`
|
||||||
|
|
||||||
The http://docs.oracle.com/javaee/7/api/javax/persistence/ExcludeSuperclassListeners.html[`@ExcludeSuperclassListeners`] annotation is used to specify that the current annotated entity skips the invocation of listeners declared by its superclass.
|
The http://docs.oracle.com/javaee/7/api/javax/persistence/ExcludeSuperclassListeners.html[`@ExcludeSuperclassListeners`] annotation is used to specify that the current annotated entity skips the invocation of listeners declared by its superclass.
|
||||||
|
|
||||||
//TODO: Add example
|
See the <<chapters/events/Events.adoc#events-exclude-default-listener, Exclude default entity listeners>> section for more info.
|
||||||
|
|
||||||
[[annotations-jpa-fieldresult]]
|
[[annotations-jpa-fieldresult]]
|
||||||
==== `@FieldResult`
|
==== `@FieldResult`
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
[[events]]
|
[[events]]
|
||||||
== Interceptors and events
|
== Interceptors and events
|
||||||
:sourcedir: ../../../../../test/java/org/hibernate/userguide/events
|
:sourcedir: ../../../../../test/java/org/hibernate/userguide/events
|
||||||
|
:extrasdir: extras
|
||||||
|
|
||||||
It is useful for the application to react to certain events that occur inside Hibernate.
|
It is useful for the application to react to certain events that occur inside Hibernate.
|
||||||
This allows for the implementation of generic functionality and the extension of Hibernate functionality.
|
This allows for the implementation of generic functionality and the extension of Hibernate functionality.
|
||||||
|
@ -177,3 +178,105 @@ See the `javax.persistence.ExcludeSuperclassListener`s annotation.
|
||||||
If a callback type is annotated on both an entity and one or more of its superclasses without method overriding, both would be called, the most general superclass first.
|
If a callback type is annotated on both an entity and one or more of its superclasses without method overriding, both would be called, the most general superclass first.
|
||||||
An entity class is also allowed to override a callback method defined in a superclass in which case the super callback would not get invoked; the overriding method would get invoked provided it is annotated.
|
An entity class is also allowed to override a callback method defined in a superclass in which case the super callback would not get invoked; the overriding method would get invoked provided it is annotated.
|
||||||
|
|
||||||
|
[[events-default-listener]]
|
||||||
|
=== Default entity listeners
|
||||||
|
|
||||||
|
The JPA specification allows you to define a default entity listener which is going to be applied for every entity in that particular system.
|
||||||
|
Default entity listeners can only be defined in XML mapping files.
|
||||||
|
|
||||||
|
[[events-default-listener-mapping-example]]
|
||||||
|
.Default event listner mapping
|
||||||
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/DefaultEntityListener.java[tags=events-default-listener-mapping-example]
|
||||||
|
----
|
||||||
|
|
||||||
|
[source, XML, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/DefaultEntityListener-orm.xml[tags=events-default-listener-mapping-example]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
Considering that all entities extend the `BaseEntity` class:
|
||||||
|
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/BaseEntity.java[tags=events-default-listener-mapping-example]
|
||||||
|
----
|
||||||
|
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/DefaultEntityListenerTest.java[tags=events-default-listener-mapping-example]
|
||||||
|
----
|
||||||
|
|
||||||
|
When persisting a `Person` or `Book` entity, the `createdOn` is going to be set by the `onPersist` method of the `DefaultEntityListener`.
|
||||||
|
|
||||||
|
[[events-default-listener-persist-example]]
|
||||||
|
.Default event listner persist event
|
||||||
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/DefaultEntityListenerTest.java[tags=events-default-listener-persist-example]
|
||||||
|
----
|
||||||
|
|
||||||
|
[source, SQL, indent=0]
|
||||||
|
----
|
||||||
|
include::{extrasdir}/events-default-listener-persist-example.sql[]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
When updating a `Person` or `Book` entity, the `updatedOn` is going to be set by the `onUpdate` method of the `DefaultEntityListener`.
|
||||||
|
|
||||||
|
[[events-default-listener-update-example]]
|
||||||
|
.Default event listner update event
|
||||||
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/DefaultEntityListenerTest.java[tags=events-default-listener-update-example]
|
||||||
|
----
|
||||||
|
|
||||||
|
[source, SQL, indent=0]
|
||||||
|
----
|
||||||
|
include::{extrasdir}/events-default-listener-update-example.sql[]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
[[events-exclude-default-listener]]
|
||||||
|
==== Exclude default entity listeners
|
||||||
|
|
||||||
|
If you already registered a default entity listener, but you don't want to apply it to a particular entity,
|
||||||
|
you can use the
|
||||||
|
http://docs.oracle.com/javaee/7/api/javax/persistence/ExcludeDefaultListeners.html[`@ExcludeDefaultListeners`] and
|
||||||
|
http://docs.oracle.com/javaee/7/api/javax/persistence/ExcludeSuperclassListeners.html[`@ExcludeSuperclassListeners`] JPA annotations.
|
||||||
|
|
||||||
|
`@ExcludeDefaultListeners` instructs the current class to ignore the default entity listeners for the current entity
|
||||||
|
while `@ExcludeSuperclassListeners` is used to ignore the default entity listeners propagated to the `BaseEntity` super-class.
|
||||||
|
|
||||||
|
[[events-exclude-default-listener-mapping-example]]
|
||||||
|
.Exclude default event listner mapping
|
||||||
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/DefaultEntityListenerTest.java[tags=events-exclude-default-listener-mapping-example]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
When persisting a `Publisher` entity,
|
||||||
|
the `createdOn` is not going to be set by the `onPersist` method of the `DefaultEntityListener`
|
||||||
|
because the `Publisher` entity was marked with the `@ExcludeDefaultListeners` and `@ExcludeSuperclassListeners` annotations.
|
||||||
|
|
||||||
|
[[events-exclude-default-listener-persist-example]]
|
||||||
|
.Excluding default event listner events
|
||||||
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/DefaultEntityListenerTest.java[tags=events-exclude-default-listener-persist-example]
|
||||||
|
----
|
||||||
|
|
||||||
|
[source, SQL, indent=0]
|
||||||
|
----
|
||||||
|
include::{extrasdir}/events-exclude-default-listener-persist-example.sql[]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
insert
|
||||||
|
into
|
||||||
|
Person
|
||||||
|
(createdOn, updatedOn, name, id)
|
||||||
|
values
|
||||||
|
(?, ?, ?, ?)
|
||||||
|
|
||||||
|
-- binding parameter [1] as [TIMESTAMP] - [2017-06-08 19:23:48.224]
|
||||||
|
-- binding parameter [2] as [TIMESTAMP] - [null]
|
||||||
|
-- binding parameter [3] as [VARCHAR] - [Vlad Mihalcea]
|
||||||
|
-- binding parameter [4] as [BIGINT] - [1]
|
||||||
|
|
||||||
|
insert
|
||||||
|
into
|
||||||
|
Book
|
||||||
|
(createdOn, updatedOn, author_id, title, id)
|
||||||
|
values
|
||||||
|
(?, ?, ?, ?, ?)
|
||||||
|
|
||||||
|
-- binding parameter [1] as [TIMESTAMP] - [2017-06-08 19:23:48.246]
|
||||||
|
-- binding parameter [2] as [TIMESTAMP] - [null]
|
||||||
|
-- binding parameter [3] as [BIGINT] - [1]
|
||||||
|
-- binding parameter [4] as [VARCHAR] - [High-Performance Java Persistence]
|
||||||
|
-- binding parameter [5] as [BIGINT] - [1]
|
|
@ -0,0 +1,29 @@
|
||||||
|
update
|
||||||
|
Person
|
||||||
|
set
|
||||||
|
createdOn=?,
|
||||||
|
updatedOn=?,
|
||||||
|
name=?
|
||||||
|
where
|
||||||
|
id=?
|
||||||
|
|
||||||
|
-- binding parameter [1] as [TIMESTAMP] - [2017-06-08 19:23:48.224]
|
||||||
|
-- binding parameter [2] as [TIMESTAMP] - [2017-06-08 19:23:48.316]
|
||||||
|
-- binding parameter [3] as [VARCHAR] - [Vlad-Alexandru Mihalcea]
|
||||||
|
-- binding parameter [4] as [BIGINT] - [1]
|
||||||
|
|
||||||
|
update
|
||||||
|
Book
|
||||||
|
set
|
||||||
|
createdOn=?,
|
||||||
|
updatedOn=?,
|
||||||
|
author_id=?,
|
||||||
|
title=?
|
||||||
|
where
|
||||||
|
id=?
|
||||||
|
|
||||||
|
-- binding parameter [1] as [TIMESTAMP] - [2017-06-08 19:23:48.246]
|
||||||
|
-- binding parameter [2] as [TIMESTAMP] - [2017-06-08 19:23:48.317]
|
||||||
|
-- binding parameter [3] as [BIGINT] - [1]
|
||||||
|
-- binding parameter [4] as [VARCHAR] - [High-Performance Java Persistence 2nd Edition]
|
||||||
|
-- binding parameter [5] as [BIGINT] - [1]
|
|
@ -0,0 +1,11 @@
|
||||||
|
insert
|
||||||
|
into
|
||||||
|
Publisher
|
||||||
|
(createdOn, updatedOn, name, id)
|
||||||
|
values
|
||||||
|
(?, ?, ?, ?)
|
||||||
|
|
||||||
|
-- binding parameter [1] as [TIMESTAMP] - [null]
|
||||||
|
-- binding parameter [2] as [TIMESTAMP] - [null]
|
||||||
|
-- binding parameter [3] as [VARCHAR] - [Amazon]
|
||||||
|
-- binding parameter [4] as [BIGINT] - [1]
|
|
@ -0,0 +1,34 @@
|
||||||
|
package org.hibernate.userguide.events;
|
||||||
|
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import javax.persistence.MappedSuperclass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Vlad Mihalcea
|
||||||
|
*/
|
||||||
|
//tag::events-default-listener-mapping-example[]
|
||||||
|
@MappedSuperclass
|
||||||
|
public abstract class BaseEntity {
|
||||||
|
|
||||||
|
private Timestamp createdOn;
|
||||||
|
|
||||||
|
private Timestamp updatedOn;
|
||||||
|
|
||||||
|
public Timestamp getCreatedOn() {
|
||||||
|
return createdOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCreatedOn(Timestamp createdOn) {
|
||||||
|
this.createdOn = createdOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Timestamp getUpdatedOn() {
|
||||||
|
return updatedOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setUpdatedOn(Timestamp updatedOn) {
|
||||||
|
this.updatedOn = updatedOn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//end::events-default-listener-mapping-example[]
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
~ Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
~
|
||||||
|
~ License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
~ See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
-->
|
||||||
|
<!--tag::events-default-listener-mapping-example[]-->
|
||||||
|
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm
|
||||||
|
http://xmlns.jcp.org/xml/ns/persistence/orm_2_1.xsd"
|
||||||
|
version="2.1">
|
||||||
|
<persistence-unit-metadata>
|
||||||
|
<persistence-unit-defaults>
|
||||||
|
<entity-listeners>
|
||||||
|
<entity-listener
|
||||||
|
class="org.hibernate.userguide.events.DefaultEntityListener">
|
||||||
|
<pre-persist method-name="onPersist"/>
|
||||||
|
<pre-update method-name="onUpdate"/>
|
||||||
|
</entity-listener>
|
||||||
|
</entity-listeners>
|
||||||
|
</persistence-unit-defaults>
|
||||||
|
</persistence-unit-metadata>
|
||||||
|
</entity-mappings>
|
||||||
|
<!--end::events-default-listener-mapping-example[]-->
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.hibernate.userguide.events;
|
||||||
|
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Vlad Mihalcea
|
||||||
|
*/
|
||||||
|
//tag::events-default-listener-mapping-example[]
|
||||||
|
public class DefaultEntityListener {
|
||||||
|
|
||||||
|
public void onPersist(Object entity) {
|
||||||
|
if ( entity instanceof BaseEntity ) {
|
||||||
|
BaseEntity baseEntity = (BaseEntity) entity;
|
||||||
|
baseEntity.setCreatedOn( now() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onUpdate(Object entity) {
|
||||||
|
if ( entity instanceof BaseEntity ) {
|
||||||
|
BaseEntity baseEntity = (BaseEntity) entity;
|
||||||
|
baseEntity.setUpdatedOn( now() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Timestamp now() {
|
||||||
|
return Timestamp.from(
|
||||||
|
LocalDateTime.now().toInstant( ZoneOffset.UTC )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//end::events-default-listener-mapping-example[]
|
|
@ -0,0 +1,192 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.userguide.events;
|
||||||
|
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.ExcludeDefaultListeners;
|
||||||
|
import javax.persistence.ExcludeSuperclassListeners;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.ManyToOne;
|
||||||
|
|
||||||
|
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static junit.framework.TestCase.assertNull;
|
||||||
|
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Vlad Mihalcea
|
||||||
|
*/
|
||||||
|
public class DefaultEntityListenerTest extends BaseEntityManagerFunctionalTestCase {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<?>[] getAnnotatedClasses() {
|
||||||
|
return new Class<?>[] {
|
||||||
|
Person.class,
|
||||||
|
Book.class,
|
||||||
|
Publisher.class
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String[] getMappings() {
|
||||||
|
return new String[] { "org/hibernate/userguide/events/DefaultEntityListener-orm.xml" };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
//tag::events-default-listener-persist-example[]
|
||||||
|
Person author = new Person();
|
||||||
|
author.setId( 1L );
|
||||||
|
author.setName( "Vlad Mihalcea" );
|
||||||
|
|
||||||
|
entityManager.persist( author );
|
||||||
|
|
||||||
|
Book book = new Book();
|
||||||
|
book.setId( 1L );
|
||||||
|
book.setTitle( "High-Performance Java Persistence" );
|
||||||
|
book.setAuthor( author );
|
||||||
|
|
||||||
|
entityManager.persist( book );
|
||||||
|
//end::events-default-listener-persist-example[]
|
||||||
|
} );
|
||||||
|
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
//tag::events-default-listener-update-example[]
|
||||||
|
Person author = entityManager.find( Person.class, 1L );
|
||||||
|
author.setName( "Vlad-Alexandru Mihalcea" );
|
||||||
|
|
||||||
|
Book book = entityManager.find( Book.class, 1L );
|
||||||
|
book.setTitle( "High-Performance Java Persistence 2nd Edition" );
|
||||||
|
//end::events-default-listener-update-example[]
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExclude() {
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
//tag::events-exclude-default-listener-persist-example[]
|
||||||
|
Publisher publisher = new Publisher();
|
||||||
|
publisher.setId( 1L );
|
||||||
|
publisher.setName( "Amazon" );
|
||||||
|
|
||||||
|
entityManager.persist( publisher );
|
||||||
|
//end::events-exclude-default-listener-persist-example[]
|
||||||
|
} );
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
Publisher publisher = entityManager.find( Publisher.class, 1L );
|
||||||
|
assertNull(publisher.getCreatedOn());
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
//tag::events-default-listener-mapping-example[]
|
||||||
|
@Entity(name = "Person")
|
||||||
|
public static class Person extends BaseEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
//Getters and setters omitted for brevity
|
||||||
|
//end::events-default-listener-mapping-example[]
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
//tag::events-default-listener-mapping-example[]
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Book")
|
||||||
|
public static class Book extends BaseEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
private Person author;
|
||||||
|
|
||||||
|
//Getters and setters omitted for brevity
|
||||||
|
//end::events-default-listener-mapping-example[]
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Person getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthor(Person author) {
|
||||||
|
this.author = author;
|
||||||
|
}
|
||||||
|
//tag::events-default-listener-mapping-example[]
|
||||||
|
}
|
||||||
|
//end::events-default-listener-mapping-example[]
|
||||||
|
|
||||||
|
//tag::events-exclude-default-listener-mapping-example[]
|
||||||
|
@Entity(name = "Publisher")
|
||||||
|
@ExcludeDefaultListeners
|
||||||
|
@ExcludeSuperclassListeners
|
||||||
|
public static class Publisher extends BaseEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
//Getters and setters omitted for brevity
|
||||||
|
//end::events-exclude-default-listener-mapping-example[]
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
//tag::events-exclude-default-listener-mapping-example[]
|
||||||
|
}
|
||||||
|
//end::events-exclude-default-listener-mapping-example[]
|
||||||
|
}
|
Loading…
Reference in New Issue