Remove opendaylight directory
[netconf.git] / netconf / netconf-client / src / main / java / org / opendaylight / netconf / client / NetconfClientSessionNegotiator.java
diff --git a/netconf/netconf-client/src/main/java/org/opendaylight/netconf/client/NetconfClientSessionNegotiator.java b/netconf/netconf-client/src/main/java/org/opendaylight/netconf/client/NetconfClientSessionNegotiator.java
new file mode 100644 (file)
index 0000000..4ae7dea
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+ * 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.netconf.client;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.util.Timer;
+import io.netty.util.concurrent.Promise;
+import java.util.Collection;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import org.opendaylight.controller.config.util.xml.XmlUtil;
+import org.opendaylight.netconf.nettyutil.AbstractChannelInitializer;
+import org.opendaylight.netconf.nettyutil.AbstractNetconfSessionNegotiator;
+import org.opendaylight.netconf.nettyutil.handler.exi.NetconfStartExiMessage;
+import org.opendaylight.netconf.api.messages.NetconfHelloMessage;
+import org.opendaylight.netconf.util.messages.NetconfMessageUtil;
+import org.opendaylight.netconf.util.xml.XMLNetconfUtil;
+import org.opendaylight.netconf.api.NetconfClientSessionPreferences;
+import org.opendaylight.netconf.api.NetconfDocumentedException;
+import org.opendaylight.netconf.api.NetconfMessage;
+import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+public class NetconfClientSessionNegotiator extends
+        AbstractNetconfSessionNegotiator<NetconfClientSessionPreferences, NetconfClientSession, NetconfClientSessionListener>
+{
+    private static final Logger LOG = LoggerFactory.getLogger(NetconfClientSessionNegotiator.class);
+
+    private static final XPathExpression sessionIdXPath = XMLNetconfUtil
+            .compileXPath("/netconf:hello/netconf:session-id");
+
+    private static final XPathExpression sessionIdXPathNoNamespace = XMLNetconfUtil
+            .compileXPath("/hello/session-id");
+
+    private static final String EXI_1_0_CAPABILITY_MARKER = "exi:1.0";
+
+    protected NetconfClientSessionNegotiator(final NetconfClientSessionPreferences sessionPreferences,
+                                             final Promise<NetconfClientSession> promise,
+                                             final Channel channel,
+                                             final Timer timer,
+                                             final NetconfClientSessionListener sessionListener,
+                                             final long connectionTimeoutMillis) {
+        super(sessionPreferences, promise, channel, timer, sessionListener, connectionTimeoutMillis);
+    }
+
+    @Override
+    protected void handleMessage(final NetconfHelloMessage netconfMessage) throws NetconfDocumentedException {
+        final NetconfClientSession session = getSessionForHelloMessage(netconfMessage);
+        replaceHelloMessageInboundHandler(session);
+
+        // If exi should be used, try to initiate exi communication
+        // Call negotiationSuccessFul after exi negotiation is finished successfully or not
+        if (shouldUseExi(netconfMessage)) {
+            LOG.debug("Netconf session {} should use exi.", session);
+            NetconfStartExiMessage startExiMessage = (NetconfStartExiMessage) sessionPreferences.getStartExiMessage();
+            tryToInitiateExi(session, startExiMessage);
+        } else {
+            // Exi is not supported, release session immediately
+            LOG.debug("Netconf session {} isn't capable of using exi.", session);
+            negotiationSuccessful(session);
+        }
+    }
+
+    /**
+     * Initiates exi communication by sending start-exi message and waiting for positive/negative response.
+     *
+     * @param startExiMessage
+     */
+    void tryToInitiateExi(final NetconfClientSession session, final NetconfStartExiMessage startExiMessage) {
+        channel.pipeline().addAfter(AbstractChannelInitializer.NETCONF_MESSAGE_DECODER,
+                ExiConfirmationInboundHandler.EXI_CONFIRMED_HANDLER,
+                new ExiConfirmationInboundHandler(session, startExiMessage));
+
+        session.sendMessage(startExiMessage).addListener(new ChannelFutureListener() {
+            @Override
+            public void operationComplete(final ChannelFuture f) {
+                if (!f.isSuccess()) {
+                    LOG.warn("Failed to send start-exi message {} on session {}", startExiMessage, this, f.cause());
+                    channel.pipeline().remove(ExiConfirmationInboundHandler.EXI_CONFIRMED_HANDLER);
+                } else {
+                    LOG.trace("Start-exi message {} sent to socket on session {}", startExiMessage, this);
+                }
+            }
+        });
+    }
+
+    private boolean shouldUseExi(final NetconfHelloMessage helloMsg) {
+        return containsExi10Capability(helloMsg.getDocument())
+                && containsExi10Capability(sessionPreferences.getHelloMessage().getDocument());
+    }
+
+    private static boolean containsExi10Capability(final Document doc) {
+        final NodeList nList = doc.getElementsByTagName(XmlNetconfConstants.CAPABILITY);
+        for (int i = 0; i < nList.getLength(); i++) {
+            if (nList.item(i).getTextContent().contains(EXI_1_0_CAPABILITY_MARKER)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static long extractSessionId(final Document doc) {
+        String textContent = getSessionIdWithXPath(doc, sessionIdXPath);
+        if (Strings.isNullOrEmpty(textContent)) {
+            textContent = getSessionIdWithXPath(doc, sessionIdXPathNoNamespace);
+            if (Strings.isNullOrEmpty(textContent)) {
+                throw new IllegalStateException("Session id not received from server, hello message: " + XmlUtil.toString(doc));
+            }
+        }
+
+        return Long.valueOf(textContent);
+    }
+
+    private static String getSessionIdWithXPath(final Document doc, final XPathExpression sessionIdXPath) {
+        final Node sessionIdNode = (Node) XmlUtil.evaluateXPath(sessionIdXPath, doc, XPathConstants.NODE);
+        return sessionIdNode != null ? sessionIdNode.getTextContent() : null;
+    }
+
+    @Override
+    protected NetconfClientSession getSession(final NetconfClientSessionListener sessionListener, final Channel channel,
+            final NetconfHelloMessage message) throws NetconfDocumentedException {
+        long sessionId = extractSessionId(message.getDocument());
+
+        // Copy here is important: it disconnects the strings from the document
+        Collection<String> capabilities = ImmutableList.copyOf(NetconfMessageUtil.extractCapabilitiesFromHello(message.getDocument()));
+
+        // FIXME: scalability: we could instantiate a cache to share the same collections
+        return new NetconfClientSession(sessionListener, channel, sessionId, capabilities);
+    }
+
+    /**
+     * Handler to process response for start-exi message
+     */
+    private final class ExiConfirmationInboundHandler extends ChannelInboundHandlerAdapter {
+        private static final String EXI_CONFIRMED_HANDLER = "exiConfirmedHandler";
+
+        private final NetconfClientSession session;
+        private final NetconfStartExiMessage startExiMessage;
+
+        ExiConfirmationInboundHandler(final NetconfClientSession session, final NetconfStartExiMessage startExiMessage) {
+            this.session = session;
+            this.startExiMessage = startExiMessage;
+        }
+
+        @Override
+        public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
+            ctx.pipeline().remove(ExiConfirmationInboundHandler.EXI_CONFIRMED_HANDLER);
+
+            NetconfMessage netconfMessage = (NetconfMessage) msg;
+
+            // Ok response to start-exi, try to add exi handlers
+            if (NetconfMessageUtil.isOKMessage(netconfMessage)) {
+                LOG.trace("Positive response on start-exi call received on session {}", session);
+                try {
+                    session.startExiCommunication(startExiMessage);
+                } catch (RuntimeException e) {
+                    // Unable to add exi, continue without exi
+                    LOG.warn("Unable to start exi communication, Communication will continue without exi on session {}", session, e);
+                }
+
+                // Error response
+            } else if(NetconfMessageUtil.isErrorMessage(netconfMessage)) {
+                LOG.warn(
+                        "Error response to start-exi message {}, Communication will continue without exi on session {}",
+                        netconfMessage, session);
+
+                // Unexpected response to start-exi, throwing message away, continue without exi
+            } else {
+                LOG.warn("Unexpected response to start-exi message, should be ok, was {}, " +
+                         "Communication will continue without exi and response message will be thrown away on session {}",
+                         netconfMessage, session);
+            }
+
+            negotiationSuccessful(session);
+        }
+    }
+
+}