/*
* 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.cluster.manager;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.controller.service.ControllerServiceState;
import org.apache.nifi.web.api.dto.ControllerServiceDTO;
import org.apache.nifi.web.api.dto.ControllerServiceReferencingComponentDTO;
import org.apache.nifi.web.api.dto.PermissionsDTO;
import org.apache.nifi.web.api.dto.PropertyDescriptorDTO;
import org.apache.nifi.web.api.entity.ControllerServiceEntity;
import org.apache.nifi.web.api.entity.ControllerServiceReferencingComponentEntity;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class ControllerServiceEntityMerger implements ComponentEntityMerger<ControllerServiceEntity> {
/**
* Merges the ControllerServiceEntity responses.
*
* @param clientEntity the entity being returned to the client
* @param entityMap all node responses
*/
@Override
public void mergeComponents(final ControllerServiceEntity clientEntity, final Map<NodeIdentifier, ControllerServiceEntity> entityMap) {
final ControllerServiceDTO clientDto = clientEntity.getComponent();
final Map<NodeIdentifier, ControllerServiceDTO> dtoMap = new HashMap<>();
for (final Map.Entry<NodeIdentifier, ControllerServiceEntity> entry : entityMap.entrySet()) {
final ControllerServiceEntity nodeControllerServiceEntity = entry.getValue();
final ControllerServiceDTO nodeControllerServiceDto = nodeControllerServiceEntity.getComponent();
dtoMap.put(entry.getKey(), nodeControllerServiceDto);
}
mergeDtos(clientDto, dtoMap);
}
private static void mergeDtos(final ControllerServiceDTO clientDto, final Map<NodeIdentifier, ControllerServiceDTO> dtoMap) {
// if unauthorized for the client dto, simple return
if (clientDto == null) {
return;
}
final Map<String, Set<NodeIdentifier>> validationErrorMap = new HashMap<>();
final Set<ControllerServiceReferencingComponentEntity> referencingComponents = clientDto.getReferencingComponents();
final Map<NodeIdentifier, Set<ControllerServiceReferencingComponentEntity>> nodeReferencingComponentsMap = new HashMap<>();
final Map<String, Map<NodeIdentifier, PropertyDescriptorDTO>> propertyDescriptorMap = new HashMap<>();
String state = null;
for (final Map.Entry<NodeIdentifier, ControllerServiceDTO> nodeEntry : dtoMap.entrySet()) {
final ControllerServiceDTO nodeControllerService = nodeEntry.getValue();
// consider the node controller service if authorized
if (nodeControllerService != null) {
final NodeIdentifier nodeId = nodeEntry.getKey();
if (state == null) {
if (ControllerServiceState.DISABLING.name().equals(nodeControllerService.getState())) {
state = ControllerServiceState.DISABLING.name();
} else if (ControllerServiceState.ENABLING.name().equals(nodeControllerService.getState())) {
state = ControllerServiceState.ENABLING.name();
}
}
nodeReferencingComponentsMap.put(nodeId, nodeControllerService.getReferencingComponents());
// merge the validation errors
ErrorMerger.mergeErrors(validationErrorMap, nodeId, nodeControllerService.getValidationErrors());
// aggregate the property descriptors
final Map<String, PropertyDescriptorDTO> descriptors = nodeControllerService.getDescriptors();
if (descriptors != null) {
descriptors.values().stream().forEach(propertyDescriptor -> {
propertyDescriptorMap.computeIfAbsent(propertyDescriptor.getName(), nodeIdToPropertyDescriptor -> new HashMap<>()).put(nodeId, propertyDescriptor);
});
}
}
}
// merge property descriptors
for (Map<NodeIdentifier, PropertyDescriptorDTO> propertyDescriptorByNodeId : propertyDescriptorMap.values()) {
final Collection<PropertyDescriptorDTO> nodePropertyDescriptors = propertyDescriptorByNodeId.values();
if (!nodePropertyDescriptors.isEmpty()) {
// get the name of the property descriptor and find that descriptor being returned to the client
final PropertyDescriptorDTO propertyDescriptor = nodePropertyDescriptors.iterator().next();
final PropertyDescriptorDTO clientPropertyDescriptor = clientDto.getDescriptors().get(propertyDescriptor.getName());
PropertyDescriptorDtoMerger.merge(clientPropertyDescriptor, propertyDescriptorByNodeId);
}
}
// merge the referencing components
mergeControllerServiceReferences(referencingComponents, nodeReferencingComponentsMap);
// store the 'transition' state is applicable
if (state != null) {
clientDto.setState(state);
}
// set the merged the validation errors
clientDto.setValidationErrors(ErrorMerger.normalizedMergedErrors(validationErrorMap, dtoMap.size()));
}
public static void mergeControllerServiceReferences(final Set<ControllerServiceReferencingComponentEntity> referencingComponents,
final Map<NodeIdentifier, Set<ControllerServiceReferencingComponentEntity>> referencingComponentMap) {
final Map<String, Integer> activeThreadCounts = new HashMap<>();
final Map<String, String> states = new HashMap<>();
final Map<String, PermissionsDTO> canReads = new HashMap<>();
for (final Map.Entry<NodeIdentifier, Set<ControllerServiceReferencingComponentEntity>> nodeEntry : referencingComponentMap.entrySet()) {
final Set<ControllerServiceReferencingComponentEntity> nodeReferencingComponents = nodeEntry.getValue();
// go through all the nodes referencing components
if (nodeReferencingComponents != null) {
for (final ControllerServiceReferencingComponentEntity nodeReferencingComponentEntity : nodeReferencingComponents) {
final ControllerServiceReferencingComponentDTO nodeReferencingComponent = nodeReferencingComponentEntity.getComponent();
if (nodeReferencingComponentEntity.getPermissions().getCanRead()) {
// handle active thread counts
if (nodeReferencingComponent.getActiveThreadCount() != null && nodeReferencingComponent.getActiveThreadCount() > 0) {
final Integer current = activeThreadCounts.get(nodeReferencingComponent.getId());
if (current == null) {
activeThreadCounts.put(nodeReferencingComponent.getId(), nodeReferencingComponent.getActiveThreadCount());
} else {
activeThreadCounts.put(nodeReferencingComponent.getId(), nodeReferencingComponent.getActiveThreadCount() + current);
}
}
// handle controller service state
final String state = states.get(nodeReferencingComponent.getId());
if (state == null) {
if (ControllerServiceState.DISABLING.name().equals(nodeReferencingComponent.getState())) {
states.put(nodeReferencingComponent.getId(), ControllerServiceState.DISABLING.name());
} else if (ControllerServiceState.ENABLING.name().equals(nodeReferencingComponent.getState())) {
states.put(nodeReferencingComponent.getId(), ControllerServiceState.ENABLING.name());
}
}
}
// handle read permissions
final PermissionsDTO mergedPermissions = canReads.get(nodeReferencingComponentEntity.getId());
final PermissionsDTO permissions = nodeReferencingComponentEntity.getPermissions();
if (permissions != null) {
if (mergedPermissions == null) {
canReads.put(nodeReferencingComponentEntity.getId(), permissions);
} else {
PermissionsDtoMerger.mergePermissions(mergedPermissions, permissions);
}
}
}
}
}
// go through each referencing components
if (referencingComponents != null) {
for (final ControllerServiceReferencingComponentEntity referencingComponent : referencingComponents) {
final PermissionsDTO permissions = canReads.get(referencingComponent.getId());
if (permissions != null && permissions.getCanRead() != null && permissions.getCanRead()) {
final Integer activeThreadCount = activeThreadCounts.get(referencingComponent.getId());
if (activeThreadCount != null) {
referencingComponent.getComponent().setActiveThreadCount(activeThreadCount);
}
final String state = states.get(referencingComponent.getId());
if (state != null) {
referencingComponent.getComponent().setState(state);
}
final Map<NodeIdentifier, ControllerServiceReferencingComponentEntity> nodeEntities = new HashMap<>();
referencingComponentMap.entrySet().forEach(entry -> {
final NodeIdentifier nodeIdentifier = entry.getKey();
final Set<ControllerServiceReferencingComponentEntity> nodeControllerServiceReferencingComponents = entry.getValue();
nodeControllerServiceReferencingComponents.forEach(nodeControllerServiceReferencingComponent -> {
if (referencingComponent.getId() != null && referencingComponent.getId().equals(nodeControllerServiceReferencingComponent.getId())) {
nodeEntities.put(nodeIdentifier, nodeControllerServiceReferencingComponent);
}
});
});
mergeControllerServiceReferencingComponent(referencingComponent, nodeEntities);
} else {
referencingComponent.setPermissions(permissions);
referencingComponent.setComponent(null);
}
}
}
}
private static void mergeControllerServiceReferencingComponent(ControllerServiceReferencingComponentEntity clientEntity, Map<NodeIdentifier,
ControllerServiceReferencingComponentEntity> nodeEntities) {
final Map<String, Map<NodeIdentifier, PropertyDescriptorDTO>> propertyDescriptorMap = new HashMap<>();
final Map<NodeIdentifier, Set<ControllerServiceReferencingComponentEntity>> nodeReferencingComponentsMap = new HashMap<>();
// aggregate the property descriptors
for (Map.Entry<NodeIdentifier, ControllerServiceReferencingComponentEntity> entry : nodeEntities.entrySet()) {
final NodeIdentifier nodeIdentifier = entry.getKey();
final ControllerServiceReferencingComponentEntity nodeEntity = entry.getValue();
nodeEntity.getComponent().getDescriptors().values().stream().forEach(propertyDescriptor -> {
propertyDescriptorMap.computeIfAbsent(propertyDescriptor.getName(), nodeIdToPropertyDescriptor -> new HashMap<>()).put(nodeIdentifier, propertyDescriptor);
});
nodeReferencingComponentsMap.put(nodeIdentifier, nodeEntity.getComponent().getReferencingComponents());
}
// merge property descriptors
for (Map<NodeIdentifier, PropertyDescriptorDTO> propertyDescriptorByNodeId : propertyDescriptorMap.values()) {
final Collection<PropertyDescriptorDTO> nodePropertyDescriptors = propertyDescriptorByNodeId.values();
if (!nodePropertyDescriptors.isEmpty()) {
// get the name of the property descriptor and find that descriptor being returned to the client
final PropertyDescriptorDTO propertyDescriptor = nodePropertyDescriptors.iterator().next();
final PropertyDescriptorDTO clientPropertyDescriptor = clientEntity.getComponent().getDescriptors().get(propertyDescriptor.getName());
PropertyDescriptorDtoMerger.merge(clientPropertyDescriptor, propertyDescriptorByNodeId);
}
}
final Set<ControllerServiceReferencingComponentEntity> referencingComponents = clientEntity.getComponent().getReferencingComponents();
mergeControllerServiceReferences(referencingComponents, nodeReferencingComponentsMap);
}
}