<commit_before>/*
 * Copyright (c) 2013 Cisco Systems, Inc. and others.  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.opendaylight.controller.netconf.persist.impl;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import org.opendaylight.controller.config.api.ConflictingVersionException;
import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder;
import org.opendaylight.controller.config.persist.api.Persister;
import org.opendaylight.controller.netconf.api.NetconfMessage;
import org.opendaylight.controller.netconf.api.jmx.CommitJMXNotification;
import org.opendaylight.controller.netconf.api.jmx.DefaultCommitOperationMXBean;
import org.opendaylight.controller.netconf.api.jmx.NetconfJMXNotification;
import org.opendaylight.controller.netconf.client.NetconfClient;
import org.opendaylight.controller.netconf.client.NetconfClientDispatcher;
import org.opendaylight.controller.netconf.util.xml.XMLNetconfUtil;
import org.opendaylight.controller.netconf.util.xml.XmlElement;
import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants;
import org.opendaylight.controller.netconf.util.xml.XmlUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

import javax.annotation.concurrent.ThreadSafe;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanServerConnection;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.net.ssl.SSLContext;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * Responsible for listening for notifications from netconf containing latest
 * committed configuration that should be persisted, and also for loading last
 * configuration.
 */
@ThreadSafe
public class ConfigPersisterNotificationHandler implements NotificationListener, Closeable {

    private static final Logger logger = LoggerFactory.getLogger(ConfigPersisterNotificationHandler.class);
    private static final int NETCONF_SEND_ATTEMPT_MS_DELAY = 1000;
    private static final int NETCONF_SEND_ATTEMPTS = 20;

    private final InetSocketAddress address;
    private final EventLoopGroup nettyThreadgroup;

    private NetconfClientDispatcher netconfClientDispatcher;
    private NetconfClient netconfClient;

    private final Persister persister;
    private final MBeanServerConnection mbeanServer;


    private final ObjectName on = DefaultCommitOperationMXBean.objectName;

    public static final long DEFAULT_TIMEOUT = 120000L;// 120 seconds until netconf must be stable
    private final long timeout;
    private final Pattern ignoredMissingCapabilityRegex;

    public ConfigPersisterNotificationHandler(Persister persister, InetSocketAddress address,
            MBeanServerConnection mbeanServer, Pattern ignoredMissingCapabilityRegex) {
        this(persister, address, mbeanServer, DEFAULT_TIMEOUT, ignoredMissingCapabilityRegex);

    }

    public ConfigPersisterNotificationHandler(Persister persister, InetSocketAddress address,
            MBeanServerConnection mbeanServer, long timeout, Pattern ignoredMissingCapabilityRegex) {
        this.persister = persister;
        this.address = address;
        this.mbeanServer = mbeanServer;
        this.timeout = timeout;

        this.nettyThreadgroup = new NioEventLoopGroup();
        this.ignoredMissingCapabilityRegex = ignoredMissingCapabilityRegex;
    }

    public void init() throws InterruptedException {
        Optional<ConfigSnapshotHolder> maybeConfig = loadLastConfig();

        if (maybeConfig.isPresent()) {
            logger.debug("Last config found {}", persister);
            ConflictingVersionException lastException = null;
            int maxAttempts = 30;
            for(int i = 0 ; i < maxAttempts; i++) {
                registerToNetconf(maybeConfig.get().getCapabilities());

                final String configSnapshot = maybeConfig.get().getConfigSnapshot();
                logger.trace("Pushing following xml to netconf {}", configSnapshot);
                try {
                    pushLastConfig(XmlUtil.readXmlToElement(configSnapshot));
                } catch(ConflictingVersionException e) {
                    closeClientAndDispatcher(netconfClient, netconfClientDispatcher);
                    lastException = e;
                    Thread.sleep(1000);
                } catch (SAXException | IOException e) {
                    throw new IllegalStateException("Unable to load last config", e);
                }
            }
            throw new IllegalStateException("Failed to push configuration, maximum attempt count has been reached: "
                    + maxAttempts, lastException);

        } else {
            // this ensures that netconf is initialized, this is first
            // connection
            // this means we can register as listener for commit
            registerToNetconf(Collections.<String>emptySet());

            logger.info("No last config provided by backend storage {}", persister);
        }
        registerAsJMXListener();
    }

    private synchronized long registerToNetconf(Set<String> expectedCaps) throws InterruptedException {

        Set<String> currentCapabilities = Sets.newHashSet();

        // TODO think about moving capability subset check to netconf client
        // could be utilized by integration tests<commit_msg>Fix bug in persister causing to persist last configuration in a loop.<commit_after>/*
 * Copyright (c) 2013 Cisco Systems, Inc. and others.  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.opendaylight.controller.netconf.persist.impl;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import org.opendaylight.controller.config.api.ConflictingVersionException;
import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder;
import org.opendaylight.controller.config.persist.api.Persister;
import org.opendaylight.controller.netconf.api.NetconfMessage;
import org.opendaylight.controller.netconf.api.jmx.CommitJMXNotification;
import org.opendaylight.controller.netconf.api.jmx.DefaultCommitOperationMXBean;
import org.opendaylight.controller.netconf.api.jmx.NetconfJMXNotification;
import org.opendaylight.controller.netconf.client.NetconfClient;
import org.opendaylight.controller.netconf.client.NetconfClientDispatcher;
import org.opendaylight.controller.netconf.util.xml.XMLNetconfUtil;
import org.opendaylight.controller.netconf.util.xml.XmlElement;
import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants;
import org.opendaylight.controller.netconf.util.xml.XmlUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

import javax.annotation.concurrent.ThreadSafe;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanServerConnection;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.net.ssl.SSLContext;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * Responsible for listening for notifications from netconf containing latest
 * committed configuration that should be persisted, and also for loading last
 * configuration.
 */
@ThreadSafe
public class ConfigPersisterNotificationHandler implements NotificationListener, Closeable {

    private static final Logger logger = LoggerFactory.getLogger(ConfigPersisterNotificationHandler.class);
    private static final int NETCONF_SEND_ATTEMPT_MS_DELAY = 1000;
    private static final int NETCONF_SEND_ATTEMPTS = 20;

    private final InetSocketAddress address;
    private final EventLoopGroup nettyThreadgroup;

    private NetconfClientDispatcher netconfClientDispatcher;
    private NetconfClient netconfClient;

    private final Persister persister;
    private final MBeanServerConnection mbeanServer;


    private final ObjectName on = DefaultCommitOperationMXBean.objectName;

    public static final long DEFAULT_TIMEOUT = 120000L;// 120 seconds until netconf must be stable
    private final long timeout;
    private final Pattern ignoredMissingCapabilityRegex;

    public ConfigPersisterNotificationHandler(Persister persister, InetSocketAddress address,
            MBeanServerConnection mbeanServer, Pattern ignoredMissingCapabilityRegex) {
        this(persister, address, mbeanServer, DEFAULT_TIMEOUT, ignoredMissingCapabilityRegex);

    }

    public ConfigPersisterNotificationHandler(Persister persister, InetSocketAddress address,
            MBeanServerConnection mbeanServer, long timeout, Pattern ignoredMissingCapabilityRegex) {
        this.persister = persister;
        this.address = address;
        this.mbeanServer = mbeanServer;
        this.timeout = timeout;

        this.nettyThreadgroup = new NioEventLoopGroup();
        this.ignoredMissingCapabilityRegex = ignoredMissingCapabilityRegex;
    }

    public void init() throws InterruptedException {
        Optional<ConfigSnapshotHolder> maybeConfig = loadLastConfig();

        if (maybeConfig.isPresent()) {
            logger.debug("Last config found {}", persister);
            ConflictingVersionException lastException = null;
            int maxAttempts = 30;
            for(int i = 0 ; i < maxAttempts; i++) {
                registerToNetconf(maybeConfig.get().getCapabilities());

                final String configSnapshot = maybeConfig.get().getConfigSnapshot();
                logger.trace("Pushing following xml to netconf {}", configSnapshot);
                try {
                    pushLastConfig(XmlUtil.readXmlToElement(configSnapshot));
                    return;
                } catch(ConflictingVersionException e) {
                    closeClientAndDispatcher(netconfClient, netconfClientDispatcher);
                    lastException = e;
                    Thread.sleep(1000);
                } catch (SAXException | IOException e) {
                    throw new IllegalStateException("Unable to load last config", e);
                }
            }
            throw new IllegalStateException("Failed to push configuration, maximum attempt count has been reached: "
                    + maxAttempts, lastException);

        } else {
            // this ensures that netconf is initialized, this is first
            // connection
            // this means we can register as listener for commit
            registerToNetconf(Collections.<String>emptySet());

            logger.info("No last config provided by backend storage {}", persister);
        }
        registerAsJMXListener();
    }

    private synchronized long registerToNetconf(Set<String> expectedCaps) throws InterruptedException {

        Set<String> currentCapabilities = Sets.newHashSet();

        // TODO think about moving capability subset check to netconf client
        // could be utilized by integration tests
{'fraction_non_alphanumeric': 0.07428010471204188, 'fraction_numerical': 0.00556282722513089, 'mean_word_length': 3.771662763466042, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 16}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5331915', 'n_tokens_mistral': 3201, 'n_tokens_neox': 3068, 'n_words': 773}