public static AttributeMap mergeAttributeMaps(final AttributeMap baseAttributeMap,
                                                  final Consumer<AttributeMap> attributeMapWriter) {

        Objects.requireNonNull(attributeMapWriter);
        // Add the meta from headers first, then read the entry meta on top,
        // so the values from the headers act as a fallback
        final AttributeMap outputAttributeMap =
                AttributeMapUtil.cloneAllowable(baseAttributeMap);

        // Now write attributes on top.
        // defaultFeedName/defaultTypeName are in attributeMap, so act as fallbacks
        // unless they are explicitly set to null in the .meta
        attributeMapWriter.accept(outputAttributeMap);

        // attributeMap contains the receiptId generated when we received this zip, so we
        // need to set/append it to each meta in the zip
        final String receiptId = baseAttributeMap.get(StandardHeaderArguments.RECEIPT_ID);
        AttributeMapUtil.setAndAppendReceiptId(outputAttributeMap, receiptId);

        // This value was set by ProxyRequestHandler, so we trust the format, thus don't need
        // to worry about normalising the date format
        final String receiptTimeStr = baseAttributeMap.get(StandardHeaderArguments.RECEIVED_TIME);
        if (NullSafe.isNonBlankString(receiptTimeStr)) {
            outputAttributeMap.put(StandardHeaderArguments.RECEIVED_TIME, receiptTimeStr);
            outputAttributeMap.appendItemIfDifferent(StandardHeaderArguments.RECEIVED_TIME_HISTORY, receiptTimeStr);
        }

        final List<String> receivedPathItems = baseAttributeMap.getAsList(StandardHeaderArguments.RECEIVED_PATH);
        if (!receivedPathItems.isEmpty()) {
            outputAttributeMap.appendItemIfDifferent(
                    StandardHeaderArguments.RECEIVED_PATH,
                    receivedPathItems.getLast());
        }

        LOGGER.debug("""
                mergeAttributeMaps()
                baseAttributeMap: {}
                outputAttributeMap: {}""", baseAttributeMap, outputAttributeMap);

        return outputAttributeMap;
    }
Creates a new {@link AttributeMap} that is initially populated with copies of the attributes
in baseAttributeMap (that are allowed to be cloned, i.e. not security ones).
{@code attributeMapWriter} is then called to write any attributes that need to be merged in
on top, i.e. from a ZIP .meta entry. Thus, any attributes (with a value or explicitly set to null)
set by attributeMapWriter will trump those in baseAttributeMap.
<p>
After the above, it will set/append the following attributes using values from baseAttributeMap.
This is because the values for these attributes will be more up-to-date in attributeMapWriter than
in baseAttributeMap.
<ul>
<li>ReceiptId</li>
<li>ReceiptIdPath</li>
<li>ReceivedTime</li>
<li>ReceivedTimeHistory</li>
<li>ReceivedPath</li>
</ul>
</p>
<p>
Assumes that ReceiptId, ReceivedTime and ReceivedPath have all been set in baseAttributeMap
on receipt, i.e. these are the latest values for these attributes.
</p>

@return A new {@link AttributeMap} instance containing the merged attributes.
stroom-meta/stroom-meta-api/src/main/java/stroom/meta/api/AttributeMapUtil.java