/*
* 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.claim;
import java.util.Collection;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class StandardResourceClaimManager implements ResourceClaimManager {
private static final Logger logger = LoggerFactory.getLogger(StandardResourceClaimManager.class);
private final ConcurrentMap<ResourceClaim, ClaimCount> claimantCounts = new ConcurrentHashMap<>();
private final BlockingQueue<ResourceClaim> destructableClaims = new LinkedBlockingQueue<>(50000);
@Override
public ResourceClaim newResourceClaim(final String container, final String section, final String id, final boolean lossTolerant, final boolean writable) {
final StandardResourceClaim claim = new StandardResourceClaim(this, container, section, id, lossTolerant);
if (!writable) {
claim.freeze();
}
return claim;
}
@Override
public ResourceClaim getResourceClaim(final String container, final String section, final String id) {
final ResourceClaim tempClaim = new StandardResourceClaim(this, container, section, id, false);
final ClaimCount count = claimantCounts.get(tempClaim);
return (count == null) ? null : count.getClaim();
}
private AtomicInteger getCounter(final ResourceClaim claim) {
if (claim == null) {
return null;
}
ClaimCount counter = claimantCounts.get(claim);
if (counter != null) {
return counter.getCount();
}
counter = new ClaimCount(claim, new AtomicInteger(0));
final ClaimCount existingCounter = claimantCounts.putIfAbsent(claim, counter);
return existingCounter == null ? counter.getCount() : existingCounter.getCount();
}
@Override
public int getClaimantCount(final ResourceClaim claim) {
if (claim == null) {
return 0;
}
synchronized (claim) {
final ClaimCount counter = claimantCounts.get(claim);
return counter == null ? 0 : counter.getCount().get();
}
}
@Override
public int decrementClaimantCount(final ResourceClaim claim) {
if (claim == null) {
return 0;
}
synchronized (claim) {
final ClaimCount counter = claimantCounts.get(claim);
if (counter == null) {
logger.warn("Decrementing claimant count for {} but claimant count is not known. Returning -1", claim);
return -1;
}
final int newClaimantCount = counter.getCount().decrementAndGet();
if (newClaimantCount < 0) {
logger.error("Decremented claimant count for {} to {}", claim, newClaimantCount);
} else {
logger.debug("Decrementing claimant count for {} to {}", claim, newClaimantCount);
}
// If the claim is no longer referenced, we want to remove it. We consider the claim to be "no longer referenced"
// if the count is 0 and it is no longer writable (if it's writable, it may still be writable by the Content Repository,
// even though no existing FlowFile is referencing the claim).
if (newClaimantCount == 0 && !claim.isWritable()) {
removeClaimantCount(claim);
}
return newClaimantCount;
}
}
// protected so that it can be used in unit tests
protected void removeClaimantCount(final ResourceClaim claim) {
claimantCounts.remove(claim);
}
@Override
public int incrementClaimantCount(final ResourceClaim claim) {
return incrementClaimantCount(claim, false);
}
@Override
public int incrementClaimantCount(final ResourceClaim claim, final boolean newClaim) {
if (claim == null) {
return 0;
}
synchronized (claim) {
final AtomicInteger counter = getCounter(claim);
final int newClaimantCount = counter.incrementAndGet();
logger.debug("Incrementing claimant count for {} to {}", claim, newClaimantCount);
// If the claimant count moved from 0 to 1, remove it from the queue of destructable claims.
if (!newClaim && newClaimantCount == 1) {
destructableClaims.remove(claim);
}
return newClaimantCount;
}
}
@Override
public void markDestructable(final ResourceClaim claim) {
if (claim == null) {
return;
}
synchronized (claim) {
if (getClaimantCount(claim) > 0) {
return;
}
logger.debug("Marking claim {} as destructable", claim);
try {
while (!destructableClaims.offer(claim, 30, TimeUnit.MINUTES)) {
}
} catch (final InterruptedException ie) {
}
}
}
@Override
public void drainDestructableClaims(final Collection<ResourceClaim> destination, final int maxElements) {
final int drainedCount = destructableClaims.drainTo(destination, maxElements);
logger.debug("Drained {} destructable claims to {}", drainedCount, destination);
}
@Override
public void drainDestructableClaims(final Collection<ResourceClaim> destination, final int maxElements, final long timeout, final TimeUnit unit) {
try {
final ResourceClaim firstClaim = destructableClaims.poll(timeout, unit);
if (firstClaim != null) {
destination.add(firstClaim);
destructableClaims.drainTo(destination, maxElements - 1);
}
} catch (final InterruptedException e) {
}
}
@Override
public void purge() {
claimantCounts.clear();
}
@Override
public void freeze(final ResourceClaim claim) {
if (claim == null) {
return;
}
if (!(claim instanceof StandardResourceClaim)) {
throw new IllegalArgumentException("The given resource claim is not managed by this Resource Claim Manager");
}
((StandardResourceClaim) claim).freeze();
synchronized (claim) {
if (getClaimantCount(claim) == 0) {
claimantCounts.remove(claim);
}
}
}
private static final class ClaimCount {
private final ResourceClaim claim;
private final AtomicInteger count;
public ClaimCount(final ResourceClaim claim, final AtomicInteger count) {
this.claim = claim;
this.count = count;
}
public AtomicInteger getCount() {
return count;
}
public ResourceClaim getClaim() {
return claim;
}
}
}