/* * 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.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import org.apache.nifi.connectable.Connectable; import org.apache.nifi.connectable.ConnectableType; import org.apache.nifi.connectable.Connection; import org.apache.nifi.controller.ProcessorNode; import org.apache.nifi.processor.Relationship; import org.apache.nifi.provenance.ProvenanceEventRepository; import org.apache.nifi.util.Connectables; /** * */ public class ProcessContext { private final Connectable connectable; private final ContentRepository contentRepo; private final FlowFileRepository flowFileRepo; private final FlowFileEventRepository flowFileEventRepo; private final CounterRepository counterRepo; private final ProvenanceEventRepository provenanceRepo; private final AtomicLong connectionIndex; public ProcessContext(final Connectable connectable, final AtomicLong connectionIndex, final ContentRepository contentRepository, final FlowFileRepository flowFileRepository, final FlowFileEventRepository flowFileEventRepository, final CounterRepository counterRepository, final ProvenanceEventRepository provenanceRepository) { this.connectable = connectable; contentRepo = contentRepository; flowFileRepo = flowFileRepository; flowFileEventRepo = flowFileEventRepository; counterRepo = counterRepository; provenanceRepo = provenanceRepository; this.connectionIndex = connectionIndex; } Connectable getConnectable() { return connectable; } /** * * @param relationship relationship * @return connections for relationship */ Collection<Connection> getConnections(final Relationship relationship) { Collection<Connection> collection = connectable.getConnections(relationship); if (collection == null) { collection = new ArrayList<>(); } return Collections.unmodifiableCollection(collection); } /** * @return an unmodifiable list containing a copy of all incoming connections for the processor from which FlowFiles are allowed to be pulled */ List<Connection> getPollableConnections() { if (pollFromSelfLoopsOnly()) { final List<Connection> selfLoops = new ArrayList<>(); for (final Connection connection : connectable.getIncomingConnections()) { if (connection.getSource() == connection.getDestination()) { selfLoops.add(connection); } } return selfLoops; } else { return connectable.getIncomingConnections(); } } private boolean isTriggerWhenAnyDestinationAvailable() { if (connectable.getConnectableType() != ConnectableType.PROCESSOR) { return false; } final ProcessorNode procNode = (ProcessorNode) connectable; return procNode.isTriggerWhenAnyDestinationAvailable(); } /** * @return true if we are allowed to take FlowFiles only from self-loops. This is the case when no Relationships are available except for self-looping Connections */ private boolean pollFromSelfLoopsOnly() { if (isTriggerWhenAnyDestinationAvailable()) { // we can pull from any incoming connection, as long as at least one downstream connection // is available for each relationship. // I.e., we can poll only from self if no relationships are available return !Connectables.anyRelationshipAvailable(connectable); } else { for (final Connection connection : connectable.getConnections()) { // A downstream connection is full. We are only allowed to pull from self-loops. if (connection.getFlowFileQueue().isFull()) { return true; } } } return false; } void adjustCounter(final String name, final long delta) { final String localContext = connectable.getName() + " (" + connectable.getIdentifier() + ")"; final String globalContext = "All " + connectable.getComponentType() + "'s"; counterRepo.adjustCounter(localContext, name, delta); counterRepo.adjustCounter(globalContext, name, delta); } ContentRepository getContentRepository() { return contentRepo; } FlowFileRepository getFlowFileRepository() { return flowFileRepo; } public FlowFileEventRepository getFlowFileEventRepository() { return flowFileEventRepo; } ProvenanceEventRepository getProvenanceRepository() { return provenanceRepo; } long getNextFlowFileSequence() { return flowFileRepo.getNextFlowFileSequence(); } int getNextIncomingConnectionIndex() { final int numIncomingConnections = connectable.getIncomingConnections().size(); return (int) (connectionIndex.getAndIncrement() % Math.max(1, numIncomingConnections)); } public boolean isAnyRelationshipAvailable() { for (final Relationship relationship : getConnectable().getRelationships()) { final Collection<Connection> connections = getConnections(relationship); boolean available = true; for (final Connection connection : connections) { if (connection.getFlowFileQueue().isFull()) { available = false; break; } } if (available) { return true; } } return false; } public int getAvailableRelationshipCount() { int count = 0; for (final Relationship relationship : connectable.getRelationships()) { final Collection<Connection> connections = connectable.getConnections(relationship); if (connections == null || connections.isEmpty()) { count++; } else { boolean available = true; for (final Connection connection : connections) { // consider self-loops available if (connection.getSource() == connection.getDestination()) { continue; } if (connection.getFlowFileQueue().isFull()) { available = false; break; } } if (available) { count++; } } } return count; } /** * A Relationship is said to be Available if and only if all Connections for that Relationship are either self-loops or have non-full queues. * * @param requiredNumber minimum number of relationships that must have availability * @return Checks if at least <code>requiredNumber</code> of Relationationships are "available." If so, returns <code>true</code>, otherwise returns <code>false</code> */ public boolean isRelationshipAvailabilitySatisfied(final int requiredNumber) { int unavailable = 0; final Collection<Relationship> allRelationships = connectable.getRelationships(); final int numRelationships = allRelationships.size(); // the maximum number of Relationships that can be unavailable and still return true. final int maxUnavailable = numRelationships - requiredNumber; for (final Relationship relationship : allRelationships) { final Collection<Connection> connections = connectable.getConnections(relationship); if (connections != null && !connections.isEmpty()) { boolean available = true; for (final Connection connection : connections) { // consider self-loops available if (connection.getSource() == connection.getDestination()) { continue; } if (connection.getFlowFileQueue().isFull()) { available = false; break; } } if (!available) { unavailable++; if (unavailable > maxUnavailable) { return false; } } } } return true; } public Set<Relationship> getAvailableRelationships() { final Set<Relationship> set = new HashSet<>(); for (final Relationship relationship : getConnectable().getRelationships()) { final Collection<Connection> connections = getConnections(relationship); if (connections.isEmpty()) { set.add(relationship); } else { boolean available = true; for (final Connection connection : connections) { if (connection.getFlowFileQueue().isFull()) { available = false; } } if (available) { set.add(relationship); } } } return set; } }