Browse Source

Polishing.

Update Kotlin, Reactive and List repository variant documentation.

See #2651
Original pull request: #2673.
pull/2681/head
Mark Paluch 3 years ago
parent
commit
02ec7ed19d
No known key found for this signature in database
GPG Key ID: 4406B84C1661DCD1
  1. 31
      src/main/java/org/springframework/data/repository/CrudRepository.java
  2. 5
      src/main/java/org/springframework/data/repository/ListCrudRepository.java
  3. 26
      src/main/java/org/springframework/data/repository/reactive/ReactiveCrudRepository.java
  4. 30
      src/main/java/org/springframework/data/repository/reactive/RxJava3CrudRepository.java
  5. 55
      src/main/kotlin/org/springframework/data/repository/kotlin/CoroutineCrudRepository.kt

31
src/main/java/org/springframework/data/repository/CrudRepository.java

@ -36,9 +36,9 @@ public interface CrudRepository<T, ID> extends Repository<T, ID> { @@ -36,9 +36,9 @@ public interface CrudRepository<T, ID> extends Repository<T, ID> {
* @param entity must not be {@literal null}.
* @return the saved entity; will never be {@literal null}.
* @throws IllegalArgumentException in case the given {@literal entity} is {@literal null}.
* @throws OptimisticLockingFailureException when the entity has a version attribute with a different value from that
* found in the persistence store. This also occurs when the entity which has a version attribute does no
* longer exist in the database.
* @throws OptimisticLockingFailureException when the entity uses optimistic locking and has a version attribute with
* a different value from that found in the persistence store. Also thrown if the entity is assumed to be
* present but does not exist in the database.
*/
<S extends T> S save(S entity);
@ -50,9 +50,9 @@ public interface CrudRepository<T, ID> extends Repository<T, ID> { @@ -50,9 +50,9 @@ public interface CrudRepository<T, ID> extends Repository<T, ID> {
* as the {@literal Iterable} passed as an argument.
* @throws IllegalArgumentException in case the given {@link Iterable entities} or one of its entities is
* {@literal null}.
* @throws OptimisticLockingFailureException when at least one entity has a version attribute with a different value from that
* found in the persistence store. This also occurs when the entity which has a version attribute does no
* longer exist in the database.
* @throws OptimisticLockingFailureException when at least one entity uses optimistic locking and has a version
* attribute with a different value from that found in the persistence store. Also thrown if at least one
* entity is assumed to be present but does not exist in the database.
*/
<S extends T> Iterable<S> saveAll(Iterable<S> entities);
@ -105,8 +105,7 @@ public interface CrudRepository<T, ID> extends Repository<T, ID> { @@ -105,8 +105,7 @@ public interface CrudRepository<T, ID> extends Repository<T, ID> {
/**
* Deletes the entity with the given id.
* <p>
* When no entity with the given id is available, <b>no</b> exception will be thrown.
* </p>
* If the entity is not found in the persistence store it is silently ignored.
*
* @param id must not be {@literal null}.
* @throws IllegalArgumentException in case the given {@literal id} is {@literal null}
@ -118,18 +117,16 @@ public interface CrudRepository<T, ID> extends Repository<T, ID> { @@ -118,18 +117,16 @@ public interface CrudRepository<T, ID> extends Repository<T, ID> {
*
* @param entity must not be {@literal null}.
* @throws IllegalArgumentException in case the given entity is {@literal null}.
* @throws OptimisticLockingFailureException when the entity has a version attribute with a different value from that
* found in the persistence store. This also occurs when the entity which has a version attribute does no
* longer exist in the database.
* @throws OptimisticLockingFailureException when the entity uses optimistic locking and has a version attribute with
* a different value from that found in the persistence store. Also thrown if the entity is assumed to be
* present but does not exist in the database.
*/
void delete(T entity);
/**
* Deletes all instances of the type {@code T} with the given IDs.
* <p>
* When some or all of the ids aren't found in the persistence store this is silently ignored and <b>no</b> exception
* is thrown.
* </p>
* Entities that aren't found in the persistence store are silently ignored.
*
* @param ids must not be {@literal null}. Must not contain {@literal null} elements.
* @throws IllegalArgumentException in case the given {@literal ids} or one of its elements is {@literal null}.
@ -142,9 +139,9 @@ public interface CrudRepository<T, ID> extends Repository<T, ID> { @@ -142,9 +139,9 @@ public interface CrudRepository<T, ID> extends Repository<T, ID> {
*
* @param entities must not be {@literal null}. Must not contain {@literal null} elements.
* @throws IllegalArgumentException in case the given {@literal entities} or one of its entities is {@literal null}.
* @throws OptimisticLockingFailureException when at least one of the entities has a version attribute with a
* different value from that found in the persistence store. This also occurs when the entity which has a
* version attribute does no longer exist in the database.
* @throws OptimisticLockingFailureException when at least one entity uses optimistic locking and has a version
* attribute with a different value from that found in the persistence store. Also thrown if at least one
* entity is assumed to be present but does not exist in the database.
*/
void deleteAll(Iterable<? extends T> entities);

5
src/main/java/org/springframework/data/repository/ListCrudRepository.java

@ -17,6 +17,8 @@ package org.springframework.data.repository; @@ -17,6 +17,8 @@ package org.springframework.data.repository;
import java.util.List;
import org.springframework.dao.OptimisticLockingFailureException;
/**
* Interface for generic CRUD operations on a repository for a specific type. This an extension to
* {@link CrudRepository} returning {@link List} instead of {@link Iterable} where applicable.
@ -36,6 +38,9 @@ public interface ListCrudRepository<T, ID> extends CrudRepository<T, ID> { @@ -36,6 +38,9 @@ public interface ListCrudRepository<T, ID> extends CrudRepository<T, ID> {
* as the {@literal Iterable} passed as an argument.
* @throws IllegalArgumentException in case the given {@link Iterable entities} or one of its entities is
* {@literal null}.
* @throws OptimisticLockingFailureException when at least one entity uses optimistic locking and has a version
* attribute with a different value from that found in the persistence store. Also thrown if at least one
* entity is assumed to be present but does not exist in the database.
*/
<S extends T> List<S> saveAll(Iterable<S> entities);

26
src/main/java/org/springframework/data/repository/reactive/ReactiveCrudRepository.java

@ -19,6 +19,8 @@ import reactor.core.publisher.Flux; @@ -19,6 +19,8 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.reactivestreams.Publisher;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.Repository;
@ -52,6 +54,9 @@ public interface ReactiveCrudRepository<T, ID> extends Repository<T, ID> { @@ -52,6 +54,9 @@ public interface ReactiveCrudRepository<T, ID> extends Repository<T, ID> {
* @param entity must not be {@literal null}.
* @return {@link Mono} emitting the saved entity.
* @throws IllegalArgumentException in case the given {@literal entity} is {@literal null}.
* @throws OptimisticLockingFailureException when the entity uses optimistic locking and has a version attribute with
* a different value from that found in the persistence store. Also thrown if the entity is assumed to be
* present but does not exist in the database.
*/
<S extends T> Mono<S> save(S entity);
@ -62,6 +67,9 @@ public interface ReactiveCrudRepository<T, ID> extends Repository<T, ID> { @@ -62,6 +67,9 @@ public interface ReactiveCrudRepository<T, ID> extends Repository<T, ID> {
* @return {@link Flux} emitting the saved entities.
* @throws IllegalArgumentException in case the given {@link Iterable entities} or one of its entities is
* {@literal null}.
* @throws OptimisticLockingFailureException when at least one entity uses optimistic locking and has a version
* attribute with a different value from that found in the persistence store. Also thrown if at least one
* entity is assumed to be present but does not exist in the database.
*/
<S extends T> Flux<S> saveAll(Iterable<S> entities);
@ -71,6 +79,9 @@ public interface ReactiveCrudRepository<T, ID> extends Repository<T, ID> { @@ -71,6 +79,9 @@ public interface ReactiveCrudRepository<T, ID> extends Repository<T, ID> {
* @param entityStream must not be {@literal null}.
* @return {@link Flux} emitting the saved entities.
* @throws IllegalArgumentException in case the given {@link Publisher entityStream} is {@literal null}.
* @throws OptimisticLockingFailureException when at least one entity uses optimistic locking and has a version
* attribute with a different value from that found in the persistence store. Also thrown if at least one
* entity is assumed to be present but does not exist in the database.
*/
<S extends T> Flux<S> saveAll(Publisher<S> entityStream);
@ -154,6 +165,8 @@ public interface ReactiveCrudRepository<T, ID> extends Repository<T, ID> { @@ -154,6 +165,8 @@ public interface ReactiveCrudRepository<T, ID> extends Repository<T, ID> {
/**
* Deletes the entity with the given id.
* <p>
* If the entity is not found in the persistence store it is silently ignored.
*
* @param id must not be {@literal null}.
* @return {@link Mono} signaling when operation has completed.
@ -163,6 +176,8 @@ public interface ReactiveCrudRepository<T, ID> extends Repository<T, ID> { @@ -163,6 +176,8 @@ public interface ReactiveCrudRepository<T, ID> extends Repository<T, ID> {
/**
* Deletes the entity with the given id supplied by a {@link Publisher}.
* <p>
* If the entity is not found in the persistence store it is silently ignored.
*
* @param id must not be {@literal null}.
* @return {@link Mono} signaling when operation has completed.
@ -176,11 +191,16 @@ public interface ReactiveCrudRepository<T, ID> extends Repository<T, ID> { @@ -176,11 +191,16 @@ public interface ReactiveCrudRepository<T, ID> extends Repository<T, ID> {
* @param entity must not be {@literal null}.
* @return {@link Mono} signaling when operation has completed.
* @throws IllegalArgumentException in case the given entity is {@literal null}.
* @throws OptimisticLockingFailureException when the entity uses optimistic locking and has a version attribute with
* a different value from that found in the persistence store. Also thrown if the entity is assumed to be
* present but does not exist in the database.
*/
Mono<Void> delete(T entity);
/**
* Deletes all instances of the type {@code T} with the given IDs.
* <p>
* Entities that aren't found in the persistence store are silently ignored.
*
* @param ids must not be {@literal null}.
* @return {@link Mono} signaling when operation has completed.
@ -197,6 +217,9 @@ public interface ReactiveCrudRepository<T, ID> extends Repository<T, ID> { @@ -197,6 +217,9 @@ public interface ReactiveCrudRepository<T, ID> extends Repository<T, ID> {
* @return {@link Mono} signaling when operation has completed.
* @throws IllegalArgumentException in case the given {@link Iterable entities} or one of its entities is
* {@literal null}.
* @throws OptimisticLockingFailureException when at least one entity uses optimistic locking and has a version
* attribute with a different value from that found in the persistence store. Also thrown if at least one
* entity is assumed to be present but does not exist in the database.
*/
Mono<Void> deleteAll(Iterable<? extends T> entities);
@ -206,6 +229,9 @@ public interface ReactiveCrudRepository<T, ID> extends Repository<T, ID> { @@ -206,6 +229,9 @@ public interface ReactiveCrudRepository<T, ID> extends Repository<T, ID> {
* @param entityStream must not be {@literal null}.
* @return {@link Mono} signaling when operation has completed.
* @throws IllegalArgumentException in case the given {@link Publisher entityStream} is {@literal null}.
* @throws OptimisticLockingFailureException when at least one entity uses optimistic locking and has a version
* attribute with a different value from that found in the persistence store. Also thrown if at least one
* entity is assumed to be present but does not exist in the database.
*/
Mono<Void> deleteAll(Publisher<? extends T> entityStream);

30
src/main/java/org/springframework/data/repository/reactive/RxJava3CrudRepository.java

@ -20,6 +20,7 @@ import io.reactivex.rxjava3.core.Flowable; @@ -20,6 +20,7 @@ import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.rxjava3.core.Single;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.Repository;
@ -27,10 +28,13 @@ import org.springframework.data.repository.Repository; @@ -27,10 +28,13 @@ import org.springframework.data.repository.Repository;
* Interface for generic CRUD operations on a repository for a specific type. This repository follows reactive paradigms
* and uses RxJava 3 types.
* <p>
* Save and delete operations with entities that have a version attribute trigger an {@code onError} with a {@link org.springframework.dao.OptimisticLockingFailureException} when they encounter a different version value in the persistence store than in the entity passed as an argument.
* Save and delete operations with entities that have a version attribute trigger an {@code onError} with a
* {@link org.springframework.dao.OptimisticLockingFailureException} when they encounter a different version value in
* the persistence store than in the entity passed as an argument.
* </p>
* <p>
* Other delete operations that only receive ids or entities without version attribute do not trigger an error when no matching data is found in the persistence store.
* Other delete operations that only receive ids or entities without version attribute do not trigger an error when no
* matching data is found in the persistence store.
* </p>
*
* @author Mark Paluch
@ -50,6 +54,9 @@ public interface RxJava3CrudRepository<T, ID> extends Repository<T, ID> { @@ -50,6 +54,9 @@ public interface RxJava3CrudRepository<T, ID> extends Repository<T, ID> {
* @param entity must not be {@literal null}.
* @return {@link Single} emitting the saved entity.
* @throws IllegalArgumentException in case the given {@literal entity} is {@literal null}.
* @throws OptimisticLockingFailureException when the entity uses optimistic locking and has a version attribute with
* a different value from that found in the persistence store. Also thrown if the entity is assumed to be
* present but does not exist in the database.
*/
<S extends T> Single<S> save(S entity);
@ -60,6 +67,9 @@ public interface RxJava3CrudRepository<T, ID> extends Repository<T, ID> { @@ -60,6 +67,9 @@ public interface RxJava3CrudRepository<T, ID> extends Repository<T, ID> {
* @return {@link Flowable} emitting the saved entities.
* @throws IllegalArgumentException in case the given {@link Iterable entities} or one of its entities is
* {@literal null}.
* @throws OptimisticLockingFailureException when at least one entity uses optimistic locking and has a version
* attribute with a different value from that found in the persistence store. Also thrown if at least one
* entity is assumed to be present but does not exist in the database.
*/
<S extends T> Flowable<S> saveAll(Iterable<S> entities);
@ -69,6 +79,9 @@ public interface RxJava3CrudRepository<T, ID> extends Repository<T, ID> { @@ -69,6 +79,9 @@ public interface RxJava3CrudRepository<T, ID> extends Repository<T, ID> {
* @param entityStream must not be {@literal null}.
* @return {@link Flowable} emitting the saved entities.
* @throws IllegalArgumentException in case the given {@link Flowable entityStream} is {@literal null}.
* @throws OptimisticLockingFailureException when at least one entity uses optimistic locking and has a version
* attribute with a different value from that found in the persistence store. Also thrown if at least one
* entity is assumed to be present but does not exist in the database.
*/
<S extends T> Flowable<S> saveAll(Flowable<S> entityStream);
@ -151,6 +164,8 @@ public interface RxJava3CrudRepository<T, ID> extends Repository<T, ID> { @@ -151,6 +164,8 @@ public interface RxJava3CrudRepository<T, ID> extends Repository<T, ID> {
/**
* Deletes the entity with the given id.
* <p>
* If the entity is not found in the persistence store it is silently ignored.
*
* @param id must not be {@literal null}.
* @return {@link Completable} signaling when operation has completed.
@ -164,11 +179,16 @@ public interface RxJava3CrudRepository<T, ID> extends Repository<T, ID> { @@ -164,11 +179,16 @@ public interface RxJava3CrudRepository<T, ID> extends Repository<T, ID> {
* @param entity must not be {@literal null}.
* @return {@link Completable} signaling when operation has completed.
* @throws IllegalArgumentException in case the given entity is {@literal null}.
* @throws OptimisticLockingFailureException when the entity uses optimistic locking and has a version attribute with
* a different value from that found in the persistence store. Also thrown if the entity is assumed to be
* present but does not exist in the database.
*/
Completable delete(T entity);
/**
* Deletes all instances of the type {@code T} with the given IDs.
* <p>
* Entities that aren't found in the persistence store are silently ignored.
*
* @param ids must not be {@literal null}.
* @return {@link Completable} signaling when operation has completed.
@ -185,6 +205,9 @@ public interface RxJava3CrudRepository<T, ID> extends Repository<T, ID> { @@ -185,6 +205,9 @@ public interface RxJava3CrudRepository<T, ID> extends Repository<T, ID> {
* @return {@link Completable} signaling when operation has completed.
* @throws IllegalArgumentException in case the given {@link Iterable entities} or one of its entities is
* {@literal null}.
* @throws OptimisticLockingFailureException when at least one entity uses optimistic locking and has a version
* attribute with a different value from that found in the persistence store. Also thrown if at least one
* entity is assumed to be present but does not exist in the database.
*/
Completable deleteAll(Iterable<? extends T> entities);
@ -194,6 +217,9 @@ public interface RxJava3CrudRepository<T, ID> extends Repository<T, ID> { @@ -194,6 +217,9 @@ public interface RxJava3CrudRepository<T, ID> extends Repository<T, ID> {
* @param entityStream must not be {@literal null}.
* @return {@link Completable} signaling when operation has completed.
* @throws IllegalArgumentException in case the given {@link Flowable entityStream} is {@literal null}.
* @throws OptimisticLockingFailureException when at least one entity uses optimistic locking and has a version
* attribute with a different value from that found in the persistence store. Also thrown if at least one
* entity is assumed to be present but does not exist in the database.
*/
Completable deleteAll(Flowable<? extends T> entityStream);

55
src/main/kotlin/org/springframework/data/repository/kotlin/CoroutineCrudRepository.kt

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
package org.springframework.data.repository.kotlin
import kotlinx.coroutines.flow.Flow
import org.springframework.dao.OptimisticLockingFailureException
import org.springframework.data.repository.NoRepositoryBean
import org.springframework.data.repository.Repository
import reactor.core.publisher.Mono
@ -44,7 +45,9 @@ interface CoroutineCrudRepository<T, ID> : Repository<T, ID> { @@ -44,7 +45,9 @@ interface CoroutineCrudRepository<T, ID> : Repository<T, ID> {
*
* @param entity must not be null.
* @return the saved entity.
* @throws IllegalArgumentException in case the given entity is null.
* @throws IllegalArgumentException in case the given entity is `null`.
* @throws OptimisticLockingFailureException when the entity uses optimistic locking and has a version attribute with a different value from that
* found in the persistence store. Also thrown if the entity is assumed to be present but does not exist in the database.
*/
suspend fun <S : T> save(entity: S): T
@ -54,7 +57,9 @@ interface CoroutineCrudRepository<T, ID> : Repository<T, ID> { @@ -54,7 +57,9 @@ interface CoroutineCrudRepository<T, ID> : Repository<T, ID> {
* @param entities must not be null.
* @return [Flow] emitting the saved entities.
* @throws IllegalArgumentException in case the given [entities][Flow] or one of its entities is
* null.
* `null`.
* @throws OptimisticLockingFailureException when at least one entity uses optimistic locking and has a version attribute with a different value from that
* found in the persistence store. Also thrown if at least one entity is assumed to be present but does not exist in the database.
*/
fun <S : T> saveAll(entities: Iterable<S>): Flow<S>
@ -63,25 +68,25 @@ interface CoroutineCrudRepository<T, ID> : Repository<T, ID> { @@ -63,25 +68,25 @@ interface CoroutineCrudRepository<T, ID> : Repository<T, ID> {
*
* @param entityStream must not be null.
* @return [Flow] emitting the saved entities.
* @throws IllegalArgumentException in case the given [entityStream][Flow] is null.
* @throws IllegalArgumentException in case the given [entityStream][Flow] is `null`.
*/
fun <S : T> saveAll(entityStream: Flow<S>): Flow<S>
/**
* Retrieves an entity by its id.
*
* @param id must not be null.
* @param id must not be `null`.
* @return [Mono] emitting the entity with the given id or empty if none found.
* @throws IllegalArgumentException in case the given id is null.
* @throws IllegalArgumentException in case the given id is `null`.
*/
suspend fun findById(id: ID): T?
/**
* Returns whether an entity with the given id exists.
*
* @param id must not be null.
* @param id must not be `null`.
* @return true if an entity with the given id exists, false otherwise.
* @throws IllegalArgumentException in case the given id is null.
* @throws IllegalArgumentException in case the given id is `null`.
*/
suspend fun existsById(id: ID): Boolean
@ -97,10 +102,10 @@ interface CoroutineCrudRepository<T, ID> : Repository<T, ID> { @@ -97,10 +102,10 @@ interface CoroutineCrudRepository<T, ID> : Repository<T, ID> {
* If some or all ids are not found, no entities are returned for these IDs.
* Note that the order of elements in the result is not guaranteed.
*
* @param ids must not be null nor contain any null values.
* @param ids must not be `null` nor contain any `null` values.
* @return [Flow] emitting the found entities. The size can be equal or less than the number of given
* ids.
* @throws IllegalArgumentException in case the given [ids][Iterable] or one of its items is null.
* @throws IllegalArgumentException in case the given [ids][Iterable] or one of its items is `null`.
*/
fun findAllById(ids: Iterable<ID>): Flow<T>
@ -109,41 +114,47 @@ interface CoroutineCrudRepository<T, ID> : Repository<T, ID> { @@ -109,41 +114,47 @@ interface CoroutineCrudRepository<T, ID> : Repository<T, ID> {
* If some or all ids are not found, no entities are returned for these IDs.
* Note that the order of elements in the result is not guaranteed.
*
* @param ids must not be null nor contain any null values.
* @param ids must not be `null` nor contain any `null` values.
* @return [Flow] emitting the found entities. The size can be equal or less than the number of given
* ids.
* @throws IllegalArgumentException in case the given [ids][Iterable] or one of its items is null.
* @throws IllegalArgumentException in case the given [ids][Iterable] or one of its items is `null`.
*/
fun findAllById(ids: Flow<ID>): Flow<T>
/**
* Returns the number of entities available.
*
* @return number of entities.
* @return number of entities.
*/
suspend fun count(): Long
/**
* Deletes the entity with the given id.
* <p>
* If the entity is not found in the persistence store it is silently ignored.
*
* @param id must not be null.
* @throws IllegalArgumentException in case the given id is null.
* @param id must not be `null`.
* @throws IllegalArgumentException in case the given id is `null`.
*/
suspend fun deleteById(id: ID)
/**
* Deletes a given entity.
*
* @param entity must not be null.
* @throws IllegalArgumentException in case the given entity is null.
* @param entity must not be `null`.
* @throws IllegalArgumentException in case the given entity is `null`.
* @throws OptimisticLockingFailureException when the entity uses optimistic locking and has a version attribute with a different value from that
* found in the persistence store. Also thrown if the entity is assumed to be present but does not exist in the database.
*/
suspend fun delete(entity: T)
/**
* Deletes all instances of the type {@code T} with the given IDs.
* <p>
* Entities that aren't found in the persistence store are silently ignored.
*
* @param ids must not be null nor contain any null values.
* @throws IllegalArgumentException in case the given [ids][Iterable] or one of its items is null.
* @param ids must not be `null` nor contain any `null` values.
* @throws IllegalArgumentException in case the given [ids][Iterable] or one of its items is `null`.
* @since 2.5
*/
suspend fun deleteAllById(ids: Iterable<ID>)
@ -151,9 +162,9 @@ interface CoroutineCrudRepository<T, ID> : Repository<T, ID> { @@ -151,9 +162,9 @@ interface CoroutineCrudRepository<T, ID> : Repository<T, ID> {
/**
* Deletes the given entities.
*
* @param entities must not be null.
* @param entities must not be `null`.
* @throws IllegalArgumentException in case the given [entities][Iterable] or one of its entities is
* null.
* `null`.
*/
suspend fun deleteAll(entities: Iterable<T>)
@ -161,7 +172,9 @@ interface CoroutineCrudRepository<T, ID> : Repository<T, ID> { @@ -161,7 +172,9 @@ interface CoroutineCrudRepository<T, ID> : Repository<T, ID> {
* Deletes all given entities.
*
* @param entityStream must not be null.
* @throws IllegalArgumentException in case the given [entityStream][Flow] is null.
* @throws IllegalArgumentException in case the given [entityStream][Flow] is `null`.
* @throws OptimisticLockingFailureException when at least one entity uses optimistic locking and has a version attribute with a different value from that
* found in the persistence store. Also thrown if at least one entity is assumed to be present but does not exist in the database.
*/
suspend fun <S : T> deleteAll(entityStream: Flow<S>)

Loading…
Cancel
Save