package com.thinkbiganalytics.feedmgr.service.template; /*- * #%L * thinkbig-feed-manager-controller * %% * Copyright (C) 2017 ThinkBig Analytics * %% * Licensed 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. * #L% */ import com.thinkbiganalytics.feedmgr.nifi.cache.NifiFlowCache; import com.thinkbiganalytics.feedmgr.rest.model.RegisteredTemplate; import com.thinkbiganalytics.feedmgr.rest.model.RegisteredTemplateRequest; import com.thinkbiganalytics.feedmgr.rest.model.ReusableTemplateConnectionInfo; import com.thinkbiganalytics.feedmgr.security.FeedServicesAccessControl; import com.thinkbiganalytics.feedmgr.service.security.SecurityService; import com.thinkbiganalytics.metadata.api.MetadataAccess; import com.thinkbiganalytics.metadata.api.event.MetadataChange; import com.thinkbiganalytics.metadata.api.event.MetadataEventService; import com.thinkbiganalytics.metadata.api.event.template.TemplateChange; import com.thinkbiganalytics.metadata.api.event.template.TemplateChangeEvent; import com.thinkbiganalytics.metadata.api.template.FeedManagerTemplate; import com.thinkbiganalytics.metadata.api.template.FeedManagerTemplateProvider; import com.thinkbiganalytics.metadata.api.template.security.TemplateAccessControl; import com.thinkbiganalytics.nifi.feedmgr.TemplateCreationHelper; import com.thinkbiganalytics.nifi.rest.client.LegacyNifiRestClient; import com.thinkbiganalytics.nifi.rest.model.NiFiPropertyDescriptorTransform; import com.thinkbiganalytics.nifi.rest.model.NifiProperty; import com.thinkbiganalytics.nifi.rest.model.flow.NifiFlowProcessGroup; import com.thinkbiganalytics.nifi.rest.support.NifiConnectionUtil; import com.thinkbiganalytics.security.AccessController; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.web.api.dto.ConnectableDTO; import org.apache.nifi.web.api.dto.ConnectionDTO; import org.apache.nifi.web.api.dto.PortDTO; import org.apache.nifi.web.api.dto.ProcessGroupDTO; import org.apache.nifi.web.api.dto.ProcessorDTO; import org.apache.nifi.web.api.dto.TemplateDTO; import org.joda.time.DateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.context.SecurityContextHolder; import java.security.Principal; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.function.Function; import java.util.stream.Collectors; import javax.inject.Inject; /** */ public class DefaultFeedManagerTemplateService implements FeedManagerTemplateService { private static final Logger log = LoggerFactory.getLogger(DefaultFeedManagerTemplateService.class); @Inject FeedManagerTemplateProvider templateProvider; @Inject TemplateModelTransform templateModelTransform; @Inject MetadataAccess metadataAccess; @Inject NifiFlowCache nifiFlowCache; @Inject private MetadataEventService metadataEventService; @Inject private AccessController accessController; @Inject private NiFiPropertyDescriptorTransform propertyDescriptorTransform; @Inject private LegacyNifiRestClient nifiRestClient; @Inject private RegisteredTemplateService registeredTemplateService; @Inject RegisteredTemplateUtil registeredTemplateUtil; @Inject private SecurityService securityService; protected RegisteredTemplate saveRegisteredTemplate(final RegisteredTemplate registeredTemplate) { return registeredTemplateService.saveRegisteredTemplate(registeredTemplate); } /** * pass in the Template Ids in Order */ public void orderTemplates(List<String> orderedTemplateIds, Set<String> exclude) { registeredTemplateService.orderTemplates(orderedTemplateIds, exclude); } @Override public List<RegisteredTemplate.Processor> getRegisteredTemplateProcessors(String templateId, boolean includeReusableProcessors) { List<RegisteredTemplate.Processor> processorProperties = new ArrayList<>(); RegisteredTemplate template = registeredTemplateService.findRegisteredTemplate(new RegisteredTemplateRequest.Builder().templateId(templateId).nifiTemplateId(templateId).includeAllProperties(true).build()); if (template != null) { template.initializeProcessors(); processorProperties.addAll(template.getInputProcessors()); processorProperties.addAll(template.getNonInputProcessors()); if (includeReusableProcessors && template.getReusableTemplateConnections() != null && !template.getReusableTemplateConnections().isEmpty()) { //1 fetch ports in reusable templates Map<String, PortDTO> reusableTemplateInputPorts = new HashMap<>(); Set<PortDTO> ports = getReusableFeedInputPorts(); if (ports != null) { ports.stream().forEach(portDTO -> reusableTemplateInputPorts.put(portDTO.getName(), portDTO)); } //match to the name List<String> matchingPortIds = template.getReusableTemplateConnections().stream().filter(conn -> reusableTemplateInputPorts.containsKey(conn.getReusableTemplateInputPortName())) .map(reusableTemplateConnectionInfo -> reusableTemplateInputPorts.get(reusableTemplateConnectionInfo.getReusableTemplateInputPortName()).getId()) .collect(Collectors.toList()); List<RegisteredTemplate.Processor> reusableProcessors = getReusableTemplateProcessorsForInputPorts(matchingPortIds); processorProperties.addAll(reusableProcessors); } } return processorProperties; } /** * Ensures that the {@code RegisteredTemplate#inputProcessors} list is populated not only with the processors which were defined as having user inputs, but also those that done require any input */ public void ensureRegisteredTemplateInputProcessors(RegisteredTemplate registeredTemplate) { registeredTemplateService.ensureRegisteredTemplateInputProcessors(registeredTemplate); } @Override public RegisteredTemplate registerTemplate(RegisteredTemplate registeredTemplate) { boolean isNew = StringUtils.isBlank(registeredTemplate.getId()); RegisteredTemplate template = saveRegisteredTemplate(registeredTemplate); if (template.isUpdated()) { nifiFlowCache.updateRegisteredTemplate(template,true); //only allow update to the Access control if the user has the CHANGE_PERMS permission on this template entity if (accessController.isEntityAccessControlled() && template.hasAction(TemplateAccessControl.CHANGE_PERMS.getSystemName())) { //update the access control registeredTemplate.toRoleMembershipChangeList().stream().forEach(roleMembershipChange -> securityService.changeTemplateRoleMemberships(template.getId(), roleMembershipChange)); } //notify audit of the change FeedManagerTemplate.State state = FeedManagerTemplate.State.valueOf(template.getState()); FeedManagerTemplate.ID id = templateProvider.resolveId(registeredTemplate.getId()); MetadataChange.ChangeType changeType = isNew ? MetadataChange.ChangeType.CREATE : MetadataChange.ChangeType.UPDATE; notifyTemplateStateChange(registeredTemplate, id, state, changeType); } return template; } public boolean deleteRegisteredTemplate(final String templateId) { return metadataAccess.commit(() -> { this.accessController.checkPermission(AccessController.SERVICES, FeedServicesAccessControl.EDIT_TEMPLATES); FeedManagerTemplate.ID domainId = templateProvider.resolveId(templateId); return templateProvider.deleteTemplate(domainId); }); } public RegisteredTemplate findRegisteredTemplateByName(final String templateName){ return registeredTemplateService.findRegisteredTemplate(RegisteredTemplateRequest.requestByTemplateName(templateName)); } public RegisteredTemplate getRegisteredTemplate(final String templateId) { return registeredTemplateService.findRegisteredTemplate(RegisteredTemplateRequest.requestByTemplateId(templateId)); } @Override public List<RegisteredTemplate> getRegisteredTemplates() { return registeredTemplateService.getRegisteredTemplates(); } @Override public RegisteredTemplate enableTemplate(String templateId) { return metadataAccess.commit(() -> { this.accessController.checkPermission(AccessController.SERVICES, FeedServicesAccessControl.ADMIN_TEMPLATES); FeedManagerTemplate.ID domainId = templateProvider.resolveId(templateId); if (domainId != null) { FeedManagerTemplate template = templateProvider.enable(domainId); if (template != null) { return templateModelTransform.domainToRegisteredTemplate(template); } } return null; }); } @Override public RegisteredTemplate disableTemplate(String templateId) { return metadataAccess.commit(() -> { this.accessController.checkPermission(AccessController.SERVICES, FeedServicesAccessControl.ADMIN_TEMPLATES); FeedManagerTemplate.ID domainId = templateProvider.resolveId(templateId); if (domainId != null) { FeedManagerTemplate template = templateProvider.disable(domainId); if (template != null) { return templateModelTransform.domainToRegisteredTemplate(template); } } return null; }); } /** * update audit information for template changes * * @param template the template * @param templateId the template id * @param state the new state * @param changeType the type of change */ private void notifyTemplateStateChange(RegisteredTemplate template, FeedManagerTemplate.ID templateId, FeedManagerTemplate.State state, MetadataChange.ChangeType changeType) { final Principal principal = SecurityContextHolder.getContext().getAuthentication() != null ? SecurityContextHolder.getContext().getAuthentication() : null; TemplateChange change = new TemplateChange(changeType, template != null ? template.getTemplateName() : "", templateId, state); TemplateChangeEvent event = new TemplateChangeEvent(change, DateTime.now(), principal); metadataEventService.notify(event); } /** * Return properties registered for a template * * @param templateId a RegisteredTemplate id * @return the properties registered for the template */ public List<NifiProperty> getTemplateProperties(String templateId) { List<NifiProperty> list = new ArrayList<>(); RegisteredTemplate template = registeredTemplateService.findRegisteredTemplate(RegisteredTemplateRequest.requestByTemplateId(templateId)); if (template != null) { list = template.getProperties(); } return list; } /** * Return the processors in RegisteredTemplate that are input processors ( processors without any incoming connections). * This will call out to NiFi to inspect and obtain the NiFi template if it doesn't exist on the registeredTemplate * * @param registeredTemplate the template to inspect * @return the processors in RegisteredTemplate that are input processors without any incoming connections */ public List<RegisteredTemplate.Processor> getInputProcessorsInNifTemplate(RegisteredTemplate registeredTemplate) { return registeredTemplateService.getInputProcessorsInNifTemplate(registeredTemplate); } /** * Return the input processors (processors without any incoming connections) in a NiFi template object * * @param nifiTemplate the NiFi template * @return the input processors (processors without any incoming connections) in a NiFi template object */ public List<RegisteredTemplate.Processor> getInputProcessorsInNifTemplate(TemplateDTO nifiTemplate) { return registeredTemplateService.getInputProcessorsInNifTemplate(nifiTemplate); } /** * @return all input ports under the {@link TemplateCreationHelper#REUSABLE_TEMPLATES_PROCESS_GROUP_NAME} process group */ public Set<PortDTO> getReusableFeedInputPorts() { Set<PortDTO> ports = new HashSet<>(); ProcessGroupDTO processGroup = nifiRestClient.getProcessGroupByName("root", TemplateCreationHelper.REUSABLE_TEMPLATES_PROCESS_GROUP_NAME); if (processGroup != null) { //fetch the ports Set<PortDTO> inputPortsEntity = nifiRestClient.getInputPorts(processGroup.getId()); if (inputPortsEntity != null && !inputPortsEntity.isEmpty()) { ports.addAll(inputPortsEntity); } } return ports; } /** * Return a list of Processors and their properties for the incoming template * * @param nifiTemplateId a NiFi template id * @return a list of Processors and their properties for the incoming template */ public List<RegisteredTemplate.Processor> getNiFiTemplateProcessorsWithProperties(String nifiTemplateId) { Set<ProcessorDTO> processorDTOs = nifiRestClient.getProcessorsForTemplate(nifiTemplateId); List<RegisteredTemplate.Processor> processorProperties = processorDTOs.stream().map(processorDTO -> registeredTemplateUtil.toRegisteredTemplateProcessor(processorDTO, true)).collect(Collectors.toList()); return processorProperties; } /** * For a given Template and its related connection info to the reusable templates, walk the graph to return the Processors. * The system will first walk the incoming templateid. If the {@code connectionInfo} parameter is set it will make the connections to the incoming template and continue walking those processors * * @param nifiTemplateId the NiFi templateId required to start walking the flow * @param connectionInfo the connections required to connect * @return a list of all the processors for a template and possible connections */ public List<RegisteredTemplate.FlowProcessor> getNiFiTemplateFlowProcessors(String nifiTemplateId, List<ReusableTemplateConnectionInfo> connectionInfo) { TemplateDTO templateDTO = nifiRestClient.getTemplateById(nifiTemplateId); //make the connection if (connectionInfo != null && !connectionInfo.isEmpty()) { Set<PortDTO> templatePorts = templateDTO.getSnippet().getOutputPorts(); Map<String, PortDTO> outputPorts = templateDTO.getSnippet().getOutputPorts().stream().collect(Collectors.toMap(portDTO -> portDTO.getName(), Function.identity())); Map<String, PortDTO> inputPorts = getReusableFeedInputPorts().stream().collect(Collectors.toMap(portDTO -> portDTO.getName(), Function.identity())); connectionInfo.stream().forEach(reusableTemplateConnectionInfo -> { PortDTO outputPort = outputPorts.get(reusableTemplateConnectionInfo.getFeedOutputPortName()); PortDTO inputPort = inputPorts.get(reusableTemplateConnectionInfo.getReusableTemplateInputPortName()); ConnectionDTO connectionDTO = new ConnectionDTO(); ConnectableDTO source = new ConnectableDTO(); source.setName(reusableTemplateConnectionInfo.getFeedOutputPortName()); source.setType(outputPort.getType()); source.setId(outputPort.getId()); source.setGroupId(outputPort.getParentGroupId()); ConnectableDTO dest = new ConnectableDTO(); dest.setName(inputPort.getName()); dest.setType(inputPort.getType()); dest.setId(inputPort.getId()); dest.setGroupId(inputPort.getParentGroupId()); connectionDTO.setSource(source); connectionDTO.setDestination(dest); connectionDTO.setId(UUID.randomUUID().toString()); templateDTO.getSnippet().getConnections().add(connectionDTO); }); } NifiFlowProcessGroup template = nifiRestClient.getTemplateFeedFlow(templateDTO); return template.getProcessorMap().values().stream().map(flowProcessor -> { RegisteredTemplate.FlowProcessor p = new RegisteredTemplate.FlowProcessor(flowProcessor.getId()); p.setGroupId(flowProcessor.getParentGroupId()); p.setType(flowProcessor.getType()); p.setName(flowProcessor.getName()); p.setFlowId(flowProcessor.getFlowId()); p.setIsLeaf(flowProcessor.isLeaf()); return p; }).collect(Collectors.toList()); } /** * Return all the processors that are connected to a given NiFi input port * * @param inputPortIds the ports to inspect * @return all the processors that are connected to a given NiFi input port */ public List<RegisteredTemplate.Processor> getReusableTemplateProcessorsForInputPorts(List<String> inputPortIds) { Set<ProcessorDTO> processorDTOs = new HashSet<>(); if (inputPortIds != null && !inputPortIds.isEmpty()) { ProcessGroupDTO processGroup = nifiRestClient.getProcessGroupByName("root", TemplateCreationHelper.REUSABLE_TEMPLATES_PROCESS_GROUP_NAME); if (processGroup != null) { //fetch the Content ProcessGroupDTO content = nifiRestClient.getProcessGroup(processGroup.getId(), true, true); processGroup.setContents(content.getContents()); Set<PortDTO> ports = getReusableFeedInputPorts(); ports.stream() .filter(portDTO -> inputPortIds.contains(portDTO.getId())) .forEach(port -> { List<ConnectionDTO> connectionDTOs = NifiConnectionUtil.findConnectionsMatchingSourceId(processGroup.getContents().getConnections(), port.getId()); if (connectionDTOs != null) { connectionDTOs.stream().forEach(connectionDTO -> { String processGroupId = connectionDTO.getDestination().getGroupId(); Set<ProcessorDTO> processors = nifiRestClient.getProcessorsForFlow(processGroupId); if (processors != null) { processorDTOs.addAll(processors); } }); } }); } } List<RegisteredTemplate.Processor> processorProperties = processorDTOs.stream().map(processorDTO -> registeredTemplateUtil.toRegisteredTemplateProcessor(processorDTO, true)).collect(Collectors.toList()); return processorProperties; } }