Browse Source

DATAJDBC-453 - Polishing.

Remove unused AggregateChangeExecutionContext. Extract MutableAggregateChange interface and use it to encapsulate the implementation class. Expose MutableAggregateChange in entity callbacks where mutation of the MutableAggregateChange is intended.

Fix generics and license headers, tweak Javadoc.

Original pull request: #197.
pull/201/head
Mark Paluch 6 years ago
parent
commit
6769528068
No known key found for this signature in database
GPG Key ID: 51A00FA751B91849
  1. 2
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/AggregateChangeExecutor.java
  2. 3
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutionContext.java
  3. 5
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextUnitTests.java
  4. 1
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateUnitTests.java
  5. 36
      spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AggregateChange.java
  6. 3
      spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AggregateChangeExecutionContext.java
  7. 118
      spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DefaultAggregateChange.java
  8. 102
      spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MutableAggregateChange.java
  9. 8
      spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityDeleteWriter.java
  10. 8
      spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/WritingContext.java
  11. 5
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterDeleteEvent.java
  12. 4
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterSaveEvent.java
  13. 6
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeDeleteCallback.java
  14. 6
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeDeleteEvent.java
  15. 6
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeSaveCallback.java
  16. 6
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeSaveEvent.java
  17. 1
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/WithAggregateChange.java
  18. 6
      spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityDeleteWriterUnitTests.java
  19. 4
      spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityInsertWriterUnitTests.java
  20. 3
      spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityUpdateWriterUnitTests.java
  21. 40
      spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java

2
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/AggregateChangeExecutor.java

@ -56,7 +56,6 @@ class AggregateChangeExecutor {
} }
return root; return root;
} }
private void execute(DbAction<?> action, JdbcAggregateChangeExecutionContext executionContext) { private void execute(DbAction<?> action, JdbcAggregateChangeExecutionContext executionContext) {
@ -85,5 +84,4 @@ class AggregateChangeExecutor {
throw new DbActionExecutionException(action, e); throw new DbActionExecutionException(action, e);
} }
} }
} }

3
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutionContext.java

@ -34,7 +34,6 @@ import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.relational.core.conversion.AggregateChangeExecutionContext;
import org.springframework.data.relational.core.conversion.DbAction; import org.springframework.data.relational.core.conversion.DbAction;
import org.springframework.data.relational.core.conversion.DbActionExecutionResult; import org.springframework.data.relational.core.conversion.DbActionExecutionResult;
import org.springframework.data.relational.core.conversion.RelationalEntityVersionUtils; import org.springframework.data.relational.core.conversion.RelationalEntityVersionUtils;
@ -49,7 +48,7 @@ import org.springframework.util.Assert;
/** /**
* @author Jens Schauder * @author Jens Schauder
*/ */
class JdbcAggregateChangeExecutionContext implements AggregateChangeExecutionContext { class JdbcAggregateChangeExecutionContext {
private static final String UPDATE_FAILED = "Failed to update entity [%s]. Id [%s] not found in database."; private static final String UPDATE_FAILED = "Failed to update entity [%s]. Id [%s] not found in database.";
private static final String UPDATE_FAILED_OPTIMISTIC_LOCKING = "Failed to update entity [%s]. The entity was updated since it was rea or it isn't in the database at all."; private static final String UPDATE_FAILED_OPTIMISTIC_LOCKING = "Failed to update entity [%s]. The entity was updated since it was rea or it isn't in the database at all.";

5
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextUnitTests.java

@ -38,6 +38,11 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentProp
import org.springframework.data.relational.domain.Identifier; import org.springframework.data.relational.domain.Identifier;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
/**
* Unit tests for {@link JdbcAggregateChangeExecutionContext}.
*
* @author Jens Schauder
*/
public class JdbcAggregateChangeExecutorContextUnitTests { public class JdbcAggregateChangeExecutorContextUnitTests {
RelationalMappingContext context = new RelationalMappingContext(); RelationalMappingContext context = new RelationalMappingContext();

1
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateUnitTests.java

@ -28,6 +28,7 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;

36
spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AggregateChange.java

@ -1,18 +1,32 @@
/*
* Copyright 2017-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.relational.core.conversion; package org.springframework.data.relational.core.conversion;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
/**
* Represents the change happening to the aggregate (as used in the context of Domain Driven Design) as a whole.
*
* @author Jens Schauder
* @author Mark Paluch
*/
public interface AggregateChange<T> { public interface AggregateChange<T> {
/**
* Applies the given consumer to each {@link DbAction} in this {@code AggregateChange}.
*
* @param consumer must not be {@literal null}.
*/
void forEachAction(Consumer<? super DbAction<?>> consumer);
/** /**
* Returns the {@link Kind} of {@code AggregateChange} this is. * Returns the {@link Kind} of {@code AggregateChange} this is.
* *
@ -35,10 +49,18 @@ public interface AggregateChange<T> {
@Nullable @Nullable
T getEntity(); T getEntity();
/**
* Applies the given consumer to each {@link DbAction} in this {@code AggregateChange}.
*
* @param consumer must not be {@literal null}.
*/
void forEachAction(Consumer<? super DbAction<?>> consumer);
/** /**
* The kind of action to be performed on an aggregate. * The kind of action to be performed on an aggregate.
*/ */
enum Kind { enum Kind {
/** /**
* A {@code SAVE} of an aggregate typically involves an {@code insert} or {@code update} on the aggregate root plus * A {@code SAVE} of an aggregate typically involves an {@code insert} or {@code update} on the aggregate root plus
* {@code insert}s, {@code update}s, and {@code delete}s on the other elements of an aggregate. * {@code insert}s, {@code update}s, and {@code delete}s on the other elements of an aggregate.

3
spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/AggregateChangeExecutionContext.java

@ -1,3 +0,0 @@
package org.springframework.data.relational.core.conversion;
public interface AggregateChangeExecutionContext {}

118
spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DefaultAggregateChange.java

@ -0,0 +1,118 @@
/*
* Copyright 2017-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.relational.core.conversion;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Represents the change happening to the aggregate (as used in the context of Domain Driven Design) as a whole.
*
* @author Jens Schauder
* @author Mark Paluch
* @since 2.0
*/
class DefaultAggregateChange<T> implements MutableAggregateChange<T> {
private final Kind kind;
/** Type of the aggregate root to be changed */
private final Class<T> entityType;
private final List<DbAction<?>> actions = new ArrayList<>();
/** Aggregate root, to which the change applies, if available */
private @Nullable T entity;
public DefaultAggregateChange(Kind kind, Class<T> entityType, @Nullable T entity) {
this.kind = kind;
this.entityType = entityType;
this.entity = entity;
}
/**
* Adds an action to this {@code AggregateChange}.
*
* @param action must not be {@literal null}.
*/
@Override
public void addAction(DbAction<?> action) {
Assert.notNull(action, "Action must not be null.");
actions.add(action);
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.conversion.AggregateChange#getKind()
*/
@Override
public Kind getKind() {
return this.kind;
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.conversion.AggregateChange#getEntityType()
*/
@Override
public Class<T> getEntityType() {
return this.entityType;
}
/**
* Set the root object of the {@code AggregateChange}.
*
* @param aggregateRoot may be {@literal null} if the change refers to a list of aggregates or references it by id.
*/
@Override
public void setEntity(@Nullable T aggregateRoot) {
if (aggregateRoot != null) {
Assert.isInstanceOf(this.entityType, aggregateRoot,
String.format("AggregateRoot must be of type %s", entityType.getName()));
}
this.entity = aggregateRoot;
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.conversion.AggregateChange#getEntity()
*/
@Override
public T getEntity() {
return this.entity;
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.conversion.AggregateChange#forEachAction(java.util.function.Consumer)
*/
@Override
public void forEachAction(Consumer<? super DbAction<?>> consumer) {
Assert.notNull(consumer, "Consumer must not be null.");
actions.forEach(consumer);
}
}

102
spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MutableAggregateChange.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2017-2020 the original author or authors. * Copyright 2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -15,10 +15,6 @@
*/ */
package org.springframework.data.relational.core.conversion; package org.springframework.data.relational.core.conversion;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
@ -28,24 +24,9 @@ import org.springframework.util.ClassUtils;
* *
* @author Jens Schauder * @author Jens Schauder
* @author Mark Paluch * @author Mark Paluch
* @since 2.0
*/ */
public class MutableAggregateChange<T> implements AggregateChange<T> { public interface MutableAggregateChange<T> extends AggregateChange<T> {
private final Kind kind;
/** Type of the aggregate root to be changed */
private final Class<T> entityType;
private final List<DbAction<?>> actions = new ArrayList<>();
/** Aggregate root, to which the change applies, if available */
@Nullable private T entity;
public MutableAggregateChange(Kind kind, Class<T> entityType, @Nullable T entity) {
this.kind = kind;
this.entityType = entityType;
this.entity = entity;
}
/** /**
* Factory method to create an {@link MutableAggregateChange} for saving entities. * Factory method to create an {@link MutableAggregateChange} for saving entities.
@ -56,10 +37,11 @@ public class MutableAggregateChange<T> implements AggregateChange<T> {
* @since 1.2 * @since 1.2
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T> MutableAggregateChange<T> forSave(T entity) { static <T> MutableAggregateChange<T> forSave(T entity) {
Assert.notNull(entity, "Entity must not be null"); Assert.notNull(entity, "Entity must not be null");
return new MutableAggregateChange<>(Kind.SAVE, (Class<T>) ClassUtils.getUserClass(entity), entity);
return new DefaultAggregateChange<>(Kind.SAVE, (Class<T>) ClassUtils.getUserClass(entity), entity);
} }
/** /**
@ -71,9 +53,10 @@ public class MutableAggregateChange<T> implements AggregateChange<T> {
* @since 1.2 * @since 1.2
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T> MutableAggregateChange<T> forDelete(T entity) { static <T> MutableAggregateChange<T> forDelete(T entity) {
Assert.notNull(entity, "Entity must not be null"); Assert.notNull(entity, "Entity must not be null");
return forDelete((Class<T>) ClassUtils.getUserClass(entity), entity); return forDelete((Class<T>) ClassUtils.getUserClass(entity), entity);
} }
@ -86,81 +69,24 @@ public class MutableAggregateChange<T> implements AggregateChange<T> {
* @return the {@link MutableAggregateChange} for deleting the root {@code entity}. * @return the {@link MutableAggregateChange} for deleting the root {@code entity}.
* @since 1.2 * @since 1.2
*/ */
public static <T> MutableAggregateChange<T> forDelete(Class<T> entityClass, @Nullable T entity) { static <T> MutableAggregateChange<T> forDelete(Class<T> entityClass, @Nullable T entity) {
Assert.notNull(entityClass, "Entity class must not be null"); Assert.notNull(entityClass, "Entity class must not be null");
return new MutableAggregateChange<>(Kind.DELETE, entityClass, entity);
return new DefaultAggregateChange<>(Kind.DELETE, entityClass, entity);
} }
/** /**
* Adds an action to this {@code AggregateChange}. * Adds an action to this {@code AggregateChange}.
* *
* @param action must not be {@literal null}. * @param action must not be {@literal null}.
*/ */
public void addAction(DbAction<?> action) { void addAction(DbAction<?> action);
Assert.notNull(action, "Action must not be null.");
actions.add(action);
}
/**
* Applies the given consumer to each {@link DbAction} in this {@code AggregateChange}.
*
* @param consumer must not be {@literal null}.
*/
@Override
public void forEachAction(Consumer<? super DbAction<?>> consumer) {
Assert.notNull(consumer, "Consumer must not be null.");
actions.forEach(consumer);
}
/**
* Returns the {@link Kind} of {@code AggregateChange} this is.
*
* @return guaranteed to be not {@literal null}.
*/
@Override
public Kind getKind() {
return this.kind;
}
/**
* The type of the root of this {@code AggregateChange}.
*
* @return Guaranteed to be not {@literal null}.
*/
@Override
public Class<T> getEntityType() {
return this.entityType;
}
/** /**
* Set the root object of the {@code AggregateChange}. * Set the root object of the {@code AggregateChange}.
* *
* @param aggregateRoot may be {@literal null} if the change refers to a list of aggregates or references it by id. * @param aggregateRoot may be {@literal null} if the change refers to a list of aggregates or references it by id.
*/ */
public void setEntity(@Nullable T aggregateRoot) { void setEntity(@Nullable T aggregateRoot);
if (aggregateRoot != null) {
Assert.isInstanceOf(entityType, aggregateRoot,
String.format("AggregateRoot must be of type %s", entityType.getName()));
}
entity = aggregateRoot;
}
/**
* The entity to which this {@link MutableAggregateChange} relates.
*
* @return may be {@literal null}.
*/
@Override
@Nullable
public T getEntity() {
return this.entity;
}
} }

8
spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityDeleteWriter.java

@ -81,7 +81,7 @@ public class RelationalEntityDeleteWriter implements EntityWriter<Object, Mutabl
return actions; return actions;
} }
private <T> List<DbAction<?>> deleteRoot(Object id, MutableAggregateChange<T> aggregateChange) { private <T> List<DbAction<?>> deleteRoot(Object id, AggregateChange<T> aggregateChange) {
List<DbAction<?>> actions = new ArrayList<>(deleteReferencedEntities(id, aggregateChange)); List<DbAction<?>> actions = new ArrayList<>(deleteReferencedEntities(id, aggregateChange));
actions.add(new DbAction.DeleteRoot<>(id, aggregateChange.getEntityType(), getVersion(aggregateChange))); actions.add(new DbAction.DeleteRoot<>(id, aggregateChange.getEntityType(), getVersion(aggregateChange)));
@ -90,12 +90,12 @@ public class RelationalEntityDeleteWriter implements EntityWriter<Object, Mutabl
} }
/** /**
* Add {@link DbAction.Delete} actions to the {@link MutableAggregateChange} for deleting all referenced entities. * Add {@link DbAction.Delete} actions to the {@link AggregateChange} for deleting all referenced entities.
* *
* @param id id of the aggregate root, of which the referenced entities get deleted. * @param id id of the aggregate root, of which the referenced entities get deleted.
* @param aggregateChange the change object to which the actions should get added. Must not be {@code null} * @param aggregateChange the change object to which the actions should get added. Must not be {@code null}
*/ */
private List<DbAction<?>> deleteReferencedEntities(Object id, MutableAggregateChange<?> aggregateChange) { private List<DbAction<?>> deleteReferencedEntities(Object id, AggregateChange<?> aggregateChange) {
List<DbAction<?>> actions = new ArrayList<>(); List<DbAction<?>> actions = new ArrayList<>();
@ -108,7 +108,7 @@ public class RelationalEntityDeleteWriter implements EntityWriter<Object, Mutabl
} }
@Nullable @Nullable
private Number getVersion(MutableAggregateChange<?> aggregateChange) { private Number getVersion(AggregateChange<?> aggregateChange) {
RelationalPersistentEntity<?> persistentEntity = context RelationalPersistentEntity<?> persistentEntity = context
.getRequiredPersistentEntity(aggregateChange.getEntityType()); .getRequiredPersistentEntity(aggregateChange.getEntityType());

8
spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/WritingContext.java

@ -46,8 +46,8 @@ class WritingContext {
private final Object entity; private final Object entity;
private final Class<?> entityType; private final Class<?> entityType;
private final PersistentPropertyPaths<?, RelationalPersistentProperty> paths; private final PersistentPropertyPaths<?, RelationalPersistentProperty> paths;
private final Map<PathNode, DbAction> previousActions = new HashMap<>(); private final Map<PathNode, DbAction<?>> previousActions = new HashMap<>();
private Map<PersistentPropertyPath<RelationalPersistentProperty>, List<PathNode>> nodesCache = new HashMap<>(); private final Map<PersistentPropertyPath<RelationalPersistentProperty>, List<PathNode>> nodesCache = new HashMap<>();
WritingContext(RelationalMappingContext context, Object root, MutableAggregateChange<?> aggregateChange) { WritingContext(RelationalMappingContext context, Object root, MutableAggregateChange<?> aggregateChange) {
@ -180,7 +180,7 @@ class WritingContext {
@Nullable @Nullable
private DbAction.WithEntity<?> getAction(@Nullable PathNode parent) { private DbAction.WithEntity<?> getAction(@Nullable PathNode parent) {
DbAction action = previousActions.get(parent); DbAction<?> action = previousActions.get(parent);
if (action != null) { if (action != null) {
@ -274,7 +274,7 @@ class WritingContext {
((Map<?, ?>) value).forEach((k, v) -> nodes.add(new PathNode(path, parentNode, Pair.of(k, v)))); ((Map<?, ?>) value).forEach((k, v) -> nodes.add(new PathNode(path, parentNode, Pair.of(k, v))));
} else { } else {
List listValue = (List) value; List<Object> listValue = (List<Object>) value;
for (int k = 0; k < listValue.size(); k++) { for (int k = 0; k < listValue.size(); k++) {
nodes.add(new PathNode(path, parentNode, Pair.of(k, listValue.get(k)))); nodes.add(new PathNode(path, parentNode, Pair.of(k, listValue.get(k))));
} }

5
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterDeleteEvent.java

@ -16,7 +16,6 @@
package org.springframework.data.relational.core.mapping.event; package org.springframework.data.relational.core.mapping.event;
import org.springframework.data.relational.core.conversion.AggregateChange; import org.springframework.data.relational.core.conversion.AggregateChange;
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
/** /**
@ -33,8 +32,8 @@ public class AfterDeleteEvent<E> extends RelationalDeleteEvent<E> {
/** /**
* @param id of the entity. Must not be {@literal null}. * @param id of the entity. Must not be {@literal null}.
* @param instance the deleted entity if it is available. May be {@literal null}. * @param instance the deleted entity if it is available. May be {@literal null}.
* @param change the {@link MutableAggregateChange} encoding the actions that were performed on the database as part * @param change the {@link AggregateChange} encoding the actions that were performed on the database as part of the
* of the delete operation. Must not be {@literal null}. * delete operation. Must not be {@literal null}.
*/ */
public AfterDeleteEvent(Identifier id, @Nullable E instance, AggregateChange<E> change) { public AfterDeleteEvent(Identifier id, @Nullable E instance, AggregateChange<E> change) {
super(id, instance, change); super(id, instance, change);

4
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterSaveEvent.java

@ -16,7 +16,6 @@
package org.springframework.data.relational.core.mapping.event; package org.springframework.data.relational.core.mapping.event;
import org.springframework.data.relational.core.conversion.AggregateChange; import org.springframework.data.relational.core.conversion.AggregateChange;
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
/** /**
* Gets published after a new instance or a changed instance was saved in the database. * Gets published after a new instance or a changed instance was saved in the database.
@ -29,8 +28,7 @@ public class AfterSaveEvent<E> extends RelationalSaveEvent<E> {
/** /**
* @param instance the saved entity. Must not be {@literal null}. * @param instance the saved entity. Must not be {@literal null}.
* @param change the {@link MutableAggregateChange} encoding the actions performed on the database as part of the * @param change the {@link AggregateChange} encoding the actions performed on the database as part of the delete.
* delete.
* Must not be {@literal null}. * Must not be {@literal null}.
*/ */
public AfterSaveEvent(E instance, AggregateChange<E> change) { public AfterSaveEvent(E instance, AggregateChange<E> change) {

6
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeDeleteCallback.java

@ -16,7 +16,6 @@
package org.springframework.data.relational.core.mapping.event; package org.springframework.data.relational.core.mapping.event;
import org.springframework.data.mapping.callback.EntityCallback; import org.springframework.data.mapping.callback.EntityCallback;
import org.springframework.data.relational.core.conversion.AggregateChange;
import org.springframework.data.relational.core.conversion.MutableAggregateChange; import org.springframework.data.relational.core.conversion.MutableAggregateChange;
/** /**
@ -25,6 +24,7 @@ import org.springframework.data.relational.core.conversion.MutableAggregateChang
* or without any parameter don't invoke this callback. * or without any parameter don't invoke this callback.
* *
* @author Jens Schauder * @author Jens Schauder
* @author Mark Paluch
* @since 1.1 * @since 1.1
*/ */
@FunctionalInterface @FunctionalInterface
@ -37,8 +37,8 @@ public interface BeforeDeleteCallback<T> extends EntityCallback<T> {
* account for deleting. Only transient fields of the entity should be changed in this callback. * account for deleting. Only transient fields of the entity should be changed in this callback.
* *
* @param aggregate the aggregate. * @param aggregate the aggregate.
* @param aggregateChange the associated {@link MutableAggregateChange}. * @param aggregateChange the associated {@link DefaultAggregateChange}.
* @return the aggregate to be deleted. * @return the aggregate to be deleted.
*/ */
T onBeforeDelete(T aggregate, AggregateChange<T> aggregateChange); T onBeforeDelete(T aggregate, MutableAggregateChange<T> aggregateChange);
} }

6
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeDeleteEvent.java

@ -16,12 +16,10 @@
package org.springframework.data.relational.core.mapping.event; package org.springframework.data.relational.core.mapping.event;
import org.springframework.data.relational.core.conversion.AggregateChange; import org.springframework.data.relational.core.conversion.AggregateChange;
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
/** /**
* Gets published when an entity is about to get deleted. The contained {@link MutableAggregateChange} is mutable and * Gets published when an entity is about to get deleted.
* may be changed in order to change the actions that get performed on the database as part of the delete operation.
* *
* @author Jens Schauder * @author Jens Schauder
*/ */
@ -32,7 +30,7 @@ public class BeforeDeleteEvent<E> extends RelationalDeleteEvent<E> {
/** /**
* @param id the id of the entity. Must not be {@literal null}. * @param id the id of the entity. Must not be {@literal null}.
* @param entity the entity about to get deleted. May be {@literal null}. * @param entity the entity about to get deleted. May be {@literal null}.
* @param change the {@link MutableAggregateChange} encoding the planned actions to be performed on the database. * @param change the {@link AggregateChange} containing the planned actions to be performed on the database.
*/ */
public BeforeDeleteEvent(Identifier id, @Nullable E entity, AggregateChange<E> change) { public BeforeDeleteEvent(Identifier id, @Nullable E entity, AggregateChange<E> change) {
super(id, entity, change); super(id, entity, change);

6
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeSaveCallback.java

@ -16,7 +16,6 @@
package org.springframework.data.relational.core.mapping.event; package org.springframework.data.relational.core.mapping.event;
import org.springframework.data.mapping.callback.EntityCallback; import org.springframework.data.mapping.callback.EntityCallback;
import org.springframework.data.relational.core.conversion.AggregateChange;
import org.springframework.data.relational.core.conversion.MutableAggregateChange; import org.springframework.data.relational.core.conversion.MutableAggregateChange;
/** /**
@ -34,12 +33,11 @@ public interface BeforeSaveCallback<T> extends EntityCallback<T> {
* Entity callback method invoked before an aggregate root is saved. Can return either the same or a modified instance * Entity callback method invoked before an aggregate root is saved. Can return either the same or a modified instance
* of the aggregate and can modify {@link MutableAggregateChange} contents. This method is called after converting the * of the aggregate and can modify {@link MutableAggregateChange} contents. This method is called after converting the
* {@code aggregate} to {@link MutableAggregateChange}. Changes to the aggregate are not taken into account for * {@code aggregate} to {@link MutableAggregateChange}. Changes to the aggregate are not taken into account for
* saving. Only transient fields of the entity should be changed in this callback. To change persistent the entity * saving. Use the {@link BeforeConvertCallback} to change the persistent the entity before being converted.
* before being converted, use the {@link BeforeConvertCallback}.
* *
* @param aggregate the aggregate. * @param aggregate the aggregate.
* @param aggregateChange the associated {@link MutableAggregateChange}. * @param aggregateChange the associated {@link MutableAggregateChange}.
* @return the aggregate object to be persisted. * @return the aggregate object to be persisted.
*/ */
T onBeforeSave(T aggregate, AggregateChange<T> aggregateChange); T onBeforeSave(T aggregate, MutableAggregateChange<T> aggregateChange);
} }

6
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeSaveEvent.java

@ -16,11 +16,9 @@
package org.springframework.data.relational.core.mapping.event; package org.springframework.data.relational.core.mapping.event;
import org.springframework.data.relational.core.conversion.AggregateChange; import org.springframework.data.relational.core.conversion.AggregateChange;
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
/** /**
* Gets published before an entity gets saved to the database. The contained {@link MutableAggregateChange} is mutable * Gets published before an entity gets saved to the database.
* and may be changed in order to change the actions that get performed on the database as part of the save operation.
* *
* @author Jens Schauder * @author Jens Schauder
*/ */
@ -30,7 +28,7 @@ public class BeforeSaveEvent<E> extends RelationalSaveEvent<E> {
/** /**
* @param instance the entity about to get saved. Must not be {@literal null}. * @param instance the entity about to get saved. Must not be {@literal null}.
* @param change the {@link MutableAggregateChange} that is going to get applied to the database. Must not be * @param change the {@link AggregateChange} that is going to get applied to the database. Must not be
* {@literal null}. * {@literal null}.
*/ */
public BeforeSaveEvent(E instance, AggregateChange<E> change) { public BeforeSaveEvent(E instance, AggregateChange<E> change) {

1
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/WithAggregateChange.java

@ -19,6 +19,7 @@ import org.springframework.data.relational.core.conversion.AggregateChange;
/** /**
* {@link RelationalEvent} that represents a change to an aggregate and therefore has an {@link AggregateChange} * {@link RelationalEvent} that represents a change to an aggregate and therefore has an {@link AggregateChange}
*
* @author Jens Schauder * @author Jens Schauder
*/ */
public interface WithAggregateChange<E> extends RelationalEvent<E> { public interface WithAggregateChange<E> extends RelationalEvent<E> {

6
spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityDeleteWriterUnitTests.java

@ -47,8 +47,7 @@ public class RelationalEntityDeleteWriterUnitTests {
SomeEntity entity = new SomeEntity(23L); SomeEntity entity = new SomeEntity(23L);
MutableAggregateChange<SomeEntity> aggregateChange = new MutableAggregateChange<>(AggregateChange.Kind.DELETE, MutableAggregateChange<SomeEntity> aggregateChange = MutableAggregateChange.forDelete(SomeEntity.class, entity);
SomeEntity.class, entity);
converter.write(entity.id, aggregateChange); converter.write(entity.id, aggregateChange);
@ -64,8 +63,7 @@ public class RelationalEntityDeleteWriterUnitTests {
@Test // DATAJDBC-188 @Test // DATAJDBC-188
public void deleteAllDeletesAllEntitiesAndReferencedEntities() { public void deleteAllDeletesAllEntitiesAndReferencedEntities() {
MutableAggregateChange<SomeEntity> aggregateChange = new MutableAggregateChange<>(AggregateChange.Kind.DELETE, MutableAggregateChange<SomeEntity> aggregateChange = MutableAggregateChange.forDelete(SomeEntity.class, null);
SomeEntity.class, null);
converter.write(null, aggregateChange); converter.write(null, aggregateChange);

4
spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityInsertWriterUnitTests.java

@ -45,7 +45,7 @@ public class RelationalEntityInsertWriterUnitTests {
SingleReferenceEntity entity = new SingleReferenceEntity(null); SingleReferenceEntity entity = new SingleReferenceEntity(null);
MutableAggregateChange<SingleReferenceEntity> aggregateChange = // MutableAggregateChange<SingleReferenceEntity> aggregateChange = //
new MutableAggregateChange(AggregateChange.Kind.SAVE, SingleReferenceEntity.class, entity); MutableAggregateChange.forSave(entity);
converter.write(entity, aggregateChange); converter.write(entity, aggregateChange);
@ -63,7 +63,7 @@ public class RelationalEntityInsertWriterUnitTests {
SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID); SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID);
MutableAggregateChange<SingleReferenceEntity> aggregateChange = // MutableAggregateChange<SingleReferenceEntity> aggregateChange = //
new MutableAggregateChange(AggregateChange.Kind.SAVE, SingleReferenceEntity.class, entity); MutableAggregateChange.forSave(entity);
converter.write(entity, aggregateChange); converter.write(entity, aggregateChange);

3
spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityUpdateWriterUnitTests.java

@ -45,8 +45,7 @@ public class RelationalEntityUpdateWriterUnitTests {
SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID); SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID);
MutableAggregateChange<RelationalEntityWriterUnitTests.SingleReferenceEntity> aggregateChange = // MutableAggregateChange<SingleReferenceEntity> aggregateChange = MutableAggregateChange.forSave(entity);
new MutableAggregateChange(AggregateChange.Kind.SAVE, SingleReferenceEntity.class, entity);
converter.write(entity, aggregateChange); converter.write(entity, aggregateChange);

40
spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java

@ -80,7 +80,7 @@ public class RelationalEntityWriterUnitTests {
SingleReferenceEntity entity = new SingleReferenceEntity(null); SingleReferenceEntity entity = new SingleReferenceEntity(null);
MutableAggregateChange<SingleReferenceEntity> aggregateChange = // MutableAggregateChange<SingleReferenceEntity> aggregateChange = //
new MutableAggregateChange<>(AggregateChange.Kind.SAVE, SingleReferenceEntity.class, entity); new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, SingleReferenceEntity.class, entity);
converter.write(entity, aggregateChange); converter.write(entity, aggregateChange);
@ -102,7 +102,7 @@ public class RelationalEntityWriterUnitTests {
entity.other = new Element(2L); entity.other = new Element(2L);
MutableAggregateChange<EmbeddedReferenceEntity> aggregateChange = // MutableAggregateChange<EmbeddedReferenceEntity> aggregateChange = //
new MutableAggregateChange<>(AggregateChange.Kind.SAVE, EmbeddedReferenceEntity.class, entity); new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, EmbeddedReferenceEntity.class, entity);
converter.write(entity, aggregateChange); converter.write(entity, aggregateChange);
@ -124,7 +124,7 @@ public class RelationalEntityWriterUnitTests {
entity.other = new Element(null); entity.other = new Element(null);
MutableAggregateChange<SingleReferenceEntity> aggregateChange = // MutableAggregateChange<SingleReferenceEntity> aggregateChange = //
new MutableAggregateChange<>(AggregateChange.Kind.SAVE, SingleReferenceEntity.class, entity); new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, SingleReferenceEntity.class, entity);
converter.write(entity, aggregateChange); converter.write(entity, aggregateChange);
@ -146,7 +146,7 @@ public class RelationalEntityWriterUnitTests {
SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID); SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID);
MutableAggregateChange<SingleReferenceEntity> aggregateChange = // MutableAggregateChange<SingleReferenceEntity> aggregateChange = //
new MutableAggregateChange<>(AggregateChange.Kind.SAVE, SingleReferenceEntity.class, entity); new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, SingleReferenceEntity.class, entity);
converter.write(entity, aggregateChange); converter.write(entity, aggregateChange);
@ -168,7 +168,7 @@ public class RelationalEntityWriterUnitTests {
SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID); SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID);
entity.other = new Element(null); entity.other = new Element(null);
MutableAggregateChange<SingleReferenceEntity> aggregateChange = new MutableAggregateChange<>( MutableAggregateChange<SingleReferenceEntity> aggregateChange = new DefaultAggregateChange<>(
AggregateChange.Kind.SAVE, SingleReferenceEntity.class, entity); AggregateChange.Kind.SAVE, SingleReferenceEntity.class, entity);
converter.write(entity, aggregateChange); converter.write(entity, aggregateChange);
@ -190,7 +190,7 @@ public class RelationalEntityWriterUnitTests {
public void newEntityWithEmptySetResultsInSingleInsert() { public void newEntityWithEmptySetResultsInSingleInsert() {
SetContainer entity = new SetContainer(null); SetContainer entity = new SetContainer(null);
MutableAggregateChange<SetContainer> aggregateChange = new MutableAggregateChange<>(AggregateChange.Kind.SAVE, MutableAggregateChange<SetContainer> aggregateChange = new DefaultAggregateChange<>(AggregateChange.Kind.SAVE,
SetContainer.class, entity); SetContainer.class, entity);
converter.write(entity, aggregateChange); converter.write(entity, aggregateChange);
@ -212,7 +212,7 @@ public class RelationalEntityWriterUnitTests {
entity.elements.add(new Element(null)); entity.elements.add(new Element(null));
entity.elements.add(new Element(null)); entity.elements.add(new Element(null));
MutableAggregateChange<SetContainer> aggregateChange = new MutableAggregateChange<>(AggregateChange.Kind.SAVE, MutableAggregateChange<SetContainer> aggregateChange = new DefaultAggregateChange<>(AggregateChange.Kind.SAVE,
SetContainer.class, entity); SetContainer.class, entity);
converter.write(entity, aggregateChange); converter.write(entity, aggregateChange);
@ -243,7 +243,7 @@ public class RelationalEntityWriterUnitTests {
new Element(null)) // new Element(null)) //
); );
MutableAggregateChange<CascadingReferenceEntity> aggregateChange = new MutableAggregateChange<>( MutableAggregateChange<CascadingReferenceEntity> aggregateChange = new DefaultAggregateChange<>(
AggregateChange.Kind.SAVE, CascadingReferenceEntity.class, entity); AggregateChange.Kind.SAVE, CascadingReferenceEntity.class, entity);
converter.write(entity, aggregateChange); converter.write(entity, aggregateChange);
@ -281,7 +281,7 @@ public class RelationalEntityWriterUnitTests {
new Element(null)) // new Element(null)) //
); );
MutableAggregateChange<CascadingReferenceEntity> aggregateChange = new MutableAggregateChange<>( MutableAggregateChange<CascadingReferenceEntity> aggregateChange = new DefaultAggregateChange<>(
AggregateChange.Kind.SAVE, CascadingReferenceEntity.class, entity); AggregateChange.Kind.SAVE, CascadingReferenceEntity.class, entity);
converter.write(entity, aggregateChange); converter.write(entity, aggregateChange);
@ -310,7 +310,7 @@ public class RelationalEntityWriterUnitTests {
public void newEntityWithEmptyMapResultsInSingleInsert() { public void newEntityWithEmptyMapResultsInSingleInsert() {
MapContainer entity = new MapContainer(null); MapContainer entity = new MapContainer(null);
MutableAggregateChange<MapContainer> aggregateChange = new MutableAggregateChange<>(AggregateChange.Kind.SAVE, MutableAggregateChange<MapContainer> aggregateChange = new DefaultAggregateChange<>(AggregateChange.Kind.SAVE,
MapContainer.class, entity); MapContainer.class, entity);
converter.write(entity, aggregateChange); converter.write(entity, aggregateChange);
@ -329,7 +329,7 @@ public class RelationalEntityWriterUnitTests {
entity.elements.put("one", new Element(null)); entity.elements.put("one", new Element(null));
entity.elements.put("two", new Element(null)); entity.elements.put("two", new Element(null));
MutableAggregateChange<MapContainer> aggregateChange = new MutableAggregateChange<>(AggregateChange.Kind.SAVE, MutableAggregateChange<MapContainer> aggregateChange = new DefaultAggregateChange<>(AggregateChange.Kind.SAVE,
MapContainer.class, entity); MapContainer.class, entity);
converter.write(entity, aggregateChange); converter.write(entity, aggregateChange);
@ -368,7 +368,7 @@ public class RelationalEntityWriterUnitTests {
entity.elements.put("a", new Element(null)); entity.elements.put("a", new Element(null));
entity.elements.put("b", new Element(null)); entity.elements.put("b", new Element(null));
MutableAggregateChange<MapContainer> aggregateChange = new MutableAggregateChange<>(AggregateChange.Kind.SAVE, MutableAggregateChange<MapContainer> aggregateChange = new DefaultAggregateChange<>(AggregateChange.Kind.SAVE,
MapContainer.class, entity); MapContainer.class, entity);
converter.write(entity, aggregateChange); converter.write(entity, aggregateChange);
@ -397,7 +397,7 @@ public class RelationalEntityWriterUnitTests {
public void newEntityWithEmptyListResultsInSingleInsert() { public void newEntityWithEmptyListResultsInSingleInsert() {
ListContainer entity = new ListContainer(null); ListContainer entity = new ListContainer(null);
MutableAggregateChange<ListContainer> aggregateChange = new MutableAggregateChange<>(AggregateChange.Kind.SAVE, MutableAggregateChange<ListContainer> aggregateChange = new DefaultAggregateChange<>(AggregateChange.Kind.SAVE,
ListContainer.class, entity); ListContainer.class, entity);
converter.write(entity, aggregateChange); converter.write(entity, aggregateChange);
@ -416,7 +416,7 @@ public class RelationalEntityWriterUnitTests {
entity.elements.add(new Element(null)); entity.elements.add(new Element(null));
entity.elements.add(new Element(null)); entity.elements.add(new Element(null));
MutableAggregateChange<ListContainer> aggregateChange = new MutableAggregateChange<>(AggregateChange.Kind.SAVE, MutableAggregateChange<ListContainer> aggregateChange = new DefaultAggregateChange<>(AggregateChange.Kind.SAVE,
ListContainer.class, entity); ListContainer.class, entity);
converter.write(entity, aggregateChange); converter.write(entity, aggregateChange);
@ -443,7 +443,7 @@ public class RelationalEntityWriterUnitTests {
MapContainer entity = new MapContainer(SOME_ENTITY_ID); MapContainer entity = new MapContainer(SOME_ENTITY_ID);
entity.elements.put("one", new Element(null)); entity.elements.put("one", new Element(null));
MutableAggregateChange<MapContainer> aggregateChange = new MutableAggregateChange<>(AggregateChange.Kind.SAVE, MutableAggregateChange<MapContainer> aggregateChange = new DefaultAggregateChange<>(AggregateChange.Kind.SAVE,
MapContainer.class, entity); MapContainer.class, entity);
converter.write(entity, aggregateChange); converter.write(entity, aggregateChange);
@ -466,7 +466,7 @@ public class RelationalEntityWriterUnitTests {
ListContainer entity = new ListContainer(SOME_ENTITY_ID); ListContainer entity = new ListContainer(SOME_ENTITY_ID);
entity.elements.add(new Element(null)); entity.elements.add(new Element(null));
MutableAggregateChange<ListContainer> aggregateChange = new MutableAggregateChange<>(AggregateChange.Kind.SAVE, MutableAggregateChange<ListContainer> aggregateChange = new DefaultAggregateChange<>(AggregateChange.Kind.SAVE,
ListContainer.class, entity); ListContainer.class, entity);
converter.write(entity, aggregateChange); converter.write(entity, aggregateChange);
@ -490,7 +490,7 @@ public class RelationalEntityWriterUnitTests {
listMapContainer.maps.add(new MapContainer(SOME_ENTITY_ID)); listMapContainer.maps.add(new MapContainer(SOME_ENTITY_ID));
listMapContainer.maps.get(0).elements.put("one", new Element(null)); listMapContainer.maps.get(0).elements.put("one", new Element(null));
MutableAggregateChange<ListMapContainer> aggregateChange = new MutableAggregateChange<>(AggregateChange.Kind.SAVE, MutableAggregateChange<ListMapContainer> aggregateChange = new DefaultAggregateChange<>(AggregateChange.Kind.SAVE,
ListMapContainer.class, listMapContainer); ListMapContainer.class, listMapContainer);
converter.write(listMapContainer, aggregateChange); converter.write(listMapContainer, aggregateChange);
@ -517,7 +517,7 @@ public class RelationalEntityWriterUnitTests {
listMapContainer.maps.add(new NoIdMapContainer()); listMapContainer.maps.add(new NoIdMapContainer());
listMapContainer.maps.get(0).elements.put("one", new NoIdElement()); listMapContainer.maps.get(0).elements.put("one", new NoIdElement());
MutableAggregateChange<NoIdListMapContainer> aggregateChange = new MutableAggregateChange<>( MutableAggregateChange<NoIdListMapContainer> aggregateChange = new DefaultAggregateChange<>(
AggregateChange.Kind.SAVE, NoIdListMapContainer.class, listMapContainer); AggregateChange.Kind.SAVE, NoIdListMapContainer.class, listMapContainer);
converter.write(listMapContainer, aggregateChange); converter.write(listMapContainer, aggregateChange);
@ -544,7 +544,7 @@ public class RelationalEntityWriterUnitTests {
// the embedded is null !!! // the embedded is null !!!
MutableAggregateChange<EmbeddedReferenceChainEntity> aggregateChange = // MutableAggregateChange<EmbeddedReferenceChainEntity> aggregateChange = //
new MutableAggregateChange<>(AggregateChange.Kind.SAVE, EmbeddedReferenceChainEntity.class, entity); new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, EmbeddedReferenceChainEntity.class, entity);
converter.write(entity, aggregateChange); converter.write(entity, aggregateChange);
@ -567,7 +567,7 @@ public class RelationalEntityWriterUnitTests {
// the embedded is null !!! // the embedded is null !!!
MutableAggregateChange<RootWithEmbeddedReferenceChainEntity> aggregateChange = // MutableAggregateChange<RootWithEmbeddedReferenceChainEntity> aggregateChange = //
new MutableAggregateChange<>(AggregateChange.Kind.SAVE, RootWithEmbeddedReferenceChainEntity.class, root); new DefaultAggregateChange<>(AggregateChange.Kind.SAVE, RootWithEmbeddedReferenceChainEntity.class, root);
converter.write(root, aggregateChange); converter.write(root, aggregateChange);

Loading…
Cancel
Save