2828import java .util .Map .Entry ;
2929import java .util .Objects ;
3030import java .util .TreeSet ;
31+ import java .util .regex .Matcher ;
32+ import java .util .regex .Pattern ;
3133import java .util .stream .Collectors ;
3234
3335import org .apache .jackrabbit .oak .commons .json .JsonObject ;
@@ -270,6 +272,9 @@ public static String getStringOrStringFromSingleValueArray(String value) {
270272
271273 private static JsonObject mergeChild (String path , String child , int level , JsonObject ancestor , JsonObject custom , JsonObject product ,
272274 ArrayList <String > conflicts ) {
275+ if (level == 1 && path .indexOf ("/aggregates/" ) >= 0 ) {
276+ return mergeAggregates (path , child , level , ancestor , custom , product , conflicts );
277+ }
273278 JsonObject a = ancestor .getChildren ().get (child );
274279 JsonObject c = custom .getChildren ().get (child );
275280 JsonObject p = product .getChildren ().get (child );
@@ -287,6 +292,81 @@ private static JsonObject mergeChild(String path, String child, int level, JsonO
287292 }
288293 }
289294
295+ private static JsonObject mergeAggregates (String path , String child , int level , JsonObject ancestor , JsonObject custom , JsonObject product ,
296+ ArrayList <String > conflicts ) {
297+
298+ // merge, with level + 1 so that we don't recurse into this function again
299+ // conflicts are redirected to a new, temporary list
300+ ArrayList <String > aggregateConflicts = new ArrayList <>();
301+ JsonObject merged = mergeChild (path , child , level + 1 , ancestor , custom , product , aggregateConflicts );
302+
303+ // if there were conflicts, resolve them
304+ if (!aggregateConflicts .isEmpty ()) {
305+
306+ // list of "include" elements to move to the end
307+ ArrayList <JsonObject > elementToMove = new ArrayList <>();
308+
309+ // which is the next id for "include" (eg. 12)
310+ long nextIncludeId = getNextIncludeId (ancestor .getChildren ().get (child ));
311+ nextIncludeId = Math .max (nextIncludeId , getNextIncludeId (custom .getChildren ().get (child )));
312+ nextIncludeId = Math .max (nextIncludeId , getNextIncludeId (product .getChildren ().get (child )));
313+
314+ // loop over conflicts, and find + remove these
315+ // the aggregateConflicts will contain entries that look like this:
316+ // "Could not merge value; path=/oak:index/assets-11/aggregates/asset/include11
317+ // property=path; ancestor=null; custom=...; product=..."
318+ // and we need to extract the path
319+ for (String n : aggregateConflicts ) {
320+ String regex = "path=([^\\ s]+)\\ sproperty=" ;
321+ Pattern pattern = Pattern .compile (regex );
322+ Matcher matcher = pattern .matcher (n );
323+ if (matcher .find ()) {
324+ // the path of the conflicting aggregation node
325+ String extractedPath = matcher .group (1 );
326+ String [] elements = extractedPath .split ("/" );
327+ String conflictElement = elements [elements .length - 1 ];
328+
329+ // remove from the custom list
330+ JsonObject conflict = custom .getChildren ().get (child ).getChildren ().remove (conflictElement );
331+
332+ // remember the element, to put it back later
333+ elementToMove .add (conflict );
334+ }
335+ }
336+
337+ // merge again, with conflicts resolved now
338+ // (if there are other conflicts unrelated to aggregation,
339+ // those will not be resolved)
340+ merged = mergeChild (path , child , level + 1 , ancestor , custom , product , conflicts );
341+
342+ // add the aggregation conflict at the end, with new ids
343+ // first we need to clone the merged object,
344+ // because it might be the same object as the product currently
345+ merged = JsonObject .fromJson (merged .toString (), true );
346+ for (JsonObject json : elementToMove ) {
347+ merged .getChildren ().put ("include" + nextIncludeId , json );
348+ nextIncludeId ++;
349+ }
350+ }
351+ return merged ;
352+ }
353+
354+ private static long getNextIncludeId (JsonObject json ) {
355+ long max = 0 ;
356+ for (String n : json .getChildren ().keySet ()) {
357+ if (n .startsWith ("include" )) {
358+ n = n .substring ("include" .length ());
359+ try {
360+ long id = Long .parseLong (n );
361+ max = Math .max (max , id );
362+ } catch (NumberFormatException e ) {
363+ // ignore
364+ }
365+ }
366+ }
367+ return max + 1 ;
368+ }
369+
290370 private static boolean isSameJson (JsonObject a , JsonObject b ) {
291371 if (a == null || b == null ) {
292372 return a == null && b == null ;
0 commit comments