This is an automated email from the ASF dual-hosted git repository.

exceptionfactory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new 1d119f89b7d NIFI-16280 Pass component and Connector context through 
repository APIs (#11613)
1d119f89b7d is described below

commit 1d119f89b7d7253c6306a8d235a35efd86470cb6
Author: Mark Payne <[email protected]>
AuthorDate: Thu Sep 3 13:15:50 2026 -0400

    NIFI-16280 Pass component and Connector context through repository APIs 
(#11613)
    
    Session-driven content creates and FlowFile updates now carry component,
    Connector, and loss-tolerance context. The Content Repository reports its
    own appendable-claim size. Implements NIP-40.
    
    
    Signed-off-by: David Handermann <[email protected]>
---
 .../src/main/asciidoc/administration-guide.adoc    |   4 +-
 .../repository/ContentClaimCreationContext.java    |  27 ++---
 .../controller/repository/ContentRepository.java   |  20 ++++
 .../controller/repository/FlowFileRepository.java  |   8 ++
 .../repository/FlowFileUpdateContext.java          |  31 +++---
 .../nifi/controller/repository/LossTolerance.java  |  32 +++---
 .../StandardContentClaimCreationContext.java       |  47 +++++++++
 .../repository/StandardFlowFileUpdateContext.java  |  31 +++---
 .../repository/io/ContentClaimInputStream.java     |   5 +-
 .../repository/io/ContentClaimOutputStream.java    |   0
 .../repository/metrics/PerformanceTracker.java     |   0
 .../metrics/PerformanceTrackingInputStream.java    |   0
 .../repository/AbstractRepositoryContext.java      |  17 ++++
 .../controller/repository/RepositoryContext.java   |   4 +
 .../repository/StandardProcessSession.java         |  16 +--
 .../apache/nifi/groups/StandardProcessGroup.java   |  28 +++---
 .../nifi/groups/StandardProcessGroupTest.java      |  52 +++++++++-
 .../java/org/apache/nifi/groups/ProcessGroup.java  |  29 +++---
 .../org/apache/nifi/controller/FlowController.java |  14 +--
 .../repository/FileSystemRepository.java           |   5 +
 .../repository/NonPurgeableContentRepository.java  |  10 ++
 .../repository/StandardRepositoryContext.java      |   8 +-
 .../claim/StandardContentClaimWriteCache.java      |  11 ++-
 .../scheduling/RepositoryContextFactory.java       |   8 +-
 .../NonPurgeableContentRepositoryTest.java         |  60 ++++++++++++
 .../repository/StandardProcessSessionIT.java       | 109 ++++++++++++++++++++-
 .../claim/TestStandardContentClaimWriteCache.java  | 100 ++++++++++++++-----
 .../StatelessContentClaimWriteCache.java           |   7 +-
 .../repository/StatelessRepositoryContext.java     |   2 +-
 29 files changed, 531 insertions(+), 154 deletions(-)

diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc 
b/nifi-docs/src/main/asciidoc/administration-guide.adoc
index df67ba22a1e..5836a0d8b94 100644
--- a/nifi-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc
@@ -3059,7 +3059,9 @@ FlowFile Repository, if also on that disk, could become 
corrupt. To avoid this s
 individual FlowFile as a separate file in the content repository. Doing so 
would be very detrimental to performance, if each 120 byte FlowFile, for 
instance, was written to its own file. Instead,
 we continue writing to the same file until it reaches some threshold. This 
property configures that threshold. Setting the value too small can result in 
poor performance due to reading from and
 writing to too many files. However, a file can only be deleted from the 
content repository once there are no longer any FlowFiles pointing to it. 
Therefore, setting the value too large can result
-in data remaining in the content repository for much longer, potentially 
leading to the content repository running out of disk space. The default value 
is `50 KB`.
+in data remaining in the content repository for much longer, potentially 
leading to the content repository running out of disk space. The default value 
is `50 KB`. Each Content Repository
+implementation reports the threshold that it wants the framework to use, so 
this property is honored only by the File System Content Repository. An 
alternate implementation may report a different
+threshold and ignore this property.
 |`nifi.content.repository.directory.default`*|The location of the Content 
Repository. The default value is `./content_repository`. +
 +
 *NOTE*: Multiple content repositories can be specified by using the 
`nifi.content.repository.directory.` prefix with unique suffixes and separate 
paths as values. +
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/ContentClaimOutputStream.java
 
b/nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/ContentClaimCreationContext.java
similarity index 58%
copy from 
nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/ContentClaimOutputStream.java
copy to 
nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/ContentClaimCreationContext.java
index 3c4d7785f00..09a6356dc18 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/ContentClaimOutputStream.java
+++ 
b/nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/ContentClaimCreationContext.java
@@ -14,24 +14,27 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-package org.apache.nifi.controller.repository.io;
+package org.apache.nifi.controller.repository;
 
 import org.apache.nifi.controller.repository.claim.ContentClaim;
 
-import java.io.IOException;
-import java.io.OutputStream;
+/**
+ * Identifies the component creating a {@link ContentClaim} and that 
component's Connector and loss tolerance.
+ */
+public interface ContentClaimCreationContext {
 
-public abstract class ContentClaimOutputStream extends OutputStream {
+    /**
+     * @return the identifier of the component creating the claim; never 
{@code null}
+     */
+    String getComponentIdentifier();
 
     /**
-     * Creates a new Content Claim that is backed by this OutputStream. This 
allows the caller to
-     * create a new Content Claim but ensure that they keep writing to the 
same OutputStream, which can
-     * significantly improve performance.
-     *
-     * @return a new ContentClaim
-     * @throws IOException if unable to finalize the current ContentClaim or 
create a new one
+     * @return the owning Connector identifier, or {@code null} if the 
component is not in a Connector-managed flow
      */
-    public abstract ContentClaim newContentClaim() throws IOException;
+    String getConnectorIdentifier();
 
+    /**
+     * @return the loss tolerance of the content being written; never {@code 
null}
+     */
+    LossTolerance getLossTolerance();
 }
diff --git 
a/nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/ContentRepository.java
 
b/nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/ContentRepository.java
index 3327f7a36ba..cae8cb8d65f 100644
--- 
a/nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/ContentRepository.java
+++ 
b/nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/ContentRepository.java
@@ -33,6 +33,11 @@ import java.util.Set;
  */
 public interface ContentRepository {
 
+    /**
+     * Default for {@link #getMaxAppendableClaimBytes()}, matching {@code 
nifi.content.claim.max.appendable.size}.
+     */
+    long DEFAULT_MAX_APPENDABLE_CLAIM_BYTES = 51_200L;
+
     /**
      * Initializes the Content Repository, providing to it the
      * ContentRepositoryContext.
@@ -94,6 +99,21 @@ public interface ContentRepository {
      */
     ContentClaim create(boolean lossTolerant) throws IOException;
 
+    /**
+     * Creates a new content claim for the given context. The default 
implementation delegates to {@link #create(boolean)}.
+     */
+    default ContentClaim create(final ContentClaimCreationContext context) 
throws IOException {
+        return create(context.getLossTolerance().isLossTolerant());
+    }
+
+    /**
+     * Returns how many bytes may be written to one Content Claim before the 
framework creates a new one.
+     * Implementations must return an in-memory value; I/O is not allowed. The 
value may change at runtime.
+     */
+    default long getMaxAppendableClaimBytes() {
+        return DEFAULT_MAX_APPENDABLE_CLAIM_BYTES;
+    }
+
     /**
      * Increments the number of claimants for the given claim
      *
diff --git 
a/nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/FlowFileRepository.java
 
b/nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/FlowFileRepository.java
index 6521ba08b61..0520111f0ca 100644
--- 
a/nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/FlowFileRepository.java
+++ 
b/nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/FlowFileRepository.java
@@ -76,6 +76,14 @@ public interface FlowFileRepository extends Closeable {
      */
     void updateRepository(Collection<RepositoryRecord> records) throws 
IOException;
 
+    /**
+     * Updates the repository with the given records for the given context. 
The default implementation delegates to
+     * {@link #updateRepository(Collection)}.
+     */
+    default void updateRepository(final Collection<RepositoryRecord> records, 
final FlowFileUpdateContext context) throws IOException {
+        updateRepository(records);
+    }
+
     /**
      * Loads all FlowFiles found within the repository, establishes the content
      * claims and their reference count
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/metrics/PerformanceTracker.java
 
b/nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/FlowFileUpdateContext.java
similarity index 60%
copy from 
nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/metrics/PerformanceTracker.java
copy to 
nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/FlowFileUpdateContext.java
index 822baab41ff..f8a60526a88 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/metrics/PerformanceTracker.java
+++ 
b/nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/FlowFileUpdateContext.java
@@ -14,25 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.nifi.controller.repository;
 
-package org.apache.nifi.controller.repository.metrics;
-
-public interface PerformanceTracker {
-    void beginContentRead();
-
-    void endContentRead();
-
-    long getContentReadNanos();
-
-    void beginContentWrite();
-
-    void endContentWrite();
-
-    long getContentWriteNanos();
-
-    void beginSessionCommit();
+/**
+ * Identifies the component whose session is writing {@link RepositoryRecord}s 
to a {@link FlowFileRepository}.
+ */
+public interface FlowFileUpdateContext {
 
-    void endSessionCommit();
+    /**
+     * @return the identifier of the component whose session is updating the 
repository; never {@code null}
+     */
+    String getComponentIdentifier();
 
-    long getSessionCommitNanos();
+    /**
+     * @return the owning Connector identifier, or {@code null} if the 
component is not in a Connector-managed flow
+     */
+    String getConnectorIdentifier();
 }
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/metrics/PerformanceTracker.java
 
b/nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/LossTolerance.java
similarity index 58%
copy from 
nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/metrics/PerformanceTracker.java
copy to 
nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/LossTolerance.java
index 822baab41ff..7e1104d85a1 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/metrics/PerformanceTracker.java
+++ 
b/nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/LossTolerance.java
@@ -14,25 +14,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.nifi.controller.repository;
 
-package org.apache.nifi.controller.repository.metrics;
-
-public interface PerformanceTracker {
-    void beginContentRead();
-
-    void endContentRead();
-
-    long getContentReadNanos();
-
-    void beginContentWrite();
-
-    void endContentWrite();
-
-    long getContentWriteNanos();
+/**
+ * Indicates whether data may be lost without significant consequence. A 
repository is free to trade durability for
+ * performance when handling data that is marked as {@link #LOSS_TOLERANT}, 
for example by using more volatile storage.
+ */
+public enum LossTolerance {
+    LOSS_TOLERANT(true),
+    LOSS_INTOLERANT(false);
 
-    void beginSessionCommit();
+    private final boolean lossTolerant;
 
-    void endSessionCommit();
+    LossTolerance(final boolean lossTolerant) {
+        this.lossTolerant = lossTolerant;
+    }
 
-    long getSessionCommitNanos();
+    public boolean isLossTolerant() {
+        return lossTolerant;
+    }
 }
diff --git 
a/nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/StandardContentClaimCreationContext.java
 
b/nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/StandardContentClaimCreationContext.java
new file mode 100644
index 00000000000..63c21cdc939
--- /dev/null
+++ 
b/nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/StandardContentClaimCreationContext.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *     http://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.apache.nifi.controller.repository;
+
+import java.util.Objects;
+
+public class StandardContentClaimCreationContext implements 
ContentClaimCreationContext {
+
+    private final String componentIdentifier;
+    private final String connectorIdentifier;
+    private final LossTolerance lossTolerance;
+
+    public StandardContentClaimCreationContext(final String 
componentIdentifier, final String connectorIdentifier, final LossTolerance 
lossTolerance) {
+        this.componentIdentifier = Objects.requireNonNull(componentIdentifier, 
"Component Identifier is required");
+        this.connectorIdentifier = connectorIdentifier;
+        this.lossTolerance = Objects.requireNonNull(lossTolerance, "Loss 
Tolerance is required");
+    }
+
+    @Override
+    public String getComponentIdentifier() {
+        return componentIdentifier;
+    }
+
+    @Override
+    public String getConnectorIdentifier() {
+        return connectorIdentifier;
+    }
+
+    @Override
+    public LossTolerance getLossTolerance() {
+        return lossTolerance;
+    }
+}
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/ContentClaimOutputStream.java
 
b/nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/StandardFlowFileUpdateContext.java
similarity index 52%
copy from 
nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/ContentClaimOutputStream.java
copy to 
nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/StandardFlowFileUpdateContext.java
index 3c4d7785f00..f6787ef6ab0 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/ContentClaimOutputStream.java
+++ 
b/nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/StandardFlowFileUpdateContext.java
@@ -14,24 +14,27 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.nifi.controller.repository;
 
-package org.apache.nifi.controller.repository.io;
+import java.util.Objects;
 
-import org.apache.nifi.controller.repository.claim.ContentClaim;
+public class StandardFlowFileUpdateContext implements FlowFileUpdateContext {
 
-import java.io.IOException;
-import java.io.OutputStream;
+    private final String componentIdentifier;
+    private final String connectorIdentifier;
 
-public abstract class ContentClaimOutputStream extends OutputStream {
+    public StandardFlowFileUpdateContext(final String componentIdentifier, 
final String connectorIdentifier) {
+        this.componentIdentifier = Objects.requireNonNull(componentIdentifier, 
"Component Identifier is required");
+        this.connectorIdentifier = connectorIdentifier;
+    }
 
-    /**
-     * Creates a new Content Claim that is backed by this OutputStream. This 
allows the caller to
-     * create a new Content Claim but ensure that they keep writing to the 
same OutputStream, which can
-     * significantly improve performance.
-     *
-     * @return a new ContentClaim
-     * @throws IOException if unable to finalize the current ContentClaim or 
create a new one
-     */
-    public abstract ContentClaim newContentClaim() throws IOException;
+    @Override
+    public String getComponentIdentifier() {
+        return componentIdentifier;
+    }
 
+    @Override
+    public String getConnectorIdentifier() {
+        return connectorIdentifier;
+    }
 }
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/ContentClaimInputStream.java
 
b/nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/io/ContentClaimInputStream.java
similarity index 97%
rename from 
nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/ContentClaimInputStream.java
rename to 
nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/io/ContentClaimInputStream.java
index bfb9f5d651f..3c129890b9d 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/ContentClaimInputStream.java
+++ 
b/nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/io/ContentClaimInputStream.java
@@ -20,7 +20,6 @@ import 
org.apache.nifi.controller.repository.ContentRepository;
 import org.apache.nifi.controller.repository.claim.ContentClaim;
 import org.apache.nifi.controller.repository.metrics.PerformanceTracker;
 import 
org.apache.nifi.controller.repository.metrics.PerformanceTrackingInputStream;
-import org.apache.nifi.stream.io.StreamUtils;
 
 import java.io.BufferedInputStream;
 import java.io.IOException;
@@ -215,7 +214,7 @@ public class ContentClaimInputStream extends InputStream {
 
             performanceTracker.beginContentRead();
             try {
-                StreamUtils.skip(delegate, markOffset - claimOffset);
+                delegate.skipNBytes(markOffset - claimOffset);
             } finally {
                 performanceTracker.endContentRead();
             }
@@ -243,7 +242,7 @@ public class ContentClaimInputStream extends InputStream {
         performanceTracker.beginContentRead();
         try {
             delegate = new 
PerformanceTrackingInputStream(contentRepository.read(contentClaim), 
performanceTracker);
-            StreamUtils.skip(delegate, claimOffset);
+            delegate.skipNBytes(claimOffset);
             currentOffset = claimOffset;
         } finally {
             performanceTracker.endContentRead();
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/ContentClaimOutputStream.java
 
b/nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/io/ContentClaimOutputStream.java
similarity index 100%
rename from 
nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/ContentClaimOutputStream.java
rename to 
nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/io/ContentClaimOutputStream.java
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/metrics/PerformanceTracker.java
 
b/nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/metrics/PerformanceTracker.java
similarity index 100%
rename from 
nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/metrics/PerformanceTracker.java
rename to 
nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/metrics/PerformanceTracker.java
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/metrics/PerformanceTrackingInputStream.java
 
b/nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/metrics/PerformanceTrackingInputStream.java
similarity index 100%
rename from 
nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/metrics/PerformanceTrackingInputStream.java
rename to 
nifi-framework-api/src/main/java/org/apache/nifi/controller/repository/metrics/PerformanceTrackingInputStream.java
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/AbstractRepositoryContext.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/AbstractRepositoryContext.java
index f92390352ae..8430a77f957 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/AbstractRepositoryContext.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/AbstractRepositoryContext.java
@@ -56,6 +56,8 @@ public abstract class AbstractRepositoryContext implements 
RepositoryContext {
     private final AtomicLong connectionIndex;
     private final StateManager stateManager;
     private final ComponentMetricContext componentMetricContext;
+    private final ContentClaimCreationContext contentClaimCreationContext;
+    private final FlowFileUpdateContext flowFileUpdateContext;
 
     private final String componentNameCounterContext;
     private final String componentTypeCounterContext;
@@ -89,6 +91,11 @@ public abstract class AbstractRepositoryContext implements 
RepositoryContext {
 
         this.componentNameCounterContext = connectable.getName() + " (" + 
connectable.getIdentifier() + ")";
         this.componentTypeCounterContext = "All " + 
connectable.getComponentType() + "'s";
+
+        final String connectorIdentifier = 
connectable.getProcessGroup().findOwningConnectorIdentifier().orElse(null);
+        final LossTolerance lossTolerance = connectable.isLossTolerant() ? 
LossTolerance.LOSS_TOLERANT : LossTolerance.LOSS_INTOLERANT;
+        this.contentClaimCreationContext = new 
StandardContentClaimCreationContext(connectable.getIdentifier(), 
connectorIdentifier, lossTolerance);
+        this.flowFileUpdateContext = new 
StandardFlowFileUpdateContext(connectable.getIdentifier(), connectorIdentifier);
     }
 
     @Override
@@ -198,11 +205,21 @@ public abstract class AbstractRepositoryContext 
implements RepositoryContext {
         return contentRepo;
     }
 
+    @Override
+    public ContentClaimCreationContext getContentClaimCreationContext() {
+        return contentClaimCreationContext;
+    }
+
     @Override
     public FlowFileRepository getFlowFileRepository() {
         return flowFileRepo;
     }
 
+    @Override
+    public FlowFileUpdateContext getFlowFileUpdateContext() {
+        return flowFileUpdateContext;
+    }
+
     @Override
     public FlowFileEventRepository getFlowFileEventRepository() {
         return flowFileEventRepo;
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/RepositoryContext.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/RepositoryContext.java
index a26a39bfff9..6d5ce99b092 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/RepositoryContext.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/RepositoryContext.java
@@ -47,8 +47,12 @@ public interface RepositoryContext {
 
     ContentRepository getContentRepository();
 
+    ContentClaimCreationContext getContentClaimCreationContext();
+
     FlowFileRepository getFlowFileRepository();
 
+    FlowFileUpdateContext getFlowFileUpdateContext();
+
     FlowFileEventRepository getFlowFileEventRepository();
 
     ProvenanceEventRepository getProvenanceRepository();
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/StandardProcessSession.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/StandardProcessSession.java
index 1eb60299dd0..0349ccd79cc 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/StandardProcessSession.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/StandardProcessSession.java
@@ -616,7 +616,7 @@ public class StandardProcessSession implements 
ProcessSession, ProvenanceEventEn
             try {
                 final Collection<StandardRepositoryRecord> repoRecords = 
checkpoint.records.values();
                 if (!repoRecords.isEmpty()) {
-                    
context.getFlowFileRepository().updateRepository((Collection) repoRecords);
+                    
context.getFlowFileRepository().updateRepository((Collection) repoRecords, 
context.getFlowFileUpdateContext());
                     
context.getConnectable().getFlowFileActivity().updateLatestActivityTime();
                 }
             } catch (final IOException ioe) {
@@ -1387,7 +1387,7 @@ public class StandardProcessSession implements 
ProcessSession, ProvenanceEventEn
 
         if (!abortedRecords.isEmpty()) {
             try {
-                
context.getFlowFileRepository().updateRepository(abortedRecords);
+                
context.getFlowFileRepository().updateRepository(abortedRecords, 
context.getFlowFileUpdateContext());
             } catch (final IOException ioe) {
                 LOG.error("Unable to update FlowFile repository for aborted 
records", ioe);
             }
@@ -1401,7 +1401,7 @@ public class StandardProcessSession implements 
ProcessSession, ProvenanceEventEn
         if (!transientClaims.isEmpty()) {
             final RepositoryRecord repoRecord = new 
TransientClaimRepositoryRecord(transientClaims);
             try {
-                
context.getFlowFileRepository().updateRepository(Collections.singletonList(repoRecord));
+                
context.getFlowFileRepository().updateRepository(Collections.singletonList(repoRecord),
 context.getFlowFileUpdateContext());
             } catch (final IOException ioe) {
                 LOG.error("Unable to update FlowFile repository to cleanup 
transient claims", ioe);
             }
@@ -2752,7 +2752,7 @@ public class StandardProcessSession implements 
ProcessSession, ProvenanceEventEn
             };
 
             context.getProvenanceRepository().registerEvents(iterable);
-            context.getFlowFileRepository().updateRepository(expiredRecords);
+            context.getFlowFileRepository().updateRepository(expiredRecords, 
context.getFlowFileUpdateContext());
         } catch (final IOException e) {
             LOG.error("Failed to update FlowFile Repository to record expired 
records", e);
         }
@@ -3055,7 +3055,7 @@ public class StandardProcessSession implements 
ProcessSession, ProvenanceEventEn
         final ContentRepository contentRepo = context.getContentRepository();
         final ContentClaim newClaim;
         try {
-            newClaim = 
contentRepo.create(context.getConnectable().isLossTolerant());
+            newClaim = 
contentRepo.create(context.getContentClaimCreationContext());
             claimLog.debug("Creating ContentClaim {} for 'merge' for {}", 
newClaim, destinationRecord.getCurrent());
         } catch (final IOException e) {
             throw new FlowFileAccessException("Unable to create ContentClaim 
due to " + e, e);
@@ -3369,7 +3369,7 @@ public class StandardProcessSession implements 
ProcessSession, ProvenanceEventEn
                 claimCache.flush(oldClaim);
 
                 try (final InputStream oldClaimIn = read(source)) {
-                    newClaim = 
context.getContentRepository().create(context.getConnectable().isLossTolerant());
+                    newClaim = 
context.getContentRepository().create(context.getContentClaimCreationContext());
                     claimLog.debug("Creating ContentClaim {} for 'append' for 
{}", newClaim, source);
 
                     final OutputStream rawOutStream = 
context.getContentRepository().write(newClaim);
@@ -3679,7 +3679,7 @@ public class StandardProcessSession implements 
ProcessSession, ProvenanceEventEn
         final long claimOffset;
 
         try {
-            newClaim = 
context.getContentRepository().create(context.getConnectable().isLossTolerant());
+            newClaim = 
context.getContentRepository().create(context.getContentClaimCreationContext());
             claimLog.debug("Creating ContentClaim {} for 'importFrom' for {}", 
newClaim, destination);
         } catch (final IOException e) {
             throw new FlowFileAccessException("Unable to create ContentClaim 
due to " + e, e);
@@ -3741,7 +3741,7 @@ public class StandardProcessSession implements 
ProcessSession, ProvenanceEventEn
         final long newSize;
         try {
             try {
-                newClaim = 
context.getContentRepository().create(context.getConnectable().isLossTolerant());
+                newClaim = 
context.getContentRepository().create(context.getContentClaimCreationContext());
                 claimLog.debug("Creating ContentClaim {} for 'importFrom' for 
{}", newClaim, destination);
 
                 newSize = 
context.getContentRepository().importFrom(createTaskTerminationStream(source), 
newClaim);
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java
index dec748330ef..f2b41766f27 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java
@@ -178,6 +178,7 @@ public final class StandardProcessGroup implements 
ProcessGroup {
     private final AtomicReference<StandardVersionControlInformation> 
versionControlInfo = new AtomicReference<>();
     private static final SecureRandom randomGenerator = new SecureRandom();
     private final String connectorId;
+    private volatile Optional<String> owningConnectorIdentifier;
 
     private final ProcessScheduler scheduler;
     private final ControllerServiceProvider controllerServiceProvider;
@@ -309,6 +310,8 @@ public final class StandardProcessGroup implements 
ProcessGroup {
     @Override
     public void setParent(final ProcessGroup newParent) {
         parent.set(newParent);
+        // Forget a value resolved before this group was attached to its 
parent.
+        owningConnectorIdentifier = null;
         // Inherit connector-supplied MDC attributes from the parent so 
descendants of a connector's managed
         // flow carry the same connector metadata (attributing their logs and 
status metrics to the connector).
         // Runs on every re-parent (including initial attach), so PGs added 
later inherit automatically.
@@ -364,19 +367,22 @@ public final class StandardProcessGroup implements 
ProcessGroup {
     }
 
     @Override
-    public Optional<ConnectorNode> findOwningConnector() {
-        ProcessGroup group = this;
-        while (group != null) {
-            final Optional<String> owningConnectorId = 
group.getConnectorIdentifier();
-            if (owningConnectorId.isPresent()) {
-                final ConnectorNode connectorNode = 
flowManager.getConnector(owningConnectorId.get());
-                return Optional.ofNullable(connectorNode);
-            }
-
-            group = group.getParent();
+    public Optional<String> findOwningConnectorIdentifier() {
+        Optional<String> identifier = owningConnectorIdentifier;
+        // Check-then-modify, but safe. The parent cannot change from one 
Connector to another; during initialization
+        // it may go from no Connector to some Connector. The only incorrect 
cached value is null (unresolved), and
+        // the next call looks it up again.
+        if (identifier == null) {
+            identifier = ProcessGroup.super.findOwningConnectorIdentifier();
+            owningConnectorIdentifier = identifier;
         }
 
-        return Optional.empty();
+        return identifier;
+    }
+
+    @Override
+    public Optional<ConnectorNode> findOwningConnector() {
+        return findOwningConnectorIdentifier().map(flowManager::getConnector);
     }
 
     @Override
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/groups/StandardProcessGroupTest.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/groups/StandardProcessGroupTest.java
index 73d41805d43..958ce530bef 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/groups/StandardProcessGroupTest.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/groups/StandardProcessGroupTest.java
@@ -62,6 +62,7 @@ import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -403,6 +404,51 @@ class StandardProcessGroupTest {
         assertEquals(expected, leaf.getLoggingAttributes());
     }
 
+    @Test
+    void testFindOwningConnectorIdentifierWalksParentHierarchy() {
+        final StandardProcessGroup connectorGroup = 
createStandardProcessGroup("connector-group", "connector-1");
+        final StandardProcessGroup child = createStandardProcessGroup("child");
+        final StandardProcessGroup grandchild = 
createStandardProcessGroup("grandchild");
+
+        child.setParent(connectorGroup);
+        grandchild.setParent(child);
+
+        assertEquals(Optional.of("connector-1"), 
connectorGroup.findOwningConnectorIdentifier());
+        assertEquals(Optional.of("connector-1"), 
child.findOwningConnectorIdentifier());
+        assertEquals(Optional.of("connector-1"), 
grandchild.findOwningConnectorIdentifier());
+        assertEquals(Optional.empty(), 
processGroup.findOwningConnectorIdentifier());
+    }
+
+    @Test
+    void testFindOwningConnectorIdentifierResolvesHierarchyOnlyOnce() {
+        
when(parentProcessGroup.getConnectorIdentifier()).thenReturn(Optional.of("connector-1"));
+        processGroup.setParent(parentProcessGroup);
+
+        assertEquals(Optional.of("connector-1"), 
processGroup.findOwningConnectorIdentifier());
+        assertEquals(Optional.of("connector-1"), 
processGroup.findOwningConnectorIdentifier());
+        assertEquals(Optional.of("connector-1"), 
processGroup.findOwningConnectorIdentifier());
+        verify(parentProcessGroup, times(1)).getConnectorIdentifier();
+
+        final ProcessGroup unmanagedParent = mock(ProcessGroup.class);
+        final StandardProcessGroup unmanagedGroup = 
createStandardProcessGroup("unmanaged-group");
+        unmanagedGroup.setParent(unmanagedParent);
+
+        assertEquals(Optional.empty(), 
unmanagedGroup.findOwningConnectorIdentifier());
+        assertEquals(Optional.empty(), 
unmanagedGroup.findOwningConnectorIdentifier());
+
+        verify(unmanagedParent, times(1)).getConnectorIdentifier();
+    }
+
+    @Test
+    void testFindOwningConnectorIdentifierResolvedAgainAfterParentAssigned() {
+        assertEquals(Optional.empty(), 
processGroup.findOwningConnectorIdentifier());
+
+        
when(parentProcessGroup.getConnectorIdentifier()).thenReturn(Optional.of("connector-1"));
+        processGroup.setParent(parentProcessGroup);
+
+        assertEquals(Optional.of("connector-1"), 
processGroup.findOwningConnectorIdentifier());
+    }
+
     @Test
     void testDropAllFlowFilesCompletionWaitsForEveryConnection() {
         when(flowManager.getFlowAnalyzer()).thenReturn(Optional.empty());
@@ -431,6 +477,10 @@ class StandardProcessGroupTest {
     }
 
     private StandardProcessGroup createStandardProcessGroup(final String id) {
+        return createStandardProcessGroup(id, null);
+    }
+
+    private StandardProcessGroup createStandardProcessGroup(final String id, 
final String connectorId) {
         return new StandardProcessGroup(
                 id,
                 controllerServiceProvider,
@@ -445,7 +495,7 @@ class StandardProcessGroupTest {
                 properties,
                 statelessGroupNodeFactory,
                 assetManager,
-                null
+                connectorId
         );
     }
 
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/groups/ProcessGroup.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/groups/ProcessGroup.java
index 67ae8990138..c96f40c2253 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/groups/ProcessGroup.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/groups/ProcessGroup.java
@@ -145,22 +145,29 @@ public interface ProcessGroup extends 
ComponentAuthorizable, Positionable, Versi
     Optional<String> getConnectorIdentifier();
 
     /**
-     * Returns the owning Connector for this Process Group, traversing the 
Process Group hierarchy until a Process Group
-     * is found that is associated with a Connector. If no Process Group in 
the hierarchy is associated with a Connector,
-     * an empty Optional is returned. This is useful for determining whether a 
component is managed by a Connector.
-     *
-     * <p>The default implementation returns {@link Optional#empty()}. 
Implementations that can resolve a
-     * {@link ConnectorNode} from a connector identifier (typically via a 
FlowManager) should override this method
-     * and walk the parent chain using {@link #getConnectorIdentifier()} and 
{@link #getParent()} to locate the
-     * owning Connector.</p>
-     *
-     * @return an Optional containing the owning ConnectorNode, or empty if 
this Process Group and all of its ancestors are
-     * not managed by a Connector
+     * @return the Connector that owns this Process Group or an ancestor, or 
empty if none
      */
     default Optional<ConnectorNode> findOwningConnector() {
         return Optional.empty();
     }
 
+    /**
+     * @return the identifier of the Connector that owns this Process Group or 
an ancestor, or empty if none
+     */
+    default Optional<String> findOwningConnectorIdentifier() {
+        ProcessGroup group = this;
+        while (group != null) {
+            final Optional<String> connectorIdentifier = 
group.getConnectorIdentifier();
+            if (connectorIdentifier.isPresent()) {
+                return connectorIdentifier;
+            }
+
+            group = group.getParent();
+        }
+
+        return Optional.empty();
+    }
+
     /**
      * @return the user-set comments about this ProcessGroup, or
      * <code>null</code> if no comments have been set
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
index 8044d9abb66..fcf76a0ac9a 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
@@ -189,7 +189,6 @@ import org.apache.nifi.nar.PythonBundle;
 import org.apache.nifi.parameter.ParameterContextManager;
 import org.apache.nifi.parameter.ParameterProvider;
 import org.apache.nifi.parameter.StandardParameterContextManager;
-import org.apache.nifi.processor.DataUnit;
 import org.apache.nifi.processor.Processor;
 import org.apache.nifi.processor.Relationship;
 import org.apache.nifi.processor.StandardProcessContext;
@@ -601,9 +600,8 @@ public class FlowController implements 
ReportingTaskProvider, FlowAnalysisRulePr
         processScheduler = new 
StandardProcessScheduler(timerDrivenEngineRef.get(), this, 
stateManagerProvider, this.nifiProperties, lifecycleStateManager);
 
         parameterContextManager = new StandardParameterContextManager();
-        final long maxAppendableBytes = getMaxAppendableBytes();
         repositoryContextFactory = new 
RepositoryContextFactory(contentRepository, flowFileRepository, 
flowFileEventRepository,
-            counterRepositoryRef.get(), componentMetricReporter, 
provenanceRepository, stateManagerProvider, maxAppendableBytes);
+            counterRepositoryRef.get(), componentMetricReporter, 
provenanceRepository, stateManagerProvider);
 
         assetManager = createAssetManager(
             nifiProperties,
@@ -1352,7 +1350,6 @@ public class FlowController implements 
ReportingTaskProvider, FlowAnalysisRulePr
             flowFileRepository.updateMaxFlowFileIdentifier(maxIdFromSwapFiles 
+ 1);
 
             // Begin expiring FlowFiles that are old
-            final long maxAppendableClaimBytes = getMaxAppendableBytes();
             final RepositoryContextFactory contextFactory = new 
RepositoryContextFactory(
                     contentRepository,
                     flowFileRepository,
@@ -1360,8 +1357,7 @@ public class FlowController implements 
ReportingTaskProvider, FlowAnalysisRulePr
                     counterRepositoryRef.get(),
                     getComponentMetricReporter(),
                     provenanceRepository,
-                    stateManagerProvider,
-                    maxAppendableClaimBytes
+                    stateManagerProvider
             );
             processScheduler.scheduleFrameworkTask(new ExpireFlowFiles(this, 
contextFactory), "Expire FlowFiles", 30L, 30L, TimeUnit.SECONDS);
 
@@ -1406,12 +1402,6 @@ public class FlowController implements 
ReportingTaskProvider, FlowAnalysisRulePr
         }
     }
 
-    private long getMaxAppendableBytes() {
-        final String maxAppendableClaimSize = 
nifiProperties.getMaxAppendableClaimSize();
-        final long maxAppendableClaimBytes = 
DataUnit.parseDataSize(maxAppendableClaimSize, DataUnit.B).longValue();
-        return maxAppendableClaimBytes;
-    }
-
     private void notifyComponentsConfigurationRestored() {
         for (final ProcessorNode procNode : 
flowManager.getRootGroup().findAllProcessors()) {
             final Processor processor = procNode.getProcessor();
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/FileSystemRepository.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/FileSystemRepository.java
index b8fba96fff8..ddcb048da27 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/FileSystemRepository.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/FileSystemRepository.java
@@ -639,6 +639,11 @@ public class FileSystemRepository implements 
ContentRepository {
         }
     }
 
+    @Override
+    public long getMaxAppendableClaimBytes() {
+        return maxAppendableClaimLength;
+    }
+
     @Override
     public ContentClaim create(final boolean lossTolerant) throws IOException {
         ResourceClaim resourceClaim;
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/NonPurgeableContentRepository.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/NonPurgeableContentRepository.java
index eaef52a5aca..974be1cf7d7 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/NonPurgeableContentRepository.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/NonPurgeableContentRepository.java
@@ -68,6 +68,16 @@ public class NonPurgeableContentRepository implements 
ContentRepository {
         return delegate.create(lossTolerant);
     }
 
+    @Override
+    public ContentClaim create(final ContentClaimCreationContext context) 
throws IOException {
+        return delegate.create(context);
+    }
+
+    @Override
+    public long getMaxAppendableClaimBytes() {
+        return delegate.getMaxAppendableClaimBytes();
+    }
+
     @Override
     public int incrementClaimaintCount(final ContentClaim claim) {
         return delegate.incrementClaimaintCount(claim);
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/StandardRepositoryContext.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/StandardRepositoryContext.java
index 4b569269de2..0d72a7ebee2 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/StandardRepositoryContext.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/StandardRepositoryContext.java
@@ -28,8 +28,6 @@ import java.util.concurrent.atomic.AtomicLong;
 
 public class StandardRepositoryContext extends AbstractRepositoryContext 
implements RepositoryContext {
 
-    private final long maxAppendableClaimBytes;
-
     public StandardRepositoryContext(
             final Connectable connectable,
             final AtomicLong connectionIndex,
@@ -39,15 +37,13 @@ public class StandardRepositoryContext extends 
AbstractRepositoryContext impleme
             final CounterRepository counterRepository,
             final ComponentMetricReporter componentMetricReporter,
             final ProvenanceEventRepository provenanceRepository,
-            final StateManager stateManager,
-            final long maxAppendableClaimBytes
+            final StateManager stateManager
     ) {
         super(connectable, connectionIndex, contentRepository, 
flowFileRepository, flowFileEventRepository, counterRepository, 
componentMetricReporter, provenanceRepository, stateManager);
-        this.maxAppendableClaimBytes = maxAppendableClaimBytes;
     }
 
     @Override
     public ContentClaimWriteCache createContentClaimWriteCache(final 
PerformanceTracker performanceTracker) {
-        return new StandardContentClaimWriteCache(getContentRepository(), 
performanceTracker, maxAppendableClaimBytes, 8192);
+        return new StandardContentClaimWriteCache(getContentRepository(), 
performanceTracker, getContentClaimCreationContext(), 8192);
     }
 }
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/claim/StandardContentClaimWriteCache.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/claim/StandardContentClaimWriteCache.java
index 74639395563..7203d47874f 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/claim/StandardContentClaimWriteCache.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/claim/StandardContentClaimWriteCache.java
@@ -17,6 +17,7 @@
 
 package org.apache.nifi.controller.repository.claim;
 
+import org.apache.nifi.controller.repository.ContentClaimCreationContext;
 import org.apache.nifi.controller.repository.ContentRepository;
 import org.apache.nifi.controller.repository.io.ContentClaimOutputStream;
 import org.apache.nifi.controller.repository.metrics.PerformanceTracker;
@@ -35,13 +36,13 @@ public class StandardContentClaimWriteCache implements 
ContentClaimWriteCache {
     private final Map<ResourceClaim, MappedOutputStream> streamMap = new 
ConcurrentHashMap<>();
     private final Queue<ContentClaim> queue = new LinkedList<>();
     private final PerformanceTracker performanceTracker;
-    private final long maxAppendableClaimBytes;
+    private final ContentClaimCreationContext creationContext;
     private final int bufferSize;
 
-    public StandardContentClaimWriteCache(final ContentRepository contentRepo, 
final PerformanceTracker performanceTracker, final long 
maxAppendableClaimBytes, final int bufferSize) {
+    public StandardContentClaimWriteCache(final ContentRepository contentRepo, 
final PerformanceTracker performanceTracker, final ContentClaimCreationContext 
creationContext, final int bufferSize) {
         this.contentRepo = contentRepo;
         this.performanceTracker = performanceTracker;
-        this.maxAppendableClaimBytes = maxAppendableClaimBytes;
+        this.creationContext = creationContext;
         this.bufferSize = bufferSize;
     }
 
@@ -70,7 +71,7 @@ public class StandardContentClaimWriteCache implements 
ContentClaimWriteCache {
             }
         }
 
-        final ContentClaim claim = contentRepo.create(false);
+        final ContentClaim claim = contentRepo.create(creationContext);
         registerStream(claim);
         return claim;
     }
@@ -152,7 +153,7 @@ public class StandardContentClaimWriteCache implements 
ContentClaimWriteCache {
                 }
 
                 // Add the claim back to the queue if it is still writable
-                if ((scc.getOffset() + scc.getLength()) < 
maxAppendableClaimBytes) {
+                if ((scc.getOffset() + scc.getLength()) < 
contentRepo.getMaxAppendableClaimBytes()) {
                     queue.offer(claim);
                 }
             }
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/RepositoryContextFactory.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/RepositoryContextFactory.java
index 8aabdb676e9..9ee55397a31 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/RepositoryContextFactory.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/RepositoryContextFactory.java
@@ -40,7 +40,6 @@ public class RepositoryContextFactory {
     private final ComponentMetricReporter componentMetricReporter;
     private final ProvenanceRepository provenanceRepo;
     private final StateManagerProvider stateManagerProvider;
-    private final long maxAppendableClaimBytes;
 
     public RepositoryContextFactory(
             final ContentRepository contentRepository,
@@ -49,8 +48,7 @@ public class RepositoryContextFactory {
             final CounterRepository counterRepository,
             final ComponentMetricReporter componentMetricReporter,
             final ProvenanceRepository provenanceRepository,
-            final StateManagerProvider stateManagerProvider,
-            final long maxAppendableClaimBytes
+            final StateManagerProvider stateManagerProvider
     ) {
         this.contentRepo = contentRepository;
         this.flowFileRepo = flowFileRepository;
@@ -59,7 +57,6 @@ public class RepositoryContextFactory {
         this.componentMetricReporter = componentMetricReporter;
         this.provenanceRepo = provenanceRepository;
         this.stateManagerProvider = stateManagerProvider;
-        this.maxAppendableClaimBytes = maxAppendableClaimBytes;
     }
 
     public RepositoryContext newProcessContext(final Connectable connectable, 
final AtomicLong connectionIndex) {
@@ -76,8 +73,7 @@ public class RepositoryContextFactory {
                 counterRepo,
                 componentMetricReporter,
                 provenanceRepo,
-                stateManager,
-                maxAppendableClaimBytes
+                stateManager
         );
     }
 
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/NonPurgeableContentRepositoryTest.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/NonPurgeableContentRepositoryTest.java
new file mode 100644
index 00000000000..2a33ccc972a
--- /dev/null
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/NonPurgeableContentRepositoryTest.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *     http://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.apache.nifi.controller.repository;
+
+import org.apache.nifi.controller.repository.claim.ContentClaim;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class NonPurgeableContentRepositoryTest {
+    private static final long DELEGATE_MAX_APPENDABLE_CLAIM_BYTES = 1_048_576L;
+
+    private static final ContentClaimCreationContext CREATION_CONTEXT = new 
StandardContentClaimCreationContext("component-1", "connector-1", 
LossTolerance.LOSS_TOLERANT);
+
+    @Mock
+    private ContentRepository delegate;
+
+    @Mock
+    private ContentClaim contentClaim;
+
+    @Test
+    void testMaxAppendableClaimBytesTakenFromDelegate() {
+        
when(delegate.getMaxAppendableClaimBytes()).thenReturn(DELEGATE_MAX_APPENDABLE_CLAIM_BYTES);
+
+        final ContentRepository repository = new 
NonPurgeableContentRepository(delegate);
+
+        assertEquals(DELEGATE_MAX_APPENDABLE_CLAIM_BYTES, 
repository.getMaxAppendableClaimBytes());
+    }
+
+    @Test
+    void testCreationContextForwardedToDelegate() throws IOException {
+        when(delegate.create(CREATION_CONTEXT)).thenReturn(contentClaim);
+
+        final ContentRepository repository = new 
NonPurgeableContentRepository(delegate);
+
+        assertSame(contentClaim, repository.create(CREATION_CONTEXT));
+    }
+}
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/StandardProcessSessionIT.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/StandardProcessSessionIT.java
index 582cd68d11f..6baab772b82 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/StandardProcessSessionIT.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/StandardProcessSessionIT.java
@@ -92,6 +92,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -128,6 +129,10 @@ import static org.mockito.Mockito.when;
 public class StandardProcessSessionIT {
     private static final Relationship FAKE_RELATIONSHIP = new 
Relationship.Builder().name("FAKE").build();
 
+    private static final String CONNECTABLE_ID = "connectable-1";
+
+    private static final String CONNECTOR_ID = "connector-1";
+
     private StandardProcessSession session;
     private MockContentRepository contentRepo;
     private FlowFileQueue flowFileQueue;
@@ -193,12 +198,13 @@ public class StandardProcessSessionIT {
         final ProcessGroup procGroup = mock(ProcessGroup.class);
         when(procGroup.getIdentifier()).thenReturn("proc-group-identifier-1");
         when(procGroup.getLoggingAttributes()).thenReturn(Map.of());
+        
when(procGroup.findOwningConnectorIdentifier()).thenReturn(Optional.of(CONNECTOR_ID));
 
         connectable = mock(Connectable.class);
         when(connectable.hasIncomingConnection()).thenReturn(true);
         when(connectable.getIncomingConnections()).thenReturn(connList);
         when(connectable.getProcessGroup()).thenReturn(procGroup);
-        when(connectable.getIdentifier()).thenReturn("connectable-1");
+        when(connectable.getIdentifier()).thenReturn(CONNECTABLE_ID);
         
when(connectable.getConnectableType()).thenReturn(ConnectableType.INPUT_PORT);
         when(connectable.getComponentType()).thenReturn("Unit Test Component");
         
when(connectable.getBackoffMechanism()).thenReturn(BackoffMechanism.PENALIZE_FLOWFILE);
@@ -227,7 +233,7 @@ public class StandardProcessSessionIT {
         stateManager.setIgnoreAnnotations(true);
 
         context = new StandardRepositoryContext(connectable, new 
AtomicLong(0L), contentRepo, flowFileRepo, flowFileEventRepository,
-            counterRepository, componentMetricReporter, provenanceRepo, 
stateManager, 50_000L);
+            counterRepository, componentMetricReporter, provenanceRepo, 
stateManager);
         session = new StandardProcessSession(context, () -> false, new 
NopPerformanceTracker());
     }
 
@@ -2356,6 +2362,80 @@ public class StandardProcessSessionIT {
         assertEquals(5, transientClaims.size());
     }
 
+    @Test
+    public void 
testContentClaimCreationContextIdentifiesComponentAndConnector() {
+        FlowFile flowFile = session.create();
+        flowFile = session.importFrom(new 
ByteArrayInputStream("imported".getBytes(StandardCharsets.UTF_8)), flowFile);
+        flowFile = session.write(flowFile, out -> 
out.write("written".getBytes(StandardCharsets.UTF_8)));
+
+        session.transfer(flowFile, new 
Relationship.Builder().name("success").build());
+        session.commit();
+
+        // The first claim is created directly by the import; the second is 
obtained from the session's write cache.
+        final List<ContentClaimCreationContext> creationContexts = 
contentRepo.getCreationContexts();
+        assertEquals(2, creationContexts.size());
+
+        for (final ContentClaimCreationContext creationContext : 
creationContexts) {
+            assertEquals(CONNECTABLE_ID, 
creationContext.getComponentIdentifier());
+            assertEquals(CONNECTOR_ID, 
creationContext.getConnectorIdentifier());
+            assertEquals(LossTolerance.LOSS_INTOLERANT, 
creationContext.getLossTolerance());
+        }
+    }
+
+    @Test
+    public void testContentClaimCreationContextLossToleranceMatchesComponent() 
{
+        when(connectable.isLossTolerant()).thenReturn(true);
+
+        final StandardRepositoryContext lossTolerantContext = new 
StandardRepositoryContext(connectable, new AtomicLong(0L), contentRepo, 
flowFileRepo,
+            flowFileEventRepository, counterRepository, 
componentMetricReporter, provenanceRepo, stateManager);
+        final StandardProcessSession lossTolerantSession = new 
StandardProcessSession(lossTolerantContext, () -> false, new 
NopPerformanceTracker());
+
+        try {
+            FlowFile flowFile = lossTolerantSession.create();
+            flowFile = lossTolerantSession.importFrom(new 
ByteArrayInputStream("imported".getBytes(StandardCharsets.UTF_8)), flowFile);
+            lossTolerantSession.write(flowFile, out -> 
out.write("written".getBytes(StandardCharsets.UTF_8)));
+        } finally {
+            lossTolerantSession.rollback();
+        }
+
+        final List<ContentClaimCreationContext> creationContexts = 
contentRepo.getCreationContexts();
+        assertEquals(2, creationContexts.size());
+
+        for (final ContentClaimCreationContext creationContext : 
creationContexts) {
+            assertEquals(LossTolerance.LOSS_TOLERANT, 
creationContext.getLossTolerance());
+        }
+    }
+
+    @Test
+    public void 
testFlowFileUpdateContextIdentifiesComponentAndConnectorOnCommit() {
+        flowFileQueue.put(new MockFlowFileRecord(1L));
+
+        final FlowFile flowFile = session.get();
+        session.transfer(flowFile, new 
Relationship.Builder().name("success").build());
+        session.commit();
+
+        final List<FlowFileUpdateContext> updateContexts = 
flowFileRepo.getUpdateContexts();
+        assertEquals(1, updateContexts.size());
+        assertEquals(CONNECTABLE_ID, 
updateContexts.getFirst().getComponentIdentifier());
+        assertEquals(CONNECTOR_ID, 
updateContexts.getFirst().getConnectorIdentifier());
+    }
+
+    @Test
+    public void 
testFlowFileUpdateContextIdentifiesComponentAndConnectorOnRollback() {
+        flowFileQueue.put(new MockFlowFileRecord(1L));
+
+        FlowFile flowFile = session.get();
+        flowFile = session.write(flowFile, out -> 
out.write("content".getBytes(StandardCharsets.UTF_8)));
+        assertNotNull(flowFile);
+
+        session.rollback();
+
+        final List<FlowFileUpdateContext> updateContexts = 
flowFileRepo.getUpdateContexts();
+        assertEquals(1, updateContexts.size());
+        assertEquals(CONNECTABLE_ID, 
updateContexts.getFirst().getComponentIdentifier());
+        assertEquals(CONNECTOR_ID, 
updateContexts.getFirst().getConnectorIdentifier());
+    }
+
     @Test
     public void testMultipleReadCounts() throws IOException {
         final ContentClaim contentClaim = contentRepo.create("Hello 
there".getBytes(StandardCharsets.UTF_8));
@@ -3107,8 +3187,7 @@ public class StandardProcessSessionIT {
                 counterRepository,
                 componentMetricReporter,
                 provenanceRepo,
-                stateManager,
-                50_000L);
+                stateManager);
         return new StandardProcessSession(context, () -> false, new 
NopPerformanceTracker());
 
     }
@@ -3118,6 +3197,7 @@ public class StandardProcessSessionIT {
         private boolean failOnUpdate = false;
         private final AtomicLong idGenerator = new AtomicLong(0L);
         private final List<RepositoryRecord> updates = new ArrayList<>();
+        private final List<FlowFileUpdateContext> updateContexts = new 
ArrayList<>();
         private final ContentRepository contentRepo;
 
         public MockFlowFileRepository(final ContentRepository contentRepo) {
@@ -3164,10 +3244,20 @@ public class StandardProcessSessionIT {
             }
         }
 
+        @Override
+        public void updateRepository(final Collection<RepositoryRecord> 
records, final FlowFileUpdateContext context) throws IOException {
+            updateContexts.add(context);
+            FlowFileRepository.super.updateRepository(records, context);
+        }
+
         public List<RepositoryRecord> getUpdates() {
             return updates;
         }
 
+        public List<FlowFileUpdateContext> getUpdateContexts() {
+            return updateContexts;
+        }
+
         @Override
         public long getStorageCapacity() {
             return 0;
@@ -3220,6 +3310,7 @@ public class StandardProcessSessionIT {
 
         private final AtomicLong idGenerator = new AtomicLong(0L);
         private final AtomicLong claimsRemoved = new AtomicLong(0L);
+        private final List<ContentClaimCreationContext> creationContexts = 
Collections.synchronizedList(new ArrayList<>());
         private ResourceClaimManager claimManager;
         private boolean disableRead = false;
 
@@ -3256,6 +3347,16 @@ public class StandardProcessSessionIT {
             return contentClaim;
         }
 
+        @Override
+        public ContentClaim create(final ContentClaimCreationContext context) 
throws IOException {
+            creationContexts.add(context);
+            return ContentRepository.super.create(context);
+        }
+
+        public List<ContentClaimCreationContext> getCreationContexts() {
+            return creationContexts;
+        }
+
         public ContentClaim create(byte[] content) throws IOException {
             final ResourceClaim resourceClaim = 
claimManager.newResourceClaim("container", "section", 
String.valueOf(idGenerator.getAndIncrement()), false, false);
             final StandardContentClaim contentClaim = new 
StandardContentClaim(resourceClaim, 0L);
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/claim/TestStandardContentClaimWriteCache.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/claim/TestStandardContentClaimWriteCache.java
index 343b325455e..7d2698dba99 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/claim/TestStandardContentClaimWriteCache.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/claim/TestStandardContentClaimWriteCache.java
@@ -17,22 +17,29 @@
 
 package org.apache.nifi.controller.repository.claim;
 
+import org.apache.nifi.controller.repository.ContentClaimCreationContext;
 import org.apache.nifi.controller.repository.FileSystemRepository;
+import org.apache.nifi.controller.repository.LossTolerance;
+import 
org.apache.nifi.controller.repository.StandardContentClaimCreationContext;
 import org.apache.nifi.controller.repository.StandardContentRepositoryContext;
-import org.apache.nifi.controller.repository.TestFileSystemRepository;
 import org.apache.nifi.controller.repository.metrics.NopPerformanceTracker;
-import org.apache.nifi.controller.repository.util.DiskUtils;
 import org.apache.nifi.events.EventReporter;
 import org.apache.nifi.stream.io.StreamUtils;
 import org.apache.nifi.util.NiFiProperties;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.Random;
 
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
@@ -41,31 +48,51 @@ import static 
org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
 public class TestStandardContentClaimWriteCache {
+    private static final ContentClaimCreationContext CREATION_CONTEXT = new 
StandardContentClaimCreationContext("component-1", "connector-1", 
LossTolerance.LOSS_INTOLERANT);
+
+    private static final int BUFFER_SIZE = 4;
+
+    private static final Path NIFI_PROPERTIES_FILE = 
Paths.get("src/test/resources/conf/nifi.properties");
+
+    @TempDir
+    private Path tempDir;
+
+    private final List<FileSystemRepository> repositories = new ArrayList<>();
 
     private FileSystemRepository repository = null;
     private StandardResourceClaimManager claimManager = null;
-    private final File rootFile = new 
File("target/testContentClaimWriteCache");
 
     @BeforeEach
     public void setup() throws IOException {
-        NiFiProperties nifiProperties = 
NiFiProperties.createBasicNiFiProperties(TestFileSystemRepository.class.getResource("/conf/nifi.properties").getFile());
-        if (rootFile.exists()) {
-            DiskUtils.deleteRecursively(rootFile);
-        }
-        repository = new FileSystemRepository(nifiProperties);
         claimManager = new StandardResourceClaimManager();
-        repository.initialize(new 
StandardContentRepositoryContext(claimManager, EventReporter.NO_OP));
-        repository.purge();
+        repository = createRepository(Map.of());
     }
 
     @AfterEach
     public void shutdown() throws IOException {
-        repository.shutdown();
+        for (final FileSystemRepository createdRepository : repositories) {
+            createdRepository.shutdown();
+        }
+    }
+
+    private FileSystemRepository createRepository(final Map<String, String> 
additionalProperties) throws IOException {
+        final Path repositoryDirectory = tempDir.resolve("content-repository-" 
+ repositories.size());
+
+        final Map<String, String> properties = new 
HashMap<>(additionalProperties);
+        
properties.put(NiFiProperties.REPOSITORY_CONTENT_PREFIX.concat("default"), 
repositoryDirectory.toString());
+
+        final NiFiProperties nifiProperties = 
NiFiProperties.createBasicNiFiProperties(NIFI_PROPERTIES_FILE.toString(), 
properties);
+        final FileSystemRepository createdRepository = new 
FileSystemRepository(nifiProperties);
+        createdRepository.initialize(new 
StandardContentRepositoryContext(claimManager, EventReporter.NO_OP));
+        createdRepository.purge();
+
+        repositories.add(createdRepository);
+        return createdRepository;
     }
 
     @Test
     public void testFlushWriteCorrectData() throws IOException {
-        final ContentClaimWriteCache cache = new 
StandardContentClaimWriteCache(repository, new NopPerformanceTracker(), 
50_000L, 4);
+        final ContentClaimWriteCache cache = new 
StandardContentClaimWriteCache(repository, new NopPerformanceTracker(), 
CREATION_CONTEXT, BUFFER_SIZE);
 
         final ContentClaim claim1 = cache.getContentClaim();
         assertNotNull(claim1);
@@ -78,10 +105,11 @@ public class TestStandardContentClaimWriteCache {
         cache.flush();
 
         assertEquals(13L, claim1.getLength());
-        final InputStream in = repository.read(claim1);
-        final byte[] buff = new byte[(int) claim1.getLength()];
-        StreamUtils.fillBuffer(in, buff);
-        assertArrayEquals("hellogood-bye".getBytes(), buff);
+        try (final InputStream in = repository.read(claim1)) {
+            final byte[] buff = new byte[(int) claim1.getLength()];
+            StreamUtils.fillBuffer(in, buff);
+            assertArrayEquals("hellogood-bye".getBytes(), buff);
+        }
 
         final ContentClaim claim2 = cache.getContentClaim();
         final OutputStream out2 = cache.write(claim2);
@@ -92,15 +120,18 @@ public class TestStandardContentClaimWriteCache {
         cache.flush();
 
         assertEquals(13L, claim2.getLength());
-        final InputStream in2 = repository.read(claim2);
-        final byte[] buff2 = new byte[(int) claim2.getLength()];
-        StreamUtils.fillBuffer(in2, buff2);
-        assertArrayEquals("good-dayhello".getBytes(), buff2);
+        try (final InputStream in = repository.read(claim2)) {
+            final byte[] buff = new byte[(int) claim2.getLength()];
+            StreamUtils.fillBuffer(in, buff);
+            assertArrayEquals("good-dayhello".getBytes(), buff);
+        }
+
+        cache.reset();
     }
 
     @Test
     public void testWriteLargeRollsOverToNewFileOnNext() throws IOException {
-        final ContentClaimWriteCache cache = new 
StandardContentClaimWriteCache(repository, new NopPerformanceTracker(), 
50_000L, 4);
+        final ContentClaimWriteCache cache = new 
StandardContentClaimWriteCache(repository, new NopPerformanceTracker(), 
CREATION_CONTEXT, BUFFER_SIZE);
 
         final ContentClaim claim1 = cache.getContentClaim();
         assertNotNull(claim1);
@@ -142,4 +173,29 @@ public class TestStandardContentClaimWriteCache {
         assertEquals(1, 
claimManager.getClaimantCount(claim4.getResourceClaim()));
     }
 
+    @Test
+    public void testRolloverBoundaryDeterminedByContentRepository() throws 
IOException {
+        final FileSystemRepository smallClaimRepository = 
createRepository(Map.of(NiFiProperties.MAX_APPENDABLE_CLAIM_SIZE, "1 KB"));
+        assertEquals(1024L, smallClaimRepository.getMaxAppendableClaimBytes());
+
+        final ContentClaimWriteCache cache = new 
StandardContentClaimWriteCache(smallClaimRepository, new 
NopPerformanceTracker(), CREATION_CONTEXT, BUFFER_SIZE);
+        final byte[] content = new byte[600];
+
+        final ContentClaim claim1 = cache.getContentClaim();
+        try (final OutputStream out = cache.write(claim1)) {
+            out.write(content);
+        }
+
+        // 600 bytes have been written to the Resource Claim, which is below 
the 1 KB limit, so it remains appendable.
+        final ContentClaim claim2 = cache.getContentClaim();
+        assertEquals(claim1.getResourceClaim(), claim2.getResourceClaim());
+
+        try (final OutputStream out = cache.write(claim2)) {
+            out.write(content);
+        }
+
+        // 1,200 bytes have now been written to the Resource Claim, which 
exceeds the 1 KB limit.
+        final ContentClaim claim3 = cache.getContentClaim();
+        assertNotEquals(claim1.getResourceClaim(), claim3.getResourceClaim());
+    }
 }
diff --git 
a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/repository/StatelessContentClaimWriteCache.java
 
b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/repository/StatelessContentClaimWriteCache.java
index cd42e160d26..bd6c3431adc 100644
--- 
a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/repository/StatelessContentClaimWriteCache.java
+++ 
b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/repository/StatelessContentClaimWriteCache.java
@@ -17,6 +17,7 @@
 
 package org.apache.nifi.stateless.repository;
 
+import org.apache.nifi.controller.repository.ContentClaimCreationContext;
 import org.apache.nifi.controller.repository.ContentRepository;
 import org.apache.nifi.controller.repository.claim.ContentClaim;
 import org.apache.nifi.controller.repository.claim.ContentClaimWriteCache;
@@ -33,11 +34,13 @@ import java.util.List;
 public class StatelessContentClaimWriteCache implements ContentClaimWriteCache 
{
     private final ContentRepository contentRepository;
     private final PerformanceTracker performanceTracker;
+    private final ContentClaimCreationContext creationContext;
     private final List<OutputStream> writtenTo = new ArrayList<>();
 
-    public StatelessContentClaimWriteCache(final ContentRepository 
contentRepository, final PerformanceTracker performanceTracker) {
+    public StatelessContentClaimWriteCache(final ContentRepository 
contentRepository, final PerformanceTracker performanceTracker, final 
ContentClaimCreationContext creationContext) {
         this.contentRepository = contentRepository;
         this.performanceTracker = performanceTracker;
+        this.creationContext = creationContext;
     }
 
     @Override
@@ -55,7 +58,7 @@ public class StatelessContentClaimWriteCache implements 
ContentClaimWriteCache {
 
     @Override
     public ContentClaim getContentClaim() throws IOException {
-        return contentRepository.create(false);
+        return contentRepository.create(creationContext);
     }
 
     @Override
diff --git 
a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/repository/StatelessRepositoryContext.java
 
b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/repository/StatelessRepositoryContext.java
index f201c935357..065e3850273 100644
--- 
a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/repository/StatelessRepositoryContext.java
+++ 
b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/repository/StatelessRepositoryContext.java
@@ -52,6 +52,6 @@ public class StatelessRepositoryContext extends 
AbstractRepositoryContext implem
 
     @Override
     public ContentClaimWriteCache createContentClaimWriteCache(final 
PerformanceTracker performanceTracker) {
-        return new StatelessContentClaimWriteCache(contentRepository, 
performanceTracker);
+        return new StatelessContentClaimWriteCache(contentRepository, 
performanceTracker, getContentClaimCreationContext());
     }
 }

Reply via email to