All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.phoebus.applications.alarm.model.xml.XmlModelReader Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * Copyright (c) 2018-2021 Oak Ridge National Laboratory.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *******************************************************************************/
package org.phoebus.applications.alarm.model.xml;

import static org.phoebus.applications.alarm.AlarmSystem.logger;

import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.phoebus.applications.alarm.client.AlarmClientLeaf;
import org.phoebus.applications.alarm.client.AlarmClientNode;
import org.phoebus.applications.alarm.model.TitleDetail;
import org.phoebus.applications.alarm.model.TitleDetailDelay;
import org.phoebus.framework.persistence.XMLUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

/** Creates Alarm System model from XML.
 *  @author Evan Smith
 */
@SuppressWarnings("nls")
public class XmlModelReader
{
    /** Node Types */
    public static final String TAG_CONFIG = "config",
                               TAG_COMPONENT = "component",
                               TAG_PV = "pv";

    /** Misc. */
    public static final String TAG_NAME = "name";

    /** PV Specific */
    public static final String TAG_DESCRIPTION = "description",
                               TAG_ENABLED = "enabled",
                               TAG_LATCHING = "latching",
                               TAG_ANNUNCIATING = "annunciating",
                               TAG_DELAY = "delay",
                               TAG_COUNT = "count",
                               TAG_FILTER = "filter";

    /** PV and Component */
    public static final String TAG_GUIDANCE = "guidance",
                               TAG_DISPLAY = "display",
                               TAG_COMMAND = "command",
                               TAG_ACTIONS = "automated_action";

    /** TitleDetail specific tags. */
    public static final String TAG_TITLE = "title",
                               TAG_DETAILS = "details";

    /** All known PV names and their path, used to check for duplicates */
    private Map pv_names = new HashMap<>();

    private AlarmClientNode root = null;

    /** @return Root of alarm tree */
    public AlarmClientNode getRoot()
    {
        return root;
    }

    /** Parse the xml stream and load the stream into a document.
     *  @param stream XML
     *  @throws Exception on error
     */
    public void load(final InputStream stream) throws Exception
    {
        final DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();

        // Enable xinclude parsing
        docBuilderFactory.setNamespaceAware(true);

        try {
            docBuilderFactory.setFeature("http://apache.org/xml/features/xinclude",
                            true);
        }
        catch (ParserConfigurationException e) {
            System.err.println("could not set parser feature");
        }

        final DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
        final Document doc = docBuilder.parse(stream);


        buildModel(doc);
        // Clear map used to check for duplicates
        pv_names.clear();
    }

    private void buildModel(final Document doc) throws Exception
    {
        // Handle root node.

        doc.getDocumentElement().normalize();
        final Element root_node = doc.getDocumentElement();

        // Check if it's a . If it is collect the child nodes.
        if (!root_node.getNodeName().equals(TAG_CONFIG))
            throw new Exception("Expected " + TAG_CONFIG + " but got " + root_node.getNodeName());

        // Create the root of the model. Parent is null and name must be config.
        root = new AlarmClientNode(null, root_node.getAttribute(TAG_NAME));

        // First add PVs at this level, ..
        for (final Element child : XMLUtil.getChildElements(root_node, TAG_PV))
            processPV(root /* parent */, child);

        // .. when sub-components which again have PVs.
        // This way, duplicate PVs will be detected and ignored at a nested level,
        // keeping those toward the root
        for (final Node child : XMLUtil.getChildElements(root_node, TAG_COMPONENT))
            processComponent(root /* parent */, child);
    }

    private void processComponent(final AlarmClientNode parent, final Node node) throws Exception
    {
        // Name of the new component node.
        String comp_node_name = null;
        final NamedNodeMap attrs = node.getAttributes();

        if (attrs != null)
        {
            // Go through the attributes and find the component name.
            // This list should only ever hold the name attribute or else it will fail the schema.
            // Still best to treat it as if it could have multiple attributes.
            for (int idx = 0; idx < attrs.getLength(); idx++)
            {
                final Node attr = attrs.item(idx);
                final String attr_name = attr.getNodeName();
                if (attr_name.equals(TAG_NAME))
                    comp_node_name = attr.getNodeValue();
            }
        }

        if (comp_node_name == null)
            throw new Exception("Component without name at " + parent.getPathName());

        if (parent.getChild(comp_node_name) != null)
            throw new Exception("Component with duplicate name " + comp_node_name + " at " + parent.getPathName());

        // New component node.
        final AlarmClientNode component = new AlarmClientNode(parent.getPathName(), comp_node_name);
        component.addToParent(parent);

        // This does not refer to XML attributes but instead to the attributes of a model component node.
        processCompAttr(component, node);

        // First add PVs at this level, then sub-components
        for (final Element child : XMLUtil.getChildElements(node, TAG_PV))
            processPV(component/* parent */, child);

        for (final Element child : XMLUtil.getChildElements(node, TAG_COMPONENT))
            processComponent(component /* parent */, child);
    }

    private void processCompAttr(final AlarmClientNode component, final Node node) throws Exception
    {
        ArrayList td = new ArrayList<>();

        for (final Element child : XMLUtil.getChildElements(node, TAG_GUIDANCE))
            td.add(getTD(child));

        if (td.size() > 0)
        {
            component.setGuidance(td);
            td = new ArrayList<>();
        }
        for (final Element child : XMLUtil.getChildElements(node, TAG_DISPLAY))
            td.add(getTD(child));

        if (td.size() > 0)
        {
            component.setDisplays(td);
            td = new ArrayList<>();
        }

        for (final Element child : XMLUtil.getChildElements(node, TAG_COMMAND))
            td.add(getTD(child));

        if (td.size() > 0)
        {
            component.setCommands(td);
            td = new ArrayList<>();
        }

        ArrayList tdd = new ArrayList<>();
        for (final Element child : XMLUtil.getChildElements(node, TAG_ACTIONS))
            tdd.add(getTDD(child));

        if (tdd.size() > 0)
        {
            component.setActions(tdd);
            tdd = new ArrayList<>();
        }
    }

    private void processPV(final AlarmClientNode parent, final Element node) throws Exception
    {
        String pv_node_name = null;
        final NamedNodeMap attrs = node.getAttributes();

        if (attrs != null)
        {
            // Go through the attributes and find the component name.
            // This list should only ever hold the name attribute or else it will fail the schema.
            // Still best to treat it as if it could have multiple attributes.
            for (int idx = 0; idx < attrs.getLength(); idx++)
            {
                final Node attr = attrs.item(idx);
                final String attr_name = attr.getNodeName();
                if (attr_name.equals(TAG_NAME))
                    pv_node_name = attr.getNodeValue();
            }
        }

        if (pv_node_name == null)
            throw new Exception("PV without name at " + parent.getPathName());

        if (parent.getChild(pv_node_name) != null)
            throw new Exception("PV with duplicate name " + pv_node_name + " at " + parent.getPathName());

        // Check if PV is already handled
        final String duplicate_path = pv_names.get(pv_node_name);
        if (duplicate_path != null)
        {
            // PV is already handled elsewhere in the config,
            // so there will be alarms --> warn, but continue
            logger.log(Level.WARNING, "Ignoring duplicate PV " + parent.getPathName() + "/" + pv_node_name + ", already at " + duplicate_path);
            return;
        }
        // Remember to prevent duplicates
        pv_names.put(pv_node_name, parent.getPathName());

        final AlarmClientLeaf pv = new AlarmClientLeaf(parent.getPathName(), pv_node_name);
        pv.addToParent(parent);

        // New XML export always writes these three tags.
        // Legacy XML file only wrote them if false, true, true,
        // i.e. missing tags meant true, false, false.

        String enabled_val = XMLUtil.getChildString(node, TAG_ENABLED).orElse("");

        /* Handle enabling logic. Disable if enable date provided */
        if (! enabled_val.isEmpty()) {
            Pattern pattern = Pattern.compile("true|false", Pattern.CASE_INSENSITIVE);
            Matcher matcher = pattern.matcher(enabled_val);

            if (matcher.matches()) {
                pv.setEnabled(Boolean.parseBoolean(enabled_val));
            } else
            {
                try {
                    final LocalDateTime expiration_date = LocalDateTime.parse(enabled_val);
                    logger.log(Level.WARNING, enabled_val);
                    pv.setEnabledDate(expiration_date);
                }
                catch (Exception ex) {
                    logger.log(Level.WARNING, "Bypass date incorrectly formatted." +  enabled_val + "'");
                    ex.printStackTrace(System.out);
                }
            }
        }
        else {
            /* Default true */
            pv.setEnabled(true);
        }


        pv.setLatching(XMLUtil.getChildBoolean(node, TAG_LATCHING).orElse(false));
        pv.setAnnunciating(XMLUtil.getChildBoolean(node, TAG_ANNUNCIATING).orElse(false));

        XMLUtil.getChildString(node, TAG_DESCRIPTION).ifPresent(pv::setDescription);

        final String delayStr = XMLUtil.getChildString(node, TAG_DELAY).orElse("");
        if (delayStr.equals(""))
            pv.setDelay(0);
        else
        {
            try
            {
                final Double tmp = Double.parseDouble(delayStr);
                final Integer delay = tmp.intValue();
                pv.setDelay(delay);
            }
            catch (final NumberFormatException e)
            {
                pv.setDelay(0);
            }
        }

        XMLUtil.getChildInteger(node, TAG_COUNT).ifPresent(pv::setCount);
        XMLUtil.getChildString(node, TAG_FILTER).ifPresent(pv::setFilter);

        ArrayList td = new ArrayList<>();

        for (final Element child : XMLUtil.getChildElements(node, TAG_GUIDANCE))
            td.add(getTD(child));

        if (td.size() > 0)
        {
            pv.setGuidance(td);
            td = new ArrayList<>();
        }

        for (final Element child : XMLUtil.getChildElements(node, TAG_DISPLAY))
            td.add(getTD(child));

        if (td.size() > 0)
        {
            pv.setDisplays(td);
            td = new ArrayList<>();
        }

        for (final Element child : XMLUtil.getChildElements(node, TAG_COMMAND))
            td.add(getTD(child));

        if (td.size() > 0)
        {
            pv.setCommands(td);
            td = new ArrayList<>();
        }

        ArrayList tdd = new ArrayList<>();
        for (final Element child : XMLUtil.getChildElements(node, TAG_ACTIONS))
            tdd.add(getTDD(child));

        if (tdd.size() > 0)
        {
            pv.setActions(tdd);
            tdd = new ArrayList<>();
        }
    }

    private TitleDetail getTD(final Element node)
    {
        final String title = XMLUtil.getChildString(node, TAG_TITLE).orElse("");
        final String detail = XMLUtil.getChildString(node, TAG_DETAILS).orElse("");
        return new TitleDetail(title, detail);
    }

    private TitleDetailDelay getTDD(final Element node) throws Exception
    {
        final String  title  = XMLUtil.getChildString(node, TAG_TITLE).orElse("");
        final String  detail = XMLUtil.getChildString(node, TAG_DETAILS).orElse("");
        final int delay  = XMLUtil.getChildInteger(node, TAG_DELAY).orElse(0);
        return new TitleDetailDelay(title, detail, delay);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy