/**********************************************************************
 * Copyright (c) 2014, 2015 Ericsson
 *
 * All rights reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public License 2.0 which
 * accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Jonathan Rajotte - Initial support for machine interface lttng 2.6
 *   Bernd Hufmann - Fix check for live session
 **********************************************************************/

package org.eclipse.tracecompass.internal.lttng2.control.ui.views.service;

import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;

import java.io.IOException;
import java.io.StringReader;
import java.math.BigInteger;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.validation.SchemaFactory;

import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.osgi.util.NLS;
import org.eclipse.tracecompass.common.core.xml.XmlUtils;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.IBaseEventInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.IChannelInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.IDomainInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.IEventInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.IFieldInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.ILoggerInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.IProbeEventInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.ISessionInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.ISnapshotInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.IUstProviderInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.LogLevelType;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.TraceDomainType;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.TraceEnablement;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.TraceEventType;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.TraceJulLogLevel;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.TraceLog4jLogLevel;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.TraceLogLevel;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.TracePythonLogLevel;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.BaseEventInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.BufferType;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.ChannelInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.DomainInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.EventInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.FieldInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.LoggerInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.ProbeEventInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.SessionInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.SnapshotInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.UstProviderInfo;
import org.eclipse.tracecompass.internal.lttng2.control.ui.Activator;
import org.eclipse.tracecompass.internal.lttng2.control.ui.views.handlers.XmlMiValidationErrorHandler;
import org.eclipse.tracecompass.internal.lttng2.control.ui.views.messages.Messages;
import org.eclipse.tracecompass.tmf.remote.core.shell.ICommandInput;
import org.eclipse.tracecompass.tmf.remote.core.shell.ICommandResult;
import org.eclipse.tracecompass.tmf.remote.core.shell.ICommandShell;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * Service for sending LTTng trace control commands to remote host via machine
 * interface mode.
 *
 * @author Jonathan Rajotte
 */
public class LTTngControlServiceMI extends LTTngControlService {

    /**
     * The tracing key (.options) and System property to control whether or not schema validation should be used.
     */
    public static final String MI_SCHEMA_VALIDATION_KEY = Activator.PLUGIN_ID + "/mi/schema-validation"; //$NON-NLS-1$

    // ------------------------------------------------------------------------
    // Attributes
    // ------------------------------------------------------------------------

    private final DocumentBuilder fDocumentBuilder;

    // ------------------------------------------------------------------------
    // Constructors
    // ------------------------------------------------------------------------

    /**
     * Constructor
     *
     * @param shell
     *            the command shell implementation to use
     * @param version
     *            the lttng version
     * @throws ExecutionException
     *             if the creation of the Schema and DocumentBuilder objects
     *             fails
     */
    public LTTngControlServiceMI(@NonNull ICommandShell shell, @Nullable LttngVersion version) throws ExecutionException {
        super(shell);
        setVersion(version);

        DocumentBuilderFactory docBuilderFactory = XmlUtils.newSafeDocumentBuilderFactory();
        docBuilderFactory.setExpandEntityReferences(false);
        docBuilderFactory.setValidating(false);

        // TODO: remove check for 2.11 when new schema is available
        if (isSchemaValidationEnabled() && (version != null && (version.compareTo(new LttngVersion(2, 11, 0, null, null, null, null, null, null)) < 0))) {
            SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
            try {
                URL xsdUrl = LTTngControlService.class.getResource(LTTngControlServiceConstants.MI_XSD_FILENAME);
                if (version.compareTo(new LttngVersion(2, 8, 0, null, null, null, null, null, null)) >= 0) {
                    xsdUrl = LTTngControlService.class.getResource(LTTngControlServiceConstants.MI3_XSD_FILENAME);
                    // MI 3.0 added name spaces. It will fail to validate if this is not set to true.
                    docBuilderFactory.setNamespaceAware(true);
                }
                docBuilderFactory.setSchema(schemaFactory.newSchema(xsdUrl));
            } catch (SAXException e) {
                throw new ExecutionException(Messages.TraceControl_InvalidSchemaError, e);
            }
        }

        try {
            fDocumentBuilder = docBuilderFactory.newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            throw new ExecutionException(Messages.TraceControl_XmlDocumentBuilderError, e);
        }

        fDocumentBuilder.setErrorHandler(new XmlMiValidationErrorHandler());

    }

    private static boolean isSchemaValidationEnabled() {
        String schemaValidationKey = Platform.getDebugOption(MI_SCHEMA_VALIDATION_KEY);
        String systemProperty = System.getProperty(MI_SCHEMA_VALIDATION_KEY);
        return schemaValidationKey != null && Boolean.parseBoolean(schemaValidationKey) || systemProperty != null && Boolean.parseBoolean(systemProperty);
    }

    /**
     * Generate a Document object from an list of Strings.
     *
     * @param xmlStrings
     *            list of strings representing an xml input
     * @param documentBuilder
     *            the builder used to get the document
     * @return Document generated from strings input
     * @throws ExecutionException
     *             when parsing has failed
     */
    private static Document getDocumentFromStrings(List<String> xmlStrings, DocumentBuilder documentBuilder) throws ExecutionException {
        StringBuilder concatenedString = new StringBuilder();
        for (String string : xmlStrings) {
            concatenedString.append(string);
        }
        InputSource stream = new InputSource(new StringReader(concatenedString.toString()));

        Document document;
        try {
            document = documentBuilder.parse(stream);
        } catch (SAXException | IOException e) {
            throw new ExecutionException(Messages.TraceControl_XmlParsingError + ':' + e.toString(), e);
        }
        return document;

    }

    /**
     * Parse LTTng version from a MI command result
     *
     * @param commandResult
     *            the result obtained from a MI command
     * @return the LTTng version
     * @throws ExecutionException
     *             when xml extraction fail
     */
    public static LttngVersion parseVersion(ICommandResult commandResult) throws ExecutionException {
        DocumentBuilderFactory docBuilderFactory = XmlUtils.newSafeDocumentBuilderFactory();
        DocumentBuilder documentBuilder;
        try {
            documentBuilder = docBuilderFactory.newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            throw new ExecutionException(Messages.TraceControl_XmlDocumentBuilderError, e);
        }

        Document doc = getDocumentFromStrings(commandResult.getOutput(), documentBuilder);
        NodeList element = doc.getElementsByTagName(MIStrings.VERSION);
        if (element.getLength() != 1) {
            throw new ExecutionException(Messages.TraceControl_UnsupportedVersionError);
        }

        int major = 0;
        int minor = 0;
        int patchLevel = 0;
        String license = ""; //$NON-NLS-1$
        String commit = ""; //$NON-NLS-1$
        String name = ""; //$NON-NLS-1$
        String description = ""; //$NON-NLS-1$
        String url = ""; //$NON-NLS-1$
        String fullVersion = ""; //$NON-NLS-1$
        NodeList child = element.item(0).getChildNodes();
        // Get basic information
        for (int i = 0; i < child.getLength(); i++) {
            Node node = child.item(i);
            switch (node.getNodeName()) {
            case MIStrings.VERSION_MAJOR:
                major = Integer.parseInt(node.getTextContent());
                break;
            case MIStrings.VERSION_MINOR:
                minor = Integer.parseInt(node.getTextContent());
                break;
            case MIStrings.VERSION_PATCH_LEVEL:
                patchLevel = Integer.parseInt(node.getTextContent());
                break;
            case MIStrings.VERSION_COMMIT:
                commit = node.getTextContent();
                break;
            case MIStrings.VERSION_DESCRIPTION:
                description = node.getTextContent();
                break;
            case MIStrings.VERSION_LICENSE:
                license = node.getTextContent();
                break;
            case MIStrings.VERSION_NAME:
                name = node.getTextContent();
                break;
            case MIStrings.VERSION_STR:
                fullVersion = node.getTextContent();
                break;
            case MIStrings.VERSION_WEB:
                url = node.getTextContent();
                break;
            default:
                break;
            }
        }
        return new LttngVersion(major, minor, patchLevel, license, commit, name, description, url, fullVersion);
    }

    @Override
    public List<String> getSessionNames(IProgressMonitor monitor) throws ExecutionException {
        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_LIST);
        ICommandResult result = executeCommand(command, monitor);

        Document doc = getDocumentFromStrings(result.getOutput(), fDocumentBuilder);

        NodeList elements = doc.getElementsByTagName(MIStrings.NAME);

        ArrayList<String> retArray = new ArrayList<>();
        for (int i = 0; i < elements.getLength(); i++) {
            Node node = elements.item(i);
            if (node.getParentNode().getNodeName().equalsIgnoreCase(MIStrings.SESSION)) {
                retArray.add(node.getTextContent());
            }
        }
        return retArray;
    }

    @Override
    public ISessionInfo getSession(String sessionName, IProgressMonitor monitor) throws ExecutionException {
        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_LIST, sessionName);
        ICommandResult result = executeCommand(command, monitor);

        ISessionInfo sessionInfo = new SessionInfo(sessionName);
        Document document = getDocumentFromStrings(result.getOutput(), fDocumentBuilder);

        NodeList sessionsNode = document.getElementsByTagName(MIStrings.SESSION);
        // There should be only one session
        if (sessionsNode.getLength() != 1) {
            throw new ExecutionException(NLS.bind(Messages.TraceControl_MiInvalidNumberOfElementError, MIStrings.SESSION));
        }

        // Populate session information
        Node rawSession = sessionsNode.item(0);
        parseSession(sessionInfo, rawSession);

        // Fetch the snapshot info
        if (sessionInfo.isSnapshotSession()) {
            ISnapshotInfo snapshot = getSnapshotInfo(sessionName, monitor);
            sessionInfo.setSnapshotInfo(snapshot);
        }

        return sessionInfo;
    }

    /**
     * @param sessionInfo
     * @param rawSession
     * @throws ExecutionException
     */
    private void parseSession(ISessionInfo sessionInfo, Node rawSession) throws ExecutionException {
        if (!rawSession.getNodeName().equalsIgnoreCase(MIStrings.SESSION)) {
            throw new ExecutionException(Messages.TraceControl_MiInvalidElementError);
        }
        NodeList rawSessionInfos = rawSession.getChildNodes();
        for (int i = 0; i < rawSessionInfos.getLength(); i++) {
            Node rawInfo = rawSessionInfos.item(i);
            switch (rawInfo.getNodeName()) {
            case MIStrings.NAME:
                sessionInfo.setName(rawInfo.getTextContent());
                break;
            case MIStrings.PATH:
                sessionInfo.setSessionPath(rawInfo.getTextContent());
                break;
            case MIStrings.ENABLED:
                sessionInfo.setSessionState(rawInfo.getTextContent());
                break;
            case MIStrings.SNAPSHOT_MODE:
                if (rawInfo.getTextContent().equals(LTTngControlServiceConstants.TRUE_NUMERICAL)) {
                    // real name will be set later
                    ISnapshotInfo snapshotInfo = new SnapshotInfo(""); //$NON-NLS-1$
                    sessionInfo.setSnapshotInfo(snapshotInfo);
                }
                break;
            case MIStrings.LIVE_TIMER_INTERVAL:
                long liveDelay = Long.parseLong(rawInfo.getTextContent());
                if ((liveDelay > 0 && (liveDelay <= LTTngControlServiceConstants.MAX_LIVE_TIMER_INTERVAL))) {
                    sessionInfo.setLive(true);
                    sessionInfo.setLiveUrl(SessionInfo.DEFAULT_LIVE_NETWORK_URL);
                    sessionInfo.setLivePort(SessionInfo.DEFAULT_LIVE_PORT);
                    sessionInfo.setLiveDelay(liveDelay);
                }
                break;
            case MIStrings.DOMAINS:
                // Extract the domains node
                NodeList rawDomains = rawInfo.getChildNodes();
                IDomainInfo domain = null;
                for (int j = 0; j < rawDomains.getLength(); j++) {
                    if (rawDomains.item(j).getNodeName().equalsIgnoreCase(MIStrings.DOMAIN)) {
                        domain = parseDomain(rawDomains.item(j));
                        sessionInfo.addDomain(domain);
                    }
                }
                break;
            default:
                break;
            }
        }

        if (!sessionInfo.isSnapshotSession()) {
            Matcher matcher = LTTngControlServiceConstants.TRACE_NETWORK_PATTERN.matcher(sessionInfo.getSessionPath());
            if (matcher.matches()) {
                sessionInfo.setStreamedTrace(true);
            }
        }
    }

    /**
     * Parse a raw domain XML node to a IDomainInfo object
     *
     * @param rawDomain
     *            a domain xml node
     * @return a populated {@link DomainInfo} object
     * @throws ExecutionException
     *             when missing required xml element (type)
     */
    protected IDomainInfo parseDomain(Node rawDomain) throws ExecutionException {
        IDomainInfo domain = null;
        // Get the type
        Node rawType = getFirstOf(rawDomain.getChildNodes(), MIStrings.TYPE);
        if (rawType == null) {
            throw new ExecutionException(Messages.TraceControl_MiMissingRequiredError);
        }
        String rawTypeString = rawType.getTextContent().toLowerCase();
        TraceDomainType domainType = TraceDomainType.valueOfString(rawTypeString);
        switch (domainType) {
        case KERNEL:
            domain = new DomainInfo(Messages.TraceControl_KernelProviderDisplayName);
            domain.setDomain(TraceDomainType.KERNEL);
            break;
        case UST:
            domain = new DomainInfo(Messages.TraceControl_UstGlobalDomainDisplayName);
            domain.setDomain(TraceDomainType.UST);
            break;
        case JUL:
            domain = new DomainInfo(Messages.TraceControl_JULDomainDisplayName);
            domain.setDomain(TraceDomainType.JUL);
            break;
        case LOG4J:
            domain = new DomainInfo(Messages.TraceControl_LOG4JDomainDisplayName);
            domain.setDomain(TraceDomainType.LOG4J);
            break;
        case PYTHON:
            domain = new DomainInfo(Messages.TraceControl_PythonDomainDisplayName);
            domain.setDomain(TraceDomainType.PYTHON);
            break;
        case UNKNOWN:
            domain = new DomainInfo(Messages.TraceControl_UnknownDomainDisplayName);
            domain.setDomain(TraceDomainType.UNKNOWN);
            break;
            //$CASES-OMITTED$
        default:
            throw new ExecutionException(Messages.TraceControl_MiInvalidElementError);
        }

        NodeList rawInfos = rawDomain.getChildNodes();
        for (int i = 0; i < rawInfos.getLength(); i++) {
            Node rawInfo = rawInfos.item(i);
            switch (rawInfo.getNodeName()) {
            case MIStrings.BUFFER_TYPE:
                BufferType bufferType = BufferType.valueOfString(rawInfo.getTextContent());
                domain.setBufferType(bufferType);
                break;
            case MIStrings.CHANNELS:
                ArrayList<IChannelInfo> channels = new ArrayList<>();
                parseChannels(rawInfo.getChildNodes(), channels);
                if (!channels.isEmpty()) {
                    domain.setChannels(channels);
                }
                break;
            case MIStrings.EVENTS:
                ArrayList<ILoggerInfo> loggers = new ArrayList<>();
                getLoggerInfo(rawInfo.getChildNodes(), loggers, domain.getDomain());
                domain.setLoggers(loggers);
                break;
            default:
                break;
            }
        }

        return domain;
    }

    /**
     * Parse a list of raw channel XML node into an ArrayList of IChannelInfo
     *
     * @param rawChannes
     *            List of raw channel XML node
     * @param channels
     *            the parsed channels list
     * @throws ExecutionException
     *             when missing required xml element (type)
     */
    private static void parseChannels(NodeList rawChannels, ArrayList<IChannelInfo> channels) throws ExecutionException {
        IChannelInfo channel = null;
        for (int i = 0; i < rawChannels.getLength(); i++) {
            Node rawChannel = rawChannels.item(i);
            if (rawChannel.getNodeName().equalsIgnoreCase(MIStrings.CHANNEL)) {
                channel = new ChannelInfo(""); //$NON-NLS-1$

                // Populate the channel
                NodeList rawInfos = rawChannel.getChildNodes();
                Node rawInfo = null;
                for (int j = 0; j < rawInfos.getLength(); j++) {
                    rawInfo = rawInfos.item(j);
                    switch (rawInfo.getNodeName()) {
                    case MIStrings.NAME:
                        channel.setName(rawInfo.getTextContent());
                        break;
                    case MIStrings.ENABLED:
                        channel.setState(TraceEnablement.valueOfString(rawInfo.getTextContent()));
                        break;
                    case MIStrings.EVENTS:
                        List<IEventInfo> events = new ArrayList<>();
                        getEventInfo(rawInfo.getChildNodes(), events);
                        channel.setEvents(events);
                        break;
                    case MIStrings.ATTRIBUTES:
                        NodeList rawAttributes = rawInfo.getChildNodes();
                        for (int k = 0; k < rawAttributes.getLength(); k++) {
                            Node attribute = rawAttributes.item(k);
                            switch (attribute.getNodeName()) {
                            case MIStrings.OVERWRITE_MODE:
                                channel.setOverwriteMode(!LTTngControlServiceConstants.OVERWRITE_MODE_ATTRIBUTE_FALSE_MI.equalsIgnoreCase(attribute.getTextContent()));
                                break;
                            case MIStrings.SUBBUF_SIZE:
                                channel.setSubBufferSize(Long.valueOf(attribute.getTextContent()));
                                break;
                            case MIStrings.NUM_SUBBUF:
                                channel.setNumberOfSubBuffers(Integer.valueOf(attribute.getTextContent()));
                                break;
                            case MIStrings.SWITCH_TIMER_INTERVAL:
                                channel.setSwitchTimer(Long.valueOf(attribute.getTextContent()));
                                break;
                            case MIStrings.READ_TIMER_INTERVAL:
                                channel.setReadTimer(Long.valueOf(attribute.getTextContent()));
                                break;
                            case MIStrings.OUTPUT_TYPE:
                                channel.setOutputType(attribute.getTextContent());
                                break;
                            case MIStrings.TRACEFILE_SIZE:
                                channel.setMaxSizeTraceFiles(Long.parseLong(attribute.getTextContent()));
                                break;
                            case MIStrings.TRACEFILE_COUNT:
                                channel.setMaxNumberTraceFiles(Integer.parseInt(attribute.getTextContent()));
                                break;
                            case MIStrings.LIVE_TIMER_INTERVAL:
                                // TODO: currently not supported by tmf
                                break;
                            case MIStrings.DISCARDED_EVENTS:
                                channel.setNumberOfDiscardedEvents(Long.parseLong(attribute.getTextContent()));
                                break;
                            case MIStrings.LOST_PACKETS:
                                channel.setNumberOfLostPackets(Long.parseLong(attribute.getTextContent()));
                                break;
                            default:
                                break;
                            }
                        }
                        break;
                    default:
                        break;
                    }
                }
                channels.add(channel);
            }
        }

    }

    @Override
    public ISnapshotInfo getSnapshotInfo(String sessionName, IProgressMonitor monitor) throws ExecutionException {
        // TODO A session can have multiple snapshot output. This need to be
        // supported in the future.
        // Currently the SessionInfo object does not support multiple snashot
        // output.
        // For now only keep the last one.
        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_SNAPSHOT, LTTngControlServiceConstants.COMMAND_LIST_SNAPSHOT_OUTPUT, LTTngControlServiceConstants.OPTION_SESSION, sessionName);
        ICommandResult result = executeCommand(command, monitor);
        Document doc = getDocumentFromStrings(result.getOutput(), fDocumentBuilder);
        NodeList rawSnapshotsOutputs = doc.getElementsByTagName(MIStrings.SNAPSHOT_OUTPUTS);

        ISnapshotInfo snapshotInfo = new SnapshotInfo(""); //$NON-NLS-1$

        // TODO: tmf does not have a notion of a ctrl url.
        for (int i = 0; i < rawSnapshotsOutputs.getLength(); i++) {
            NodeList rawSnapshotOutput = rawSnapshotsOutputs.item(i).getChildNodes();
            for (int j = 0; j < rawSnapshotOutput.getLength(); j++) {
                Node rawInfo = rawSnapshotOutput.item(j);
                switch (rawInfo.getNodeName()) {
                case MIStrings.ID:
                    snapshotInfo.setId(Integer.parseInt(rawInfo.getTextContent()));
                    break;
                case MIStrings.NAME:
                    snapshotInfo.setName(rawInfo.getTextContent());
                    break;
                case MIStrings.SNAPSHOT_CTRL_URL:
                    // The use of the ctrl_url for the snapshot path is to assure
                    // basic support. Refactoring is necessary in lttng and
                    // tmf side.
                    // See http://bugs.lttng.org/issues/828 (+comment)
                    snapshotInfo.setSnapshotPath(rawInfo.getTextContent());
                    break;
                default:
                    break;
                }
            }
        }

        // Check if the snapshot output is Streamed
        Matcher matcher2 = LTTngControlServiceConstants.TRACE_NETWORK_PATTERN.matcher(snapshotInfo.getSnapshotPath());
        if (matcher2.matches()) {
            snapshotInfo.setStreamedSnapshot(true);
        }

        return snapshotInfo;
    }

    @Override
    public List<IBaseEventInfo> getKernelProvider(IProgressMonitor monitor) throws ExecutionException {
        // Tracepoint events
        ICommandInput tracepointCommand = createCommand(LTTngControlServiceConstants.COMMAND_LIST, LTTngControlServiceConstants.OPTION_KERNEL);
        ICommandResult tracepointResult = executeCommand(tracepointCommand, monitor, false);
        List<IBaseEventInfo> events = new ArrayList<>();

        if (isError(tracepointResult)) {
            // Ignore the following 2 cases:
            // Spawning a session daemon
            // Error: Unable to list kernel events
            // or:
            // Error: Unable to list kernel events
            if (ignoredPattern(tracepointResult.getErrorOutput(), LTTngControlServiceConstants.LIST_KERNEL_NO_KERNEL_PROVIDER_PATTERN)) {
                return events;
            }
            throw new ExecutionException(Messages.TraceControl_CommandError + tracepointCommand.toString());
        }

        Document tracepointDocument = getDocumentFromStrings(tracepointResult.getOutput(), fDocumentBuilder);
        NodeList rawTracepointEvents = tracepointDocument.getElementsByTagName(MIStrings.EVENT);
        getBaseEventInfo(rawTracepointEvents, events);

        // Syscall events
        ICommandInput syscallCommand = createCommand(LTTngControlServiceConstants.COMMAND_LIST, LTTngControlServiceConstants.OPTION_KERNEL, LTTngControlServiceConstants.OPTION_SYSCALL);
        ICommandResult syscallResult = executeCommand(syscallCommand, monitor, false);
        List<IBaseEventInfo> syscallEvents = new ArrayList<>();

        if (isError(syscallResult)) {
            throw new ExecutionException(Messages.TraceControl_CommandError + syscallCommand.toString());
        }

        Document syscallDocument = getDocumentFromStrings(syscallResult.getOutput(), fDocumentBuilder);
        NodeList rawSyscallEvents = syscallDocument.getElementsByTagName(MIStrings.EVENT);
        getBaseEventInfo(rawSyscallEvents, syscallEvents);

        // Merge the tracepoint events with the syscall events (all under the Kernel provider)
        events.addAll(syscallEvents);
        return events;
    }

    @Override
    public List<IUstProviderInfo> getUstProvider(IProgressMonitor monitor) throws ExecutionException {
        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_LIST, LTTngControlServiceConstants.OPTION_UST);
        // Get the field too
        command.add(LTTngControlServiceConstants.OPTION_FIELDS);
        // Execute UST listing
        ICommandResult result = executeCommand(command, monitor, false);
        List<IUstProviderInfo> allProviders = new ArrayList<>();

        if (isError(result)) {
            // Ignore the following 2 cases:
            // Spawning a session daemon
            // Error: Unable to list UST events: Listing UST events failed
            // or:
            // Error: Unable to list UST events: Listing UST events failed
            if (ignoredPattern(result.getErrorOutput(), LTTngControlServiceConstants.LIST_UST_NO_UST_PROVIDER_PATTERN)) {
                return allProviders;
            }
            throw new ExecutionException(Messages.TraceControl_CommandError + command.toString());
        }

        Document document = getDocumentFromStrings(result.getOutput(), fDocumentBuilder);
        NodeList rawProviders = document.getElementsByTagName(MIStrings.PID);

        IUstProviderInfo providerInfo = null;

        for (int i = 0; i < rawProviders.getLength(); i++) {
            Node provider = rawProviders.item(i);
            Node name = getFirstOf(provider.getChildNodes(), MIStrings.NAME);
            if (name == null) {
                throw new ExecutionException(Messages.TraceControl_MiInvalidProviderError);
            }
            providerInfo = new UstProviderInfo(name.getTextContent());

            // Populate provider
            NodeList infos = provider.getChildNodes();
            for (int j = 0; j < infos.getLength(); j++) {
                Node info = infos.item(j);
                switch (info.getNodeName()) {
                case MIStrings.PID_ID:
                    providerInfo.setPid(Integer.parseInt(info.getTextContent()));
                    break;
                case MIStrings.EVENTS:
                    List<IBaseEventInfo> events = new ArrayList<>();
                    NodeList rawEvents = info.getChildNodes();
                    getBaseEventInfo(rawEvents, events);
                    providerInfo.setEvents(events);
                    break;
                default:
                    break;
                }
            }
            allProviders.add(providerInfo);
        }

        if (isVersionSupported("2.6")) { //$NON-NLS-1$
            getUstProviderLoggers(allProviders, TraceDomainType.JUL, monitor);
            getUstProviderLoggers(allProviders, TraceDomainType.LOG4J, monitor);
            if (isVersionSupported("2.7")) { //$NON-NLS-1$
                getUstProviderLoggers(allProviders, TraceDomainType.PYTHON, monitor);
            }
        }

        return allProviders;
    }

    @Override
    public ISessionInfo createSession(ISessionInfo sessionInfo, IProgressMonitor monitor) throws ExecutionException {
        if (sessionInfo.isStreamedTrace()) {
            return createStreamedSession(sessionInfo, monitor);
        }

        ICommandInput command = prepareSessionCreationCommand(sessionInfo);
        ICommandResult result = executeCommand(command, monitor);

        Document document = getDocumentFromStrings(result.getOutput(), fDocumentBuilder);
        NodeList sessions = document.getElementsByTagName(MIStrings.SESSION);

        // Number of session should be equal to 1
        if (sessions.getLength() != 1) {
            throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" //$NON-NLS-1$//$NON-NLS-2$
                    + NLS.bind(Messages.TraceControl_UnexpectedNumberOfElementError, MIStrings.SESSION) + " " + sessions.getLength()); //$NON-NLS-1$
        }

        // Fetch a session from output
        ISessionInfo outputSession = new SessionInfo(""); //$NON-NLS-1$
        parseSession(outputSession, sessions.item(0));

        // Verify session name
        if ((outputSession.getName().equals("")) || (!"".equals(sessionInfo.getName()) && !outputSession.getName().equals(sessionInfo.getName()))) { //$NON-NLS-1$ //$NON-NLS-2$
            // Unexpected name returned
            throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
                    Messages.TraceControl_UnexpectedNameError + ": " + outputSession.getName()); //$NON-NLS-1$
        }

        // Verify session path
        if (!sessionInfo.isSnapshotSession() &&
                ((outputSession.getSessionPath() == null) || ((sessionInfo.getSessionPath() != null) && (!outputSession.getSessionPath().contains(sessionInfo.getSessionPath()))))) {
            // Unexpected path
            throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
                    Messages.TraceControl_UnexpectedPathError + ": " + outputSession.getName()); //$NON-NLS-1$
        }

        if (sessionInfo.isSnapshotSession()) {
            // Make it a snapshot session - content of snapshot info need to
            // set afterwards using getSession() or getSnapshotInfo()
            outputSession.setSnapshotInfo(new SnapshotInfo("")); //$NON-NLS-1$
        }

        return outputSession;
    }

    private @NonNull ISessionInfo createStreamedSession(ISessionInfo sessionInfo, IProgressMonitor monitor) throws ExecutionException {

        ICommandInput command = prepareStreamedSessionCreationCommand(sessionInfo);

        ICommandResult result = executeCommand(command, monitor);

        Document document = getDocumentFromStrings(result.getOutput(), fDocumentBuilder);
        NodeList sessions = document.getElementsByTagName(MIStrings.SESSION);

        // Number of session should be equal to 1
        if (sessions.getLength() != 1) {
            throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" //$NON-NLS-1$//$NON-NLS-2$
                    + NLS.bind(Messages.TraceControl_UnexpectedNumberOfElementError, MIStrings.SESSION) + " " + sessions.getLength()); //$NON-NLS-1$
        }

        // Fetch a session from output
        ISessionInfo outputSession = new SessionInfo(""); //$NON-NLS-1$
        parseSession(outputSession, sessions.item(0));

        // Verify session name
        if ((outputSession.getName().equals("")) || (!"".equals(sessionInfo.getName()) && !outputSession.getName().equals(sessionInfo.getName()))) { //$NON-NLS-1$ //$NON-NLS-2$
            // Unexpected name returned
            throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
                    Messages.TraceControl_UnexpectedNameError + ": " + outputSession.getName()); //$NON-NLS-1$
        }

        sessionInfo.setName(outputSession.getName());
        sessionInfo.setStreamedTrace(true);

        // Verify session path
        if (sessionInfo.getNetworkUrl() != null) {
            if (!sessionInfo.isSnapshotSession() && (outputSession.getSessionPath() == null)) {
                // Unexpected path
                throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
                        Messages.TraceControl_UnexpectedPathError + ": " + outputSession.getName()); //$NON-NLS-1$
            }

            if (sessionInfo.isSnapshotSession()) {
                sessionInfo.setStreamedTrace(false);
            } else {
                sessionInfo.setSessionPath(outputSession.getSessionPath());
                // Check file protocol
                Matcher matcher = LTTngControlServiceConstants.TRACE_FILE_PROTOCOL_PATTERN.matcher(outputSession.getSessionPath());
                if (matcher.matches()) {
                    sessionInfo.setStreamedTrace(false);
                }
            }
        }

        // When using controlUrl and dataUrl the full session path is not known
        // yet
        // and will be set later on when listing the session
        return sessionInfo;
    }

    @Override
    public void destroySession(String sessionName, IProgressMonitor monitor) throws ExecutionException {
        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_DESTROY_SESSION, sessionName);

        ICommandResult result = executeCommand(command, monitor, false);
        List<String> errorOutput = result.getErrorOutput();

        if (isError(result)) {
            // Don't treat this as an error
            if (ignoredPattern(errorOutput, LTTngControlServiceConstants.SESSION_NOT_FOUND_ERROR_PATTERN)) {
                return;

            }
            throw new ExecutionException(Messages.TraceControl_CommandError + " " + command.toString() + "\n" + result.toString()); //$NON-NLS-1$ //$NON-NLS-2$
        }

        // Check for action effect
        Document doc = getDocumentFromStrings(result.getOutput(), fDocumentBuilder);
        NodeList sessions = doc.getElementsByTagName(MIStrings.SESSION);
        if (sessions.getLength() != 1) {
            throw new ExecutionException(NLS.bind(Messages.TraceControl_MiInvalidNumberOfElementError, MIStrings.SESSION));
        }

        Node rawSessionName = getFirstOf(sessions.item(0).getChildNodes(), MIStrings.NAME);
        if (rawSessionName == null) {
            throw new ExecutionException(Messages.TraceControl_MiMissingRequiredError);
        }

        // Validity check
        if (!rawSessionName.getTextContent().equals(sessionName)) {
            throw new ExecutionException(NLS.bind(Messages.TraceControl_UnexpectedValueError, rawSessionName.getTextContent(), sessionName));
        }
    }

    @Override
    protected ICommandInput createCommand(String... strings) {
        ICommandInput command = getCommandShell().createCommand();
        command.add(LTTngControlServiceConstants.CONTROL_COMMAND);
        List<@NonNull String> groupOption = getTracingGroupOption();
        if (!groupOption.isEmpty()) {
            command.addAll(groupOption);
        }
        command.add(LTTngControlServiceConstants.CONTROL_COMMAND_MI_OPTION);
        command.add(LTTngControlServiceConstants.CONTROL_COMMAND_MI_XML);
        for (String string : strings) {
            command.add(checkNotNull(string));
        }
        return command;
    }

    /**
     * @param xmlBaseEvents
     *            a Node list of base xml event element
     * @param events
     *            list of event generated by the parsing of the xml event
     *            element
     * @throws ExecutionException
     *             when a raw event is not a complete/valid xml event
     */
    private static void getBaseEventInfo(NodeList xmlBaseEvents, List<IBaseEventInfo> events) throws ExecutionException {
        IBaseEventInfo eventInfo = null;
        for (int i = 0; i < xmlBaseEvents.getLength(); i++) {
            NodeList rawInfos = xmlBaseEvents.item(i).getChildNodes();
            // Search for name
            if (xmlBaseEvents.item(i).getNodeName().equalsIgnoreCase(MIStrings.EVENT)) {
                Node rawName = getFirstOf(rawInfos, MIStrings.NAME);
                if (rawName == null) {
                    throw new ExecutionException(Messages.TraceControl_MiMissingRequiredError);
                }
                eventInfo = new BaseEventInfo(rawName.getTextContent());

                // Populate the event
                for (int j = 0; j < rawInfos.getLength(); j++) {
                    Node infoNode = rawInfos.item(j);
                    switch (infoNode.getNodeName()) {
                    case MIStrings.TYPE:
                        eventInfo.setEventType(infoNode.getTextContent());
                        break;
                    case MIStrings.LOGLEVEL:
                        eventInfo.setLogLevel(infoNode.getTextContent());
                        break;
                    case MIStrings.EVENT_FIELDS:
                        List<IFieldInfo> fields = new ArrayList<>();
                        getFieldInfo(infoNode.getChildNodes(), fields);
                        eventInfo.setFields(fields);
                        break;
                    default:
                        break;
                    }
                }
                events.add(eventInfo);
            }
        }
    }

    /**
     * @param xmlBaseEvents
     *            a Node list of xml event element linked to a session
     * @param events
     *            list of event generated by the parsing of the xml event
     *            element
     * @throws ExecutionException
     *             when a raw event is not a complete/valid xml event
     */
    static void getEventInfo(NodeList xmlEvents, List<IEventInfo> events) throws ExecutionException {
        IEventInfo eventInfo = null;
        for (int i = 0; i < xmlEvents.getLength(); i++) {
            NodeList rawInfos = xmlEvents.item(i).getChildNodes();
            // Search for name
            if (xmlEvents.item(i).getNodeName().equalsIgnoreCase(MIStrings.EVENT)) {
                Node rawName = getFirstOf(rawInfos, MIStrings.NAME);
                if (rawName == null) {
                    throw new ExecutionException(Messages.TraceControl_MiMissingRequiredError);
                }

                eventInfo = new EventInfo(rawName.getTextContent());

                // Basic information
                for (int j = 0; j < rawInfos.getLength(); j++) {
                    Node infoNode = rawInfos.item(j);
                    switch (infoNode.getNodeName()) {
                    case MIStrings.TYPE:
                        eventInfo.setEventType(infoNode.getTextContent());
                        break;
                    case MIStrings.LOGLEVEL_TYPE:
                        eventInfo.setLogLevelType(LogLevelType.valueOfString(infoNode.getTextContent()));
                        break;
                    case MIStrings.LOGLEVEL:
                        eventInfo.setLogLevel(TraceLogLevel.valueOfString(infoNode.getTextContent()));
                        break;
                    case MIStrings.ENABLED:
                        eventInfo.setState(TraceEnablement.valueOfString(infoNode.getTextContent()));
                        break;
                    case MIStrings.FILTER:
                        // Before LTTng 2.8: We emulate the non-mi behavior and simply put
                        // "with filter"
                        if (Boolean.TRUE.toString().equals(infoNode.getTextContent())) {
                            eventInfo.setFilterExpression(Messages.TraceControl_DefaultEventFilterString);
                        }
                        break;
                    case MIStrings.FILTER_EXPRESSION:
                        eventInfo.setFilterExpression(infoNode.getTextContent());
                        break;
                    case MIStrings.EXCLUSION:
                        // Before LTTng 2.8: We emulate the non-mi behavior and simply put
                        // "with exclude"
                        if (Boolean.TRUE.toString().equals(infoNode.getTextContent())) {
                            eventInfo.setExcludedEvents(Messages.TraceControl_DefaultEventExcludeString);
                        }
                        break;
                    case MIStrings.EXCLUSIONS:
                        StringBuilder tmpString = new StringBuilder();
                        // If there is multiple events excluded.
                        for (int k = 0; k < infoNode.getChildNodes().getLength(); k++) {
                            if (k > 0) {
                                tmpString.append(", "); //$NON-NLS-1$
                            }
                            tmpString.append(infoNode.getChildNodes().item(k).getTextContent());
                        }
                        eventInfo.setExcludedEvents(tmpString.toString());
                        break;
                    default:
                        break;
                    }
                }

                boolean isProbeFunction = (eventInfo.getEventType().equals(TraceEventType.PROBE)) || (eventInfo.getEventType().equals(TraceEventType.FUNCTION));
                if (isProbeFunction) {
                    IProbeEventInfo probeEvent = new ProbeEventInfo(eventInfo);
                    eventInfo = probeEvent;

                    Node rawDataNode = null;
                    switch (probeEvent.getEventType()) {
                    case FUNCTION:
                    case PROBE: {
                        // get attributes
                        Node rawAttributes = getFirstOf(rawInfos, MIStrings.ATTRIBUTES);
                        if (rawAttributes == null) {
                            throw new ExecutionException(Messages.TraceControl_MiMissingRequiredError);
                        }
                        rawDataNode = getFirstOf(rawAttributes.getChildNodes(), MIStrings.PROBE_ATTRIBUTES);
                        break;
                    }
                    case SYSCALL:
                    case TRACEPOINT:
                    case UNKNOWN:
                    default:
                        throw new ExecutionException(Messages.TraceControl_MiInvalidElementError);
                    }

                    if (rawDataNode == null) {
                        throw new ExecutionException(Messages.TraceControl_MiInvalidElementError);
                    }

                    // Extract info
                    NodeList rawDatas = rawDataNode.getChildNodes();
                    for (int j = 0; j < rawDatas.getLength(); j++) {
                        Node rawData = rawDatas.item(j);
                        switch (rawData.getNodeName()) {
                        case MIStrings.SYMBOL_NAME:
                            probeEvent.setSymbol(rawData.getTextContent());
                            break;
                        case MIStrings.ADDRESS:
                            probeEvent.setAddress(String.format("%#016x", new BigInteger(rawData.getTextContent()))); //$NON-NLS-1$
                            break;
                        case MIStrings.OFFSET:
                            probeEvent.setOffset(String.format("%#016x", new BigInteger(rawData.getTextContent()))); //$NON-NLS-1$
                            break;
                        default:
                            break;
                        }
                    }
                }

                // Add the event
                events.add(eventInfo);
            }
        }
    }

    static void getLoggerInfo(NodeList xmlEvents, List<ILoggerInfo> loggers, TraceDomainType domain) throws ExecutionException {
        ILoggerInfo loggerInfo = null;
        for (int i = 0; i < xmlEvents.getLength(); i++) {
            NodeList rawInfos = xmlEvents.item(i).getChildNodes();
            // Search for name
            if (xmlEvents.item(i).getNodeName().equalsIgnoreCase(MIStrings.EVENT)) {
                Node rawName = getFirstOf(rawInfos, MIStrings.NAME);
                if (rawName == null) {
                    throw new ExecutionException(Messages.TraceControl_MiMissingRequiredError);
                }

                loggerInfo = new LoggerInfo(rawName.getTextContent());
                loggerInfo.setDomain(domain);

                // Basic information
                for (int j = 0; j < rawInfos.getLength(); j++) {
                    Node infoNode = rawInfos.item(j);
                    switch (infoNode.getNodeName()) {
                    case MIStrings.LOGLEVEL_TYPE:
                        loggerInfo.setLogLevelType(LogLevelType.valueOfString(infoNode.getTextContent()));
                        break;
                    case MIStrings.LOGLEVEL:
                        switch (domain) {
                        case JUL:
                            loggerInfo.setLogLevel(TraceJulLogLevel.valueOfString(infoNode.getTextContent()));
                            break;
                        case LOG4J:
                            loggerInfo.setLogLevel(TraceLog4jLogLevel.valueOfString(infoNode.getTextContent()));
                            break;
                        case PYTHON:
                            loggerInfo.setLogLevel(TracePythonLogLevel.valueOfString(infoNode.getTextContent()));
                            break;
                            //$CASES-OMITTED$
                        default:
                            break;
                        }
                        break;
                    case MIStrings.ENABLED:
                        loggerInfo.setState(TraceEnablement.valueOfString(infoNode.getTextContent()));
                        break;
                    default:
                        break;
                    }
                }
                // Add the event
                loggers.add(loggerInfo);
            }
        }
    }

    /**
     * @param fieldsList
     *            a list of xml event_field element
     * @param fields
     *            a list of field generated by xml parsing
     * @throws ExecutionException
     *             when parsing fail or required elements are missing
     */
    private static void getFieldInfo(NodeList fieldsList, List<IFieldInfo> fields) throws ExecutionException {
        IFieldInfo fieldInfo = null;
        for (int i = 0; i < fieldsList.getLength(); i++) {
            Node field = fieldsList.item(i);
            if (field.getNodeName().equalsIgnoreCase(MIStrings.EVENT_FIELD)) {
                // Get name
                Node name = getFirstOf(field.getChildNodes(), MIStrings.NAME);
                if (name == null) {
                    throw new ExecutionException(Messages.TraceControl_MiMissingRequiredError);
                }
                fieldInfo = new FieldInfo(name.getTextContent());

                // Populate the field information
                NodeList infos = field.getChildNodes();
                for (int j = 0; j < infos.getLength(); j++) {
                    Node info = infos.item(j);
                    switch (info.getNodeName()) {
                    case MIStrings.TYPE:
                        fieldInfo.setFieldType(info.getTextContent());
                        break;
                    default:
                        break;
                    }
                }
                fields.add(fieldInfo);
            }
        }
    }

    /**
     * Retrieve the fist instance of a given node with tag name equal to tagName
     * parameter
     *
     * @param nodeList
     *            the list of Node to search against
     * @param tagName
     *            the tag name of the desired node
     * @return the first occurrence of a node with a tag name equals to tagName
     */
    private static @Nullable Node getFirstOf(NodeList nodeList, String tagName) {
        Node node = null;
        for (int i = 0; i < nodeList.getLength(); i++) {
            if (Objects.equals(nodeList.item(i).getNodeName(), tagName)) {
                node = nodeList.item(i);
                break;
            }
        }
        return node;
    }

    /**
     * Retrieve the loggers of a certain domain type for the UST provider.
     *
     * @param allProviders
     *            the list of UST providers
     * @param domain
     *            the loggers domain
     * @param monitor
     *            progress monitor
     * @throws ExecutionException
     */
    private void getUstProviderLoggers(List<IUstProviderInfo> allProviders, TraceDomainType domain, IProgressMonitor monitor) throws ExecutionException {
        // Getting the loggers information since those are under the UST provider
        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_LIST);
        switch (domain) {
        case JUL:
            command.add(LTTngControlServiceConstants.OPTION_JUL);
            break;
        case LOG4J:
            command.add(LTTngControlServiceConstants.OPTION_LOG4J);
            break;
        case PYTHON:
            command.add(LTTngControlServiceConstants.OPTION_PYTHON);
            break;
            //$CASES-OMITTED$
        default:
            break;
        }
        // Execute listing
        ICommandResult result = executeCommand(command, monitor, false);

        if (isError(result)) {
            throw new ExecutionException(Messages.TraceControl_CommandError + command.toString());
        }

        Document document = getDocumentFromStrings(result.getOutput(), fDocumentBuilder);
        NodeList rawProviders = document.getElementsByTagName(MIStrings.PID);

        for (int i = 0; i < rawProviders.getLength(); i++) {
            Node provider = rawProviders.item(i);
            Node name = getFirstOf(provider.getChildNodes(), MIStrings.NAME);
            if (name == null) {
                throw new ExecutionException(Messages.TraceControl_MiInvalidProviderError);
            }

            Node id = getFirstOf(provider.getChildNodes(), MIStrings.PID_ID);

            if (id != null) {
                for (int k = 0; k < allProviders.size(); k++) {
                    if (allProviders.get(k).getPid() == Integer.parseInt(id.getTextContent())) {
                        Node events = getFirstOf(provider.getChildNodes(), MIStrings.EVENTS);
                        if (events != null) {
                            List<ILoggerInfo> loggers = new ArrayList<>();
                            NodeList rawEvents = events.getChildNodes();
                            getLoggerInfo(rawEvents, loggers, domain);
                            for (ILoggerInfo logger : loggers) {
                                logger.setDomain(domain);
                            }
                            allProviders.get(k).addLoggers(loggers);
                        }
                    }
                }
            }
        }
    }

    @Override
    public @NonNull List<String> getContextList(IProgressMonitor monitor) throws ExecutionException {
        if (!isVersionSupported("2.8.0")) { //$NON-NLS-1$
            return super.getContextList(monitor);
        }

        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_ADD_CONTEXT, LTTngControlServiceConstants.OPTION_LIST);
        ICommandResult result = executeCommand(command, monitor);

        if (!isVersionSupported("2.11.0")) { //$NON-NLS-1$
            return result.getOutput();
        }

        if (isError(result)) {
            throw new ExecutionException(Messages.TraceControl_CommandError + command.toString());
        }
        Document document = getDocumentFromStrings(result.getOutput(), fDocumentBuilder);
        NodeList rawContexts = document.getElementsByTagName(MIStrings.CONTEXT);

        List<String> returnedContexts = new ArrayList<>();
        for (int i = 0; i < rawContexts.getLength(); i++) {
            Node contextNode = rawContexts.item(i);
            Node symbol = getFirstOf(contextNode.getChildNodes(), MIStrings.SYMBOL);
            if (symbol != null) {
                returnedContexts.add(symbol.getTextContent());
            }
        }
        return returnedContexts;
    }
}
