2525import java .util .List ;
2626import java .util .Map ;
2727import java .util .Set ;
28+ import java .util .function .BiConsumer ;
2829
2930import org .springframework .data .mapping .PersistentProperty ;
3031import org .springframework .data .mapping .PersistentPropertyAccessor ;
3435import org .springframework .data .util .Pair ;
3536import org .springframework .lang .Nullable ;
3637import org .springframework .util .Assert ;
38+ import org .springframework .util .ClassUtils ;
3739
3840/**
3941 * Represents the change happening to the aggregate (as used in the context of Domain Driven Design) as a whole.
@@ -59,57 +61,101 @@ public AggregateChange(Kind kind, Class<T> entityType, @Nullable T entity) {
5961 this .entity = entity ;
6062 }
6163
64+ /**
65+ * Factory method to create an {@link AggregateChange} for saving entities.
66+ *
67+ * @param entity aggregate root to save.
68+ * @param <T> entity type.
69+ * @return the {@link AggregateChange} for saving the root {@code entity}.
70+ * @since 1.2
71+ */
72+ @ SuppressWarnings ("unchecked" )
73+ public static <T > AggregateChange <T > forSave (T entity ) {
74+
75+ Assert .notNull (entity , "Entity must not be null" );
76+ return new AggregateChange <>(Kind .SAVE , (Class <T >) ClassUtils .getUserClass (entity ), entity );
77+ }
78+
79+ /**
80+ * Factory method to create an {@link AggregateChange} for deleting entities.
81+ *
82+ * @param entity aggregate root to delete.
83+ * @param <T> entity type.
84+ * @return the {@link AggregateChange} for deleting the root {@code entity}.
85+ * @since 1.2
86+ */
87+ @ SuppressWarnings ("unchecked" )
88+ public static <T > AggregateChange <T > forDelete (T entity ) {
89+
90+ Assert .notNull (entity , "Entity must not be null" );
91+ return forDelete ((Class <T >) ClassUtils .getUserClass (entity ), entity );
92+ }
93+
94+ /**
95+ * Factory method to create an {@link AggregateChange} for deleting entities.
96+ *
97+ * @param entityClass aggregate root type.
98+ * @param entity aggregate root to delete.
99+ * @param <T> entity type.
100+ * @return the {@link AggregateChange} for deleting the root {@code entity}.
101+ * @since 1.2
102+ */
103+ public static <T > AggregateChange <T > forDelete (Class <T > entityClass , @ Nullable T entity ) {
104+
105+ Assert .notNull (entityClass , "Entity class must not be null" );
106+ return new AggregateChange <>(Kind .DELETE , entityClass , entity );
107+ }
108+
62109 public void setEntity (@ Nullable T aggregateRoot ) {
110+ // TODO: Check instanceOf compatibility to ensure type contract.
63111 entity = aggregateRoot ;
64112 }
65113
66114 public void executeWith (Interpreter interpreter , RelationalMappingContext context , RelationalConverter converter ) {
67115
68116 actions .forEach (action -> action .executeWith (interpreter ));
69117
70- T newRoot = setGeneratedIds (context , converter );
118+ T newRoot = populateIdsIfNecessary (context , converter );
71119
72120 if (newRoot != null ) {
73121 entity = newRoot ;
74122 }
75123 }
76124
77- @ SuppressWarnings ("unchecked" )
78125 @ Nullable
79- private T setGeneratedIds (RelationalMappingContext context , RelationalConverter converter ) {
126+ private T populateIdsIfNecessary (RelationalMappingContext context , RelationalConverter converter ) {
80127
81128 T newRoot = null ;
82129
83130 // have the actions so that the inserts on the leaves come first.
84131 ArrayList <DbAction <?>> reverseActions = new ArrayList <>(actions );
85132 Collections .reverse (reverseActions );
86133
87- CascadingValuesLookup cascadingValues = new CascadingValuesLookup ();
134+ StagedValues cascadingValues = new StagedValues ();
88135
89136 for (DbAction <?> action : reverseActions ) {
90137
91- if (action instanceof DbAction .WithGeneratedId ) {
92-
93- DbAction .WithGeneratedId <?> withGeneratedId = (DbAction .WithGeneratedId <?>) action ;
94- Object generatedId = withGeneratedId .getGeneratedId ();
138+ if (!(action instanceof DbAction .WithGeneratedId )) {
139+ continue ;
140+ }
95141
96- Object newEntity = setIdAndCascadingProperties (context , converter , withGeneratedId , generatedId ,
97- cascadingValues );
142+ DbAction .WithGeneratedId <?> withGeneratedId = (DbAction .WithGeneratedId <?>) action ;
143+ Object generatedId = withGeneratedId .getGeneratedId ();
144+ Object newEntity = setIdAndCascadingProperties (context , converter , withGeneratedId , generatedId , cascadingValues );
98145
99- // the id property was immutable so we have to propagate changes up the tree
100- if (newEntity != ((DbAction .WithGeneratedId <?>) action ).getEntity ()) {
146+ // the id property was immutable so we have to propagate changes up the tree
147+ if (newEntity != ((DbAction .WithGeneratedId <?>) action ).getEntity ()) {
101148
102- if (action instanceof DbAction .Insert ) {
103- DbAction .Insert insert = (DbAction .Insert ) action ;
149+ if (action instanceof DbAction .Insert ) {
150+ DbAction .Insert insert = (DbAction .Insert ) action ;
104151
105- Pair qualifier = insert .getQualifier ();
152+ Pair qualifier = insert .getQualifier ();
106153
107- cascadingValues .add (insert .dependingOn , insert .propertyPath , newEntity ,
108- qualifier == null ? null : qualifier .getSecond ());
154+ cascadingValues .stage (insert .dependingOn , insert .propertyPath ,
155+ qualifier == null ? null : qualifier .getSecond (), newEntity );
109156
110- } else if (action instanceof DbAction .InsertRoot ) {
111- newRoot = (T ) newEntity ;
112- }
157+ } else if (action instanceof DbAction .InsertRoot ) {
158+ newRoot = entityType .cast (newEntity );
113159 }
114160 }
115161 }
@@ -119,7 +165,7 @@ private T setGeneratedIds(RelationalMappingContext context, RelationalConverter
119165
120166 @ SuppressWarnings ("unchecked" )
121167 private <S > Object setIdAndCascadingProperties (RelationalMappingContext context , RelationalConverter converter ,
122- DbAction .WithGeneratedId <S > action , @ Nullable Object generatedId , CascadingValuesLookup cascadingValues ) {
168+ DbAction .WithGeneratedId <S > action , @ Nullable Object generatedId , StagedValues cascadingValues ) {
123169
124170 S originalEntity = action .getEntity ();
125171
@@ -132,19 +178,14 @@ private <S> Object setIdAndCascadingProperties(RelationalMappingContext context,
132178 }
133179
134180 // set values of changed immutables referenced by this entity
135- Map <PersistentPropertyPath , Object > cascadingValue = cascadingValues .get (action );
136- for (Map .Entry <PersistentPropertyPath , Object > pathValuePair : cascadingValue .entrySet ()) {
137- propertyAccessor .setProperty (getRelativePath (action , pathValuePair ), pathValuePair .getValue ());
138- }
181+ cascadingValues .forEachPath (action , (persistentPropertyPath , o ) -> propertyAccessor
182+ .setProperty (getRelativePath (action , persistentPropertyPath ), o ));
139183
140184 return propertyAccessor .getBean ();
141185 }
142186
143187 @ SuppressWarnings ("unchecked" )
144- private PersistentPropertyPath getRelativePath (DbAction action ,
145- Map .Entry <PersistentPropertyPath , Object > pathValuePair ) {
146-
147- PersistentPropertyPath pathToValue = pathValuePair .getKey ();
188+ private PersistentPropertyPath getRelativePath (DbAction action , PersistentPropertyPath pathToValue ) {
148189
149190 if (action instanceof DbAction .Insert ) {
150191 return pathToValue .getExtensionForBaseOf (((DbAction .Insert ) action ).propertyPath );
@@ -178,12 +219,13 @@ public enum Kind {
178219 }
179220
180221 /**
181- * Gathers and holds information about immutable properties in an aggregate that need updating.
222+ * Accumulates information about staged immutable objects in an aggregate that require updating because their state
223+ * changed because of {@link DbAction} execution.
182224 */
183- private static class CascadingValuesLookup {
225+ private static class StagedValues {
184226
185- static final List <MultiValueAggregator > aggregators = Arrays .asList (new SetAggregator (), new MapAggregator () ,
186- new ListAggregator (), new SingleElementAggregator () );
227+ static final List <MultiValueAggregator > aggregators = Arrays .asList (SetAggregator . INSTANCE , MapAggregator . INSTANCE ,
228+ ListAggregator . INSTANCE , SingleElementAggregator . INSTANCE );
187229
188230 Map <DbAction , Map <PersistentPropertyPath , Object >> values = new HashMap <>();
189231
@@ -194,27 +236,22 @@ private static class CascadingValuesLookup {
194236 * @param action The action responsible for persisting the entity that needs the added value set. Must not be
195237 * {@literal null}.
196238 * @param path The path to the property in which to set the value. Must not be {@literal null}.
197- * @param value The value to be set. Must not be {@literal null}.
198239 * @param qualifier If {@code path} is a qualified multivalued properties this parameter contains the qualifier. May
199240 * be {@literal null}.
241+ * @param value The value to be set. Must not be {@literal null}.
200242 */
201243 @ SuppressWarnings ("unchecked" )
202- public <T > void add (DbAction <?> action , PersistentPropertyPath path , Object value , @ Nullable Object qualifier ) {
244+ <T > void stage (DbAction <?> action , PersistentPropertyPath path , @ Nullable Object qualifier , Object value ) {
203245
204246 MultiValueAggregator <T > aggregator = getAggregatorFor (path );
205247
206- Map <PersistentPropertyPath , Object > valuesForPath = this .values .get (action );
207- if (valuesForPath == null ) {
208- valuesForPath = new HashMap <>();
209- values .put (action , valuesForPath );
210- }
248+ Map <PersistentPropertyPath , Object > valuesForPath = this .values .computeIfAbsent (action ,
249+ dbAction -> new HashMap <>());
211250
212- T currentValue = (T ) valuesForPath .get (path );
213- if (currentValue == null ) {
214- currentValue = aggregator .createEmptyInstance ();
215- }
251+ T currentValue = (T ) valuesForPath .computeIfAbsent (path ,
252+ persistentPropertyPath -> aggregator .createEmptyInstance ());
216253
217- Object newValue = aggregator .add (currentValue , value , qualifier );
254+ Object newValue = aggregator .add (currentValue , qualifier , value );
218255
219256 valuesForPath .put (path , newValue );
220257 }
@@ -231,8 +268,17 @@ private MultiValueAggregator getAggregatorFor(PersistentPropertyPath path) {
231268 throw new IllegalStateException (String .format ("Can't handle path %s" , path ));
232269 }
233270
234- public Map <PersistentPropertyPath , Object > get (DbAction <?> action ) {
235- return values .getOrDefault (action , Collections .emptyMap ());
271+ /**
272+ * Performs the given action for each entry in this the staging area that are provided by {@link DbAction} until all
273+ * {@link PersistentPropertyPath} have been processed or the action throws an exception. The {@link BiConsumer
274+ * action} is called with each applicable {@link PersistentPropertyPath} and {@code value} that is assignable to the
275+ * property.
276+ *
277+ * @param dbAction
278+ * @param action
279+ */
280+ void forEachPath (DbAction <?> dbAction , BiConsumer <PersistentPropertyPath , Object > action ) {
281+ values .getOrDefault (dbAction , Collections .emptyMap ()).forEach (action );
236282 }
237283 }
238284
@@ -249,11 +295,13 @@ default boolean handles(PersistentProperty property) {
249295 @ Nullable
250296 T createEmptyInstance ();
251297
252- T add (@ Nullable T aggregate , Object value , @ Nullable Object qualifier );
298+ T add (@ Nullable T aggregate , @ Nullable Object qualifier , Object value );
253299
254300 }
255301
256- static private class SetAggregator implements MultiValueAggregator <Set > {
302+ private enum SetAggregator implements MultiValueAggregator <Set > {
303+
304+ INSTANCE ;
257305
258306 @ Override
259307 public Class <Set > handledType () {
@@ -267,7 +315,7 @@ public Set createEmptyInstance() {
267315
268316 @ SuppressWarnings ("unchecked" )
269317 @ Override
270- public Set add (@ Nullable Set set , Object value , @ Nullable Object qualifier ) {
318+ public Set add (@ Nullable Set set , @ Nullable Object qualifier , Object value ) {
271319
272320 Assert .notNull (set , "Set must not be null" );
273321
@@ -276,7 +324,9 @@ public Set add(@Nullable Set set, Object value, @Nullable Object qualifier) {
276324 }
277325 }
278326
279- static private class ListAggregator implements MultiValueAggregator <List > {
327+ private enum ListAggregator implements MultiValueAggregator <List > {
328+
329+ INSTANCE ;
280330
281331 @ Override
282332 public boolean handles (PersistentProperty property ) {
@@ -290,7 +340,7 @@ public List createEmptyInstance() {
290340
291341 @ SuppressWarnings ("unchecked" )
292342 @ Override
293- public List add (@ Nullable List list , Object value , @ Nullable Object qualifier ) {
343+ public List add (@ Nullable List list , @ Nullable Object qualifier , Object value ) {
294344
295345 Assert .notNull (list , "List must not be null." );
296346
@@ -305,7 +355,9 @@ public List add(@Nullable List list, Object value, @Nullable Object qualifier) {
305355 }
306356 }
307357
308- static private class MapAggregator implements MultiValueAggregator <Map > {
358+ private enum MapAggregator implements MultiValueAggregator <Map > {
359+
360+ INSTANCE ;
309361
310362 @ Override
311363 public Class <Map > handledType () {
@@ -319,7 +371,7 @@ public Map createEmptyInstance() {
319371
320372 @ SuppressWarnings ("unchecked" )
321373 @ Override
322- public Map add (@ Nullable Map map , Object value , @ Nullable Object qualifier ) {
374+ public Map add (@ Nullable Map map , @ Nullable Object qualifier , Object value ) {
323375
324376 Assert .notNull (map , "Map must not be null." );
325377
@@ -328,7 +380,9 @@ public Map add(@Nullable Map map, Object value, @Nullable Object qualifier) {
328380 }
329381 }
330382
331- static private class SingleElementAggregator implements MultiValueAggregator <Object > {
383+ private enum SingleElementAggregator implements MultiValueAggregator <Object > {
384+
385+ INSTANCE ;
332386
333387 @ Override
334388 @ Nullable
@@ -337,7 +391,7 @@ public Object createEmptyInstance() {
337391 }
338392
339393 @ Override
340- public Object add (@ Nullable Object __null , Object value , @ Nullable Object qualifier ) {
394+ public Object add (@ Nullable Object __null , @ Nullable Object qualifier , Object value ) {
341395 return value ;
342396 }
343397 }
0 commit comments