org.apache.nifi.web.controller.ControllerSearchService.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.nifi.web.controller.ControllerSearchService.java

Source

/*
 * 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.web.controller;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserUtils;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.validation.ValidationStatus;
import org.apache.nifi.connectable.Connectable;
import org.apache.nifi.connectable.Connection;
import org.apache.nifi.connectable.Funnel;
import org.apache.nifi.connectable.Port;
import org.apache.nifi.controller.FlowController;
import org.apache.nifi.controller.ProcessorNode;
import org.apache.nifi.controller.ScheduledState;
import org.apache.nifi.controller.queue.FlowFileQueue;
import org.apache.nifi.flowfile.FlowFilePrioritizer;
import org.apache.nifi.groups.ProcessGroup;
import org.apache.nifi.groups.RemoteProcessGroup;
import org.apache.nifi.nar.NarCloseable;
import org.apache.nifi.processor.DataUnit;
import org.apache.nifi.processor.Processor;
import org.apache.nifi.processor.Relationship;
import org.apache.nifi.registry.ComponentVariableRegistry;
import org.apache.nifi.registry.VariableDescriptor;
import org.apache.nifi.registry.VariableRegistry;
import org.apache.nifi.remote.RootGroupPort;
import org.apache.nifi.scheduling.ExecutionNode;
import org.apache.nifi.scheduling.SchedulingStrategy;
import org.apache.nifi.search.SearchContext;
import org.apache.nifi.search.SearchResult;
import org.apache.nifi.search.Searchable;
import org.apache.nifi.web.api.dto.search.ComponentSearchResultDTO;
import org.apache.nifi.web.api.dto.search.SearchResultGroupDTO;
import org.apache.nifi.web.api.dto.search.SearchResultsDTO;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * NiFi web controller's helper service that implements component search.
 */
public class ControllerSearchService {
    private FlowController flowController;
    private Authorizer authorizer;
    private VariableRegistry variableRegistry;

    /**
     * Searches term in the controller beginning from a given process group.
     *
     * @param results Search results
     * @param search The search term
     * @param group The init process group
     */
    public void search(final SearchResultsDTO results, final String search, final ProcessGroup group) {
        final NiFiUser user = NiFiUserUtils.getNiFiUser();

        if (group.isAuthorized(authorizer, RequestAction.READ, user)) {
            final ComponentSearchResultDTO groupMatch = search(search, group);
            if (groupMatch != null) {
                // get the parent group, not the current one
                groupMatch.setParentGroup(buildResultGroup(group.getParent(), user));
                groupMatch.setVersionedGroup(buildVersionedGroup(group.getParent(), user));
                results.getProcessGroupResults().add(groupMatch);
            }
        }

        for (final ProcessorNode procNode : group.getProcessors()) {
            if (procNode.isAuthorized(authorizer, RequestAction.READ, user)) {
                final ComponentSearchResultDTO match = search(search, procNode);
                if (match != null) {
                    match.setGroupId(group.getIdentifier());
                    match.setParentGroup(buildResultGroup(group, user));
                    match.setVersionedGroup(buildVersionedGroup(group, user));
                    results.getProcessorResults().add(match);
                }
            }
        }

        for (final Connection connection : group.getConnections()) {
            if (connection.isAuthorized(authorizer, RequestAction.READ, user)) {
                final ComponentSearchResultDTO match = search(search, connection);
                if (match != null) {
                    match.setGroupId(group.getIdentifier());
                    match.setParentGroup(buildResultGroup(group, user));
                    match.setVersionedGroup(buildVersionedGroup(group, user));
                    results.getConnectionResults().add(match);
                }
            }
        }

        for (final RemoteProcessGroup remoteGroup : group.getRemoteProcessGroups()) {
            if (remoteGroup.isAuthorized(authorizer, RequestAction.READ, user)) {
                final ComponentSearchResultDTO match = search(search, remoteGroup);
                if (match != null) {
                    match.setGroupId(group.getIdentifier());
                    match.setParentGroup(buildResultGroup(group, user));
                    match.setVersionedGroup(buildVersionedGroup(group, user));
                    results.getRemoteProcessGroupResults().add(match);
                }
            }
        }

        for (final Port port : group.getInputPorts()) {
            if (port.isAuthorized(authorizer, RequestAction.READ, user)) {
                final ComponentSearchResultDTO match = search(search, port);
                if (match != null) {
                    match.setGroupId(group.getIdentifier());
                    match.setParentGroup(buildResultGroup(group, user));
                    match.setVersionedGroup(buildVersionedGroup(group, user));
                    results.getInputPortResults().add(match);
                }
            }
        }

        for (final Port port : group.getOutputPorts()) {
            if (port.isAuthorized(authorizer, RequestAction.READ, user)) {
                final ComponentSearchResultDTO match = search(search, port);
                if (match != null) {
                    match.setGroupId(group.getIdentifier());
                    match.setParentGroup(buildResultGroup(group, user));
                    match.setVersionedGroup(buildVersionedGroup(group, user));
                    results.getOutputPortResults().add(match);
                }
            }
        }

        for (final Funnel funnel : group.getFunnels()) {
            if (funnel.isAuthorized(authorizer, RequestAction.READ, user)) {
                final ComponentSearchResultDTO match = search(search, funnel);
                if (match != null) {
                    match.setGroupId(group.getIdentifier());
                    match.setParentGroup(buildResultGroup(group, user));
                    match.setVersionedGroup(buildVersionedGroup(group, user));
                    results.getFunnelResults().add(match);
                }
            }
        }

        for (final ProcessGroup processGroup : group.getProcessGroups()) {
            search(results, search, processGroup);
        }
    }

    private ComponentSearchResultDTO search(final String searchStr, final Port port) {
        final List<String> matches = new ArrayList<>();

        addIfAppropriate(searchStr, port.getIdentifier(), "Id", matches);
        addIfAppropriate(searchStr, port.getVersionedComponentId().orElse(null), "Version Control ID", matches);
        addIfAppropriate(searchStr, port.getName(), "Name", matches);
        addIfAppropriate(searchStr, port.getComments(), "Comments", matches);

        // consider scheduled state
        if (ScheduledState.DISABLED.equals(port.getScheduledState())) {
            if (StringUtils.containsIgnoreCase("disabled", searchStr)) {
                matches.add("Run status: Disabled");
            }
        } else {
            if (StringUtils.containsIgnoreCase("invalid", searchStr) && !port.isValid()) {
                matches.add("Run status: Invalid");
            } else if (ScheduledState.RUNNING.equals(port.getScheduledState())
                    && StringUtils.containsIgnoreCase("running", searchStr)) {
                matches.add("Run status: Running");
            } else if (ScheduledState.STOPPED.equals(port.getScheduledState())
                    && StringUtils.containsIgnoreCase("stopped", searchStr)) {
                matches.add("Run status: Stopped");
            }
        }

        if (port instanceof RootGroupPort) {
            final RootGroupPort rootGroupPort = (RootGroupPort) port;

            // user access controls
            for (final String userAccessControl : rootGroupPort.getUserAccessControl()) {
                addIfAppropriate(searchStr, userAccessControl, "User access control", matches);
            }

            // group access controls
            for (final String groupAccessControl : rootGroupPort.getGroupAccessControl()) {
                addIfAppropriate(searchStr, groupAccessControl, "Group access control", matches);
            }
        }

        if (matches.isEmpty()) {
            return null;
        }

        final ComponentSearchResultDTO dto = new ComponentSearchResultDTO();
        dto.setId(port.getIdentifier());
        dto.setName(port.getName());
        dto.setMatches(matches);
        return dto;
    }

    private ComponentSearchResultDTO search(final String searchStr, final ProcessorNode procNode) {
        final List<String> matches = new ArrayList<>();
        final Processor processor = procNode.getProcessor();

        addIfAppropriate(searchStr, procNode.getIdentifier(), "Id", matches);
        addIfAppropriate(searchStr, procNode.getVersionedComponentId().orElse(null), "Version Control ID", matches);
        addIfAppropriate(searchStr, procNode.getName(), "Name", matches);
        addIfAppropriate(searchStr, procNode.getComments(), "Comments", matches);

        // consider scheduling strategy
        if (SchedulingStrategy.EVENT_DRIVEN.equals(procNode.getSchedulingStrategy())
                && StringUtils.containsIgnoreCase("event", searchStr)) {
            matches.add("Scheduling strategy: Event driven");
        } else if (SchedulingStrategy.TIMER_DRIVEN.equals(procNode.getSchedulingStrategy())
                && StringUtils.containsIgnoreCase("timer", searchStr)) {
            matches.add("Scheduling strategy: Timer driven");
        } else if (SchedulingStrategy.PRIMARY_NODE_ONLY.equals(procNode.getSchedulingStrategy())
                && StringUtils.containsIgnoreCase("primary", searchStr)) {
            // PRIMARY_NODE_ONLY has been deprecated as a SchedulingStrategy and replaced by PRIMARY as an ExecutionNode.
            matches.add("Scheduling strategy: On primary node");
        }

        // consider execution node
        if (ExecutionNode.PRIMARY.equals(procNode.getExecutionNode())
                && StringUtils.containsIgnoreCase("primary", searchStr)) {
            matches.add("Execution node: primary");
        }

        // consider scheduled state
        if (ScheduledState.DISABLED.equals(procNode.getScheduledState())) {
            if (StringUtils.containsIgnoreCase("disabled", searchStr)) {
                matches.add("Run status: Disabled");
            }
        } else {
            if (StringUtils.containsIgnoreCase("invalid", searchStr)
                    && procNode.getValidationStatus() == ValidationStatus.INVALID) {
                matches.add("Run status: Invalid");
            } else if (StringUtils.containsIgnoreCase("validating", searchStr)
                    && procNode.getValidationStatus() == ValidationStatus.VALIDATING) {
                matches.add("Run status: Validating");
            } else if (ScheduledState.RUNNING.equals(procNode.getScheduledState())
                    && StringUtils.containsIgnoreCase("running", searchStr)) {
                matches.add("Run status: Running");
            } else if (ScheduledState.STOPPED.equals(procNode.getScheduledState())
                    && StringUtils.containsIgnoreCase("stopped", searchStr)) {
                matches.add("Run status: Stopped");
            }
        }

        for (final Relationship relationship : procNode.getRelationships()) {
            addIfAppropriate(searchStr, relationship.getName(), "Relationship", matches);
        }

        // Add both the actual class name and the component type. This allows us to search for 'Ghost'
        // to search for components that could not be instantiated.
        addIfAppropriate(searchStr, processor.getClass().getSimpleName(), "Type", matches);
        addIfAppropriate(searchStr, procNode.getComponentType(), "Type", matches);

        for (final Map.Entry<PropertyDescriptor, String> entry : procNode.getProperties().entrySet()) {
            final PropertyDescriptor descriptor = entry.getKey();

            addIfAppropriate(searchStr, descriptor.getName(), "Property name", matches);
            addIfAppropriate(searchStr, descriptor.getDescription(), "Property description", matches);

            // never include sensitive properties values in search results
            if (descriptor.isSensitive()) {
                continue;
            }

            String value = entry.getValue();

            // if unset consider default value
            if (value == null) {
                value = descriptor.getDefaultValue();
            }

            // evaluate if the value matches the search criteria
            if (StringUtils.containsIgnoreCase(value, searchStr)) {
                matches.add("Property value: " + descriptor.getName() + " - " + value);
            }
        }

        // consider searching the processor directly
        if (processor instanceof Searchable) {
            final Searchable searchable = (Searchable) processor;

            final SearchContext context = new StandardSearchContext(searchStr, procNode,
                    flowController.getControllerServiceProvider(), variableRegistry);

            // search the processor using the appropriate thread context classloader
            try (final NarCloseable x = NarCloseable.withComponentNarLoader(flowController.getExtensionManager(),
                    processor.getClass(), processor.getIdentifier())) {
                final Collection<SearchResult> searchResults = searchable.search(context);
                if (CollectionUtils.isNotEmpty(searchResults)) {
                    for (final SearchResult searchResult : searchResults) {
                        matches.add(searchResult.getLabel() + ": " + searchResult.getMatch());
                    }
                }
            } catch (final Throwable t) {
                // log this as error
            }
        }

        if (matches.isEmpty()) {
            return null;
        }

        final ComponentSearchResultDTO result = new ComponentSearchResultDTO();
        result.setId(procNode.getIdentifier());
        result.setMatches(matches);
        result.setName(procNode.getName());
        return result;
    }

    private ComponentSearchResultDTO search(final String searchStr, final ProcessGroup group) {
        final List<String> matches = new ArrayList<>();
        final ProcessGroup parent = group.getParent();
        if (parent == null) {
            return null;
        }

        addIfAppropriate(searchStr, group.getIdentifier(), "Id", matches);
        addIfAppropriate(searchStr, group.getVersionedComponentId().orElse(null), "Version Control ID", matches);
        addIfAppropriate(searchStr, group.getName(), "Name", matches);
        addIfAppropriate(searchStr, group.getComments(), "Comments", matches);

        final ComponentVariableRegistry varRegistry = group.getVariableRegistry();
        if (varRegistry != null) {
            final Map<VariableDescriptor, String> variableMap = varRegistry.getVariableMap();
            for (final Map.Entry<VariableDescriptor, String> entry : variableMap.entrySet()) {
                addIfAppropriate(searchStr, entry.getKey().getName(), "Variable Name", matches);
                addIfAppropriate(searchStr, entry.getValue(), "Variable Value", matches);
            }
        }

        if (matches.isEmpty()) {
            return null;
        }

        final ComponentSearchResultDTO result = new ComponentSearchResultDTO();
        result.setId(group.getIdentifier());
        result.setName(group.getName());
        result.setGroupId(parent.getIdentifier());
        result.setMatches(matches);
        return result;
    }

    private ComponentSearchResultDTO search(final String searchStr, final Connection connection) {
        final List<String> matches = new ArrayList<>();

        // search id and name
        addIfAppropriate(searchStr, connection.getIdentifier(), "Id", matches);
        addIfAppropriate(searchStr, connection.getVersionedComponentId().orElse(null), "Version Control ID",
                matches);
        addIfAppropriate(searchStr, connection.getName(), "Name", matches);

        // search relationships
        for (final Relationship relationship : connection.getRelationships()) {
            addIfAppropriate(searchStr, relationship.getName(), "Relationship", matches);
        }

        // search prioritizers
        final FlowFileQueue queue = connection.getFlowFileQueue();
        for (final FlowFilePrioritizer comparator : queue.getPriorities()) {
            addIfAppropriate(searchStr, comparator.getClass().getName(), "Prioritizer", matches);
        }

        // search expiration
        if (StringUtils.containsIgnoreCase("expires", searchStr)
                || StringUtils.containsIgnoreCase("expiration", searchStr)) {
            final int expirationMillis = connection.getFlowFileQueue().getFlowFileExpiration(TimeUnit.MILLISECONDS);
            if (expirationMillis > 0) {
                matches.add("FlowFile expiration: " + connection.getFlowFileQueue().getFlowFileExpiration());
            }
        }

        // search back pressure
        if (StringUtils.containsIgnoreCase("back pressure", searchStr)
                || StringUtils.containsIgnoreCase("pressure", searchStr)) {
            final String backPressureDataSize = connection.getFlowFileQueue().getBackPressureDataSizeThreshold();
            final Double backPressureBytes = DataUnit.parseDataSize(backPressureDataSize, DataUnit.B);
            if (backPressureBytes > 0) {
                matches.add("Back pressure data size: " + backPressureDataSize);
            }

            final long backPressureCount = connection.getFlowFileQueue().getBackPressureObjectThreshold();
            if (backPressureCount > 0) {
                matches.add("Back pressure count: " + backPressureCount);
            }
        }

        // search the source
        final Connectable source = connection.getSource();
        addIfAppropriate(searchStr, source.getIdentifier(), "Source id", matches);
        addIfAppropriate(searchStr, source.getName(), "Source name", matches);
        addIfAppropriate(searchStr, source.getComments(), "Source comments", matches);

        // search the destination
        final Connectable destination = connection.getDestination();
        addIfAppropriate(searchStr, destination.getIdentifier(), "Destination id", matches);
        addIfAppropriate(searchStr, destination.getName(), "Destination name", matches);
        addIfAppropriate(searchStr, destination.getComments(), "Destination comments", matches);

        if (matches.isEmpty()) {
            return null;
        }

        final ComponentSearchResultDTO result = new ComponentSearchResultDTO();
        result.setId(connection.getIdentifier());

        // determine the name of the search match
        if (StringUtils.isNotBlank(connection.getName())) {
            result.setName(connection.getName());
        } else if (!connection.getRelationships().isEmpty()) {
            final List<String> relationships = new ArrayList<>(connection.getRelationships().size());
            for (final Relationship relationship : connection.getRelationships()) {
                if (StringUtils.isNotBlank(relationship.getName())) {
                    relationships.add(relationship.getName());
                }
            }
            if (!relationships.isEmpty()) {
                result.setName(StringUtils.join(relationships, ", "));
            }
        }

        // ensure a name is added
        if (result.getName() == null) {
            result.setName("From source " + connection.getSource().getName());
        }

        result.setMatches(matches);
        return result;
    }

    private ComponentSearchResultDTO search(final String searchStr, final RemoteProcessGroup group) {
        final List<String> matches = new ArrayList<>();
        addIfAppropriate(searchStr, group.getIdentifier(), "Id", matches);
        addIfAppropriate(searchStr, group.getVersionedComponentId().orElse(null), "Version Control ID", matches);
        addIfAppropriate(searchStr, group.getName(), "Name", matches);
        addIfAppropriate(searchStr, group.getComments(), "Comments", matches);
        addIfAppropriate(searchStr, group.getTargetUris(), "URLs", matches);

        // consider the transmission status
        if ((StringUtils.containsIgnoreCase("transmitting", searchStr)
                || StringUtils.containsIgnoreCase("transmission enabled", searchStr)) && group.isTransmitting()) {
            matches.add("Transmission: On");
        } else if ((StringUtils.containsIgnoreCase("not transmitting", searchStr)
                || StringUtils.containsIgnoreCase("transmission disabled", searchStr)) && !group.isTransmitting()) {
            matches.add("Transmission: Off");
        }

        if (matches.isEmpty()) {
            return null;
        }

        final ComponentSearchResultDTO result = new ComponentSearchResultDTO();
        result.setId(group.getIdentifier());
        result.setName(group.getName());
        result.setMatches(matches);
        return result;
    }

    private ComponentSearchResultDTO search(final String searchStr, final Funnel funnel) {
        final List<String> matches = new ArrayList<>();
        addIfAppropriate(searchStr, funnel.getIdentifier(), "Id", matches);
        addIfAppropriate(searchStr, funnel.getVersionedComponentId().orElse(null), "Version Control ID", matches);

        if (matches.isEmpty()) {
            return null;
        }

        final ComponentSearchResultDTO dto = new ComponentSearchResultDTO();
        dto.setId(funnel.getIdentifier());
        dto.setName(funnel.getName());
        dto.setMatches(matches);
        return dto;
    }

    /**
     * Builds the nearest versioned parent result group for a given user.
     *
     * @param group The containing group
     * @param user The current NiFi user
     * @return Versioned parent group
     */
    private SearchResultGroupDTO buildVersionedGroup(final ProcessGroup group, final NiFiUser user) {
        if (group == null) {
            return null;
        }

        ProcessGroup tmpParent = group.getParent();
        ProcessGroup tmpGroup = group;

        // search for a versioned group by traversing the group tree up to the root
        while (!tmpGroup.isRootGroup()) {
            if (tmpGroup.getVersionControlInformation() != null) {
                return buildResultGroup(tmpGroup, user);
            }

            tmpGroup = tmpParent;
            tmpParent = tmpGroup.getParent();
        }

        // traversed all the way to the root
        return null;
    }

    /**
     * Builds result group for a given user.
     *
     * @param group The containing group
     * @param user The current NiFi user
     * @return Result group
     */
    private SearchResultGroupDTO buildResultGroup(final ProcessGroup group, final NiFiUser user) {
        if (group == null) {
            return null;
        }

        final SearchResultGroupDTO resultGroup = new SearchResultGroupDTO();
        resultGroup.setId(group.getIdentifier());

        // keep the group name confidential
        if (group.isAuthorized(authorizer, RequestAction.READ, user)) {
            resultGroup.setName(group.getName());
        }

        return resultGroup;
    }

    private void addIfAppropriate(final String searchStr, final String value, final String label,
            final List<String> matches) {
        if (StringUtils.containsIgnoreCase(value, searchStr)) {
            matches.add(label + ": " + value);
        }
    }

    public void setFlowController(FlowController flowController) {
        this.flowController = flowController;
    }

    public void setAuthorizer(Authorizer authorizer) {
        this.authorizer = authorizer;
    }

    public void setVariableRegistry(VariableRegistry variableRegistry) {
        this.variableRegistry = variableRegistry;
    }
}