/* * 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.service; import org.apache.nifi.bundle.BundleCoordinate; import org.apache.nifi.components.PropertyDescriptor; import org.apache.nifi.controller.FlowController; import org.apache.nifi.controller.serialization.FlowFromDOMFactory; import org.apache.nifi.encrypt.StringEncryptor; import org.apache.nifi.groups.ProcessGroup; import org.apache.nifi.reporting.BulletinRepository; import org.apache.nifi.util.BundleUtils; import org.apache.nifi.util.DomUtils; import org.apache.nifi.web.api.dto.BundleDTO; import org.apache.nifi.web.api.dto.ControllerServiceDTO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; public class ControllerServiceLoader { private static final Logger logger = LoggerFactory.getLogger(ControllerServiceLoader.class); public static List<ControllerServiceNode> loadControllerServices(final FlowController controller, final InputStream serializedStream, final ProcessGroup parentGroup, final StringEncryptor encryptor, final BulletinRepository bulletinRepo, final boolean autoResumeState) throws IOException { final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); documentBuilderFactory.setNamespaceAware(true); try (final InputStream in = new BufferedInputStream(serializedStream)) { final DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder(); builder.setErrorHandler(new org.xml.sax.ErrorHandler() { @Override public void fatalError(final SAXParseException err) throws SAXException { logger.error("Config file line " + err.getLineNumber() + ", col " + err.getColumnNumber() + ", uri " + err.getSystemId() + " :message: " + err.getMessage()); if (logger.isDebugEnabled()) { logger.error("Error Stack Dump", err); } throw err; } @Override public void error(final SAXParseException err) throws SAXParseException { logger.error("Config file line " + err.getLineNumber() + ", col " + err.getColumnNumber() + ", uri " + err.getSystemId() + " :message: " + err.getMessage()); if (logger.isDebugEnabled()) { logger.error("Error Stack Dump", err); } throw err; } @Override public void warning(final SAXParseException err) throws SAXParseException { logger.warn(" Config file line " + err.getLineNumber() + ", uri " + err.getSystemId() + " : message : " + err.getMessage()); if (logger.isDebugEnabled()) { logger.warn("Warning stack dump", err); } throw err; } }); final Document document = builder.parse(in); final Element controllerServices = document.getDocumentElement(); final List<Element> serviceElements = DomUtils.getChildElementsByTagName(controllerServices, "controllerService"); final Map<ControllerServiceNode, Element> controllerServiceMap = ControllerServiceLoader.loadControllerServices(serviceElements, controller, parentGroup, encryptor); enableControllerServices(controllerServiceMap, controller, encryptor, autoResumeState); return new ArrayList<>(controllerServiceMap.keySet()); } catch (SAXException | ParserConfigurationException sxe) { throw new IOException(sxe); } } public static Map<ControllerServiceNode, Element> loadControllerServices(final List<Element> serviceElements, final FlowController controller, final ProcessGroup parentGroup, final StringEncryptor encryptor) { final Map<ControllerServiceNode, Element> nodeMap = new HashMap<>(); for (final Element serviceElement : serviceElements) { final ControllerServiceNode serviceNode = createControllerService(controller, serviceElement, encryptor); if (parentGroup == null) { controller.addRootControllerService(serviceNode); } else { parentGroup.addControllerService(serviceNode); } // We need to clone the node because it will be used in a separate thread below, and // Element is not thread-safe. nodeMap.put(serviceNode, (Element) serviceElement.cloneNode(true)); } for (final Map.Entry<ControllerServiceNode, Element> entry : nodeMap.entrySet()) { configureControllerService(entry.getKey(), entry.getValue(), encryptor); } return nodeMap; } public static void enableControllerServices(final Map<ControllerServiceNode, Element> nodeMap, final FlowController controller, final StringEncryptor encryptor, final boolean autoResumeState) { // Start services if (autoResumeState) { final Set<ControllerServiceNode> nodesToEnable = new HashSet<>(); for (final ControllerServiceNode node : nodeMap.keySet()) { final Element controllerServiceElement = nodeMap.get(node); final ControllerServiceDTO dto; synchronized (controllerServiceElement.getOwnerDocument()) { dto = FlowFromDOMFactory.getControllerService(controllerServiceElement, encryptor); } final ControllerServiceState state = ControllerServiceState.valueOf(dto.getState()); if (state == ControllerServiceState.ENABLED) { nodesToEnable.add(node); } } enableControllerServices(nodesToEnable, controller, autoResumeState); } } public static void enableControllerServices(final Collection<ControllerServiceNode> nodesToEnable, final FlowController controller, final boolean autoResumeState) { // Start services if (autoResumeState) { controller.enableControllerServices(nodesToEnable); } } public static ControllerServiceNode cloneControllerService(final ControllerServiceProvider provider, final ControllerServiceNode controllerService) { // create a new id for the clone seeded from the original id so that it is consistent in a cluster final UUID id = UUID.nameUUIDFromBytes(controllerService.getIdentifier().getBytes(StandardCharsets.UTF_8)); final ControllerServiceNode clone = provider.createControllerService(controllerService.getCanonicalClassName(), id.toString(), controllerService.getBundleCoordinate(), Collections.emptySet(), false); clone.setName(controllerService.getName()); clone.setComments(controllerService.getComments()); if (controllerService.getProperties() != null) { Map<String,String> properties = new HashMap<>(); for (Map.Entry<PropertyDescriptor, String> propEntry : controllerService.getProperties().entrySet()) { properties.put(propEntry.getKey().getName(), propEntry.getValue()); } clone.setProperties(properties); } return clone; } private static ControllerServiceNode createControllerService(final ControllerServiceProvider provider, final Element controllerServiceElement, final StringEncryptor encryptor) { final ControllerServiceDTO dto = FlowFromDOMFactory.getControllerService(controllerServiceElement, encryptor); BundleCoordinate coordinate; try { coordinate = BundleUtils.getCompatibleBundle(dto.getType(), dto.getBundle()); } catch (final IllegalStateException e) { final BundleDTO bundleDTO = dto.getBundle(); if (bundleDTO == null) { coordinate = BundleCoordinate.UNKNOWN_COORDINATE; } else { coordinate = new BundleCoordinate(bundleDTO.getGroup(), bundleDTO.getArtifact(), bundleDTO.getVersion()); } } final ControllerServiceNode node = provider.createControllerService(dto.getType(), dto.getId(), coordinate, Collections.emptySet(), false); node.setName(dto.getName()); node.setComments(dto.getComments()); return node; } private static void configureControllerService(final ControllerServiceNode node, final Element controllerServiceElement, final StringEncryptor encryptor) { final ControllerServiceDTO dto = FlowFromDOMFactory.getControllerService(controllerServiceElement, encryptor); node.setAnnotationData(dto.getAnnotationData()); node.setProperties(dto.getProperties()); } }