/*
 * 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.queue.FlowFileQueue;
import org.apache.nifi.controller.repository.claim.ContentClaim;
import org.apache.nifi.controller.repository.claim.ResourceClaim;
import org.apache.nifi.controller.repository.claim.ResourceClaimManager;

import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

/**
 * <p>
 * An in-memory implementation of the {@link FlowFileRepository}. Upon restart, all FlowFiles will be discarded, including those that have been swapped out by a {@link FlowFileSwapManager}.
 * </p>
 */
public class VolatileFlowFileRepository implements FlowFileRepository {

    private final AtomicLong idGenerator = new AtomicLong(0L);
    private ResourceClaimManager claimManager; // effectively final

    @Override
    public void initialize(final ResourceClaimManager claimManager) {
        this.claimManager = claimManager;
    }

    @Override
    public boolean isVolatile() {
        return true;
    }

    @Override
    public long getStorageCapacity() throws IOException {
        return 1L;
    }

    @Override
    public long getUsableStorageSpace() throws IOException {
        return 0L;
    }

    @Override
    public String getFileStoreName() {
        return null;
    }

    @Override
    public void close() throws IOException {
    }

    private void markDestructable(final ContentClaim contentClaim) {
        if (contentClaim == null) {
            return;
        }

        final ResourceClaim resourceClaim = contentClaim.getResourceClaim();
        if (resourceClaim == null) {
            return;
        }

        claimManager.markDestructable(resourceClaim);
    }

    private int getClaimantCount(final ContentClaim claim) {
        if (claim == null) {
            return 0;
        }

        final ResourceClaim resourceClaim = claim.getResourceClaim();
        if (resourceClaim == null) {
            return 0;
        }

        return claimManager.getClaimantCount(resourceClaim);
    }

    @Override
    public void updateRepository(final Collection<RepositoryRecord> records) throws IOException {
        for (final RepositoryRecord record : records) {
            if (record.getType() == RepositoryRecordType.DELETE) {
                // For any DELETE record that we have, if current claim's claimant count <= 0, mark it as destructable
                if (record.getCurrentClaim() != null && getClaimantCount(record.getCurrentClaim()) <= 0) {
                    markDestructable(record.getCurrentClaim());
                }

                // If the original claim is different than the current claim and the original claim has a claimant count <= 0, mark it as destructable.
                if (record.getOriginalClaim() != null && !record.getOriginalClaim().equals(record.getCurrentClaim()) && getClaimantCount(record.getOriginalClaim()) <= 0) {
                    markDestructable(record.getOriginalClaim());
                }
            } else if (record.getType() == RepositoryRecordType.UPDATE) {
                // if we have an update, and the original is no longer needed, mark original as destructable
                if (record.getOriginalClaim() != null && record.getCurrentClaim() != record.getOriginalClaim() && getClaimantCount(record.getOriginalClaim()) <= 0) {
                    markDestructable(record.getOriginalClaim());
                }
            }
        }
    }

    @Override
    public long loadFlowFiles(final QueueProvider queueProvider) throws IOException {
        return 0;
    }

    @Override
    public void updateMaxFlowFileIdentifier(final long maxId) {
        while (true) {
            final long currentId = idGenerator.get();
            if (currentId >= maxId) {
                return;
            }

            final boolean updated = idGenerator.compareAndSet(currentId, maxId);
            if (updated) {
                return;
            }
        }
    }

    @Override
    public long getNextFlowFileSequence() {
        return idGenerator.getAndIncrement();
    }

    @Override
    public long getMaxFlowFileIdentifier() throws IOException {
        return idGenerator.get() - 1;
    }

    @Override
    public void swapFlowFilesIn(String swapLocation, List<FlowFileRecord> flowFileRecords, FlowFileQueue queue) throws IOException {
    }

    @Override
    public void swapFlowFilesOut(List<FlowFileRecord> swappedOut, FlowFileQueue queue, String swapLocation) throws IOException {
    }

    @Override
    public boolean isValidSwapLocationSuffix(final String swapLocationSuffix) {
        return false;
    }
}

@@ -1183,16 +1183,42 @@
 l.List;%0A
+import java.util.Objects;%0A
 import j
@@ -1256,16 +1256,16 @@
 icLong;%0A
-
 %0A/**%0A * 
@@ -3067,24 +3067,64 @@
  records) %7B%0A
+            updateClaimCounts(record);%0A%0A
             
@@ -4332,32 +4332,32 @@
 %7D%0A            %7D%0A
-
         %7D%0A    %7D%0A
@@ -4349,32 +4349,873 @@
        %7D%0A    %7D%0A%0A
+    private void updateClaimCounts(final RepositoryRecord record) %7B%0A        final ContentClaim currentClaim = record.getCurrentClaim();%0A        final ContentClaim originalClaim = record.getOriginalClaim();%0A        final boolean claimChanged = !Objects.equals(currentClaim, originalClaim);%0A%0A        if (record.getType() == RepositoryRecordType.DELETE %7C%7C record.getType() == RepositoryRecordType.CONTENTMISSING) %7B%0A            decrementClaimCount(currentClaim);%0A        %7D%0A%0A        if (claimChanged) %7B%0A            // records which have been updated - remove original if exists%0A            decrementClaimCount(originalClaim);%0A        %7D%0A    %7D%0A%0A    private void decrementClaimCount(final ContentClaim claim) %7B%0A        if (claim == null) %7B%0A            return;%0A        %7D%0A%0A        claimManager.decrementClaimantCount(claim.getResourceClaim());%0A    %7D%0A%0A
     @Override%0A  

Update VolatileFlowFileRepository to decrement claimant counts when FlowFiles are deleted