Hide NetconfClientSessionNegotiator
[netconf.git] / netconf / netconf-client / src / main / java / org / opendaylight / netconf / client / NetconfClientSessionNegotiator.java
index 4ae7dea12c77e4970ff785301948a82ef0d67596..5ee52bd073ac1f125176282127bce30faec4c3d3 100644 (file)
@@ -5,71 +5,84 @@
  * 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 com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Interner;
+import com.google.common.collect.Interners;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 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 java.util.Set;
 import javax.xml.xpath.XPathConstants;
 import javax.xml.xpath.XPathExpression;
-import org.opendaylight.controller.config.util.xml.XmlUtil;
+import org.opendaylight.netconf.api.NetconfClientSessionPreferences;
+import org.opendaylight.netconf.api.NetconfDocumentedException;
+import org.opendaylight.netconf.api.NetconfMessage;
+import org.opendaylight.netconf.api.messages.NetconfHelloMessage;
+import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
+import org.opendaylight.netconf.api.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>
-{
+// Non-final for mocking
+class NetconfClientSessionNegotiator
+        extends AbstractNetconfSessionNegotiator<NetconfClientSessionPreferences, NetconfClientSession,
+                NetconfClientSessionListener> {
     private static final Logger LOG = LoggerFactory.getLogger(NetconfClientSessionNegotiator.class);
 
-    private static final XPathExpression sessionIdXPath = XMLNetconfUtil
+    private static final XPathExpression SESSION_ID_X_PATH = XMLNetconfUtil
             .compileXPath("/netconf:hello/netconf:session-id");
 
-    private static final XPathExpression sessionIdXPathNoNamespace = XMLNetconfUtil
+    private static final XPathExpression SESSION_ID_X_PATH_NO_NAMESPACE = 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) {
+    private static final Interner<Set<String>> INTERNER = Interners.newWeakInterner();
+
+    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);
     }
 
+    @SuppressWarnings("checkstyle:IllegalCatch")
+    @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST",
+        justification = "SpotBugs does not understand generic cast of sessionPreferences")
     @Override
     protected void handleMessage(final NetconfHelloMessage netconfMessage) throws NetconfDocumentedException {
+        if (!ifNegotiatedAlready()) {
+            LOG.debug("Server hello message received, starting negotiation on channel {}", channel);
+            try {
+                startNegotiation();
+            } catch (final Exception e) {
+                LOG.warn("Unexpected negotiation failure on channel {}", channel, e);
+                negotiationFailed(e);
+                return;
+            }
+        }
         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)) {
+        final NetconfMessage startExiMessage = sessionPreferences.getStartExiMessage();
+        if (shouldUseExi(netconfMessage) && startExiMessage instanceof NetconfStartExiMessage) {
             LOG.debug("Netconf session {} should use exi.", session);
-            NetconfStartExiMessage startExiMessage = (NetconfStartExiMessage) sessionPreferences.getStartExiMessage();
-            tryToInitiateExi(session, startExiMessage);
+            tryToInitiateExi(session, (NetconfStartExiMessage) startExiMessage);
         } else {
             // Exi is not supported, release session immediately
             LOG.debug("Netconf session {} isn't capable of using exi.", session);
@@ -80,26 +93,26 @@ public class NetconfClientSessionNegotiator extends
     /**
      * Initiates exi communication by sending start-exi message and waiting for positive/negative response.
      *
-     * @param startExiMessage
+     * @param startExiMessage Exi message for initilization of exi communication.
      */
     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);
-                }
+        session.sendMessage(startExiMessage).addListener(channelFuture -> {
+            if (!channelFuture.isSuccess()) {
+                LOG.warn("Failed to send start-exi message {} on session {}", startExiMessage, session,
+                        channelFuture.cause());
+                channel.pipeline().remove(ExiConfirmationInboundHandler.EXI_CONFIRMED_HANDLER);
+            } else {
+                LOG.trace("Start-exi message {} sent to socket on session {}", startExiMessage, session);
             }
         });
     }
 
+    @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST",
+        justification = "SpotBugs does not understand generic cast of sessionPreferences")
     private boolean shouldUseExi(final NetconfHelloMessage helloMsg) {
         return containsExi10Capability(helloMsg.getDocument())
                 && containsExi10Capability(sessionPreferences.getHelloMessage().getDocument());
@@ -116,15 +129,16 @@ public class NetconfClientSessionNegotiator extends
     }
 
     private static long extractSessionId(final Document doc) {
-        String textContent = getSessionIdWithXPath(doc, sessionIdXPath);
+        String textContent = getSessionIdWithXPath(doc, SESSION_ID_X_PATH);
         if (Strings.isNullOrEmpty(textContent)) {
-            textContent = getSessionIdWithXPath(doc, sessionIdXPathNoNamespace);
+            textContent = getSessionIdWithXPath(doc, SESSION_ID_X_PATH_NO_NAMESPACE);
             if (Strings.isNullOrEmpty(textContent)) {
-                throw new IllegalStateException("Session id not received from server, hello message: " + XmlUtil.toString(doc));
+                throw new IllegalStateException("Session id not received from server, hello message: " + XmlUtil
+                        .toString(doc));
             }
         }
 
-        return Long.valueOf(textContent);
+        return Long.parseLong(textContent);
     }
 
     private static String getSessionIdWithXPath(final Document doc, final XPathExpression sessionIdXPath) {
@@ -134,18 +148,20 @@ public class NetconfClientSessionNegotiator extends
 
     @Override
     protected NetconfClientSession getSession(final NetconfClientSessionListener sessionListener, final Channel channel,
-            final NetconfHelloMessage message) throws NetconfDocumentedException {
-        long sessionId = extractSessionId(message.getDocument());
+                                              final NetconfHelloMessage message) {
+        final 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()));
+        Set<String> capabilities = ImmutableSet.copyOf(NetconfMessageUtil.extractCapabilitiesFromHello(message
+                .getDocument()));
+
+        capabilities = INTERNER.intern(capabilities);
 
-        // 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
+     * Handler to process response for start-exi message.
      */
     private final class ExiConfirmationInboundHandler extends ChannelInboundHandlerAdapter {
         private static final String EXI_CONFIRMED_HANDLER = "exiConfirmedHandler";
@@ -153,11 +169,13 @@ public class NetconfClientSessionNegotiator extends
         private final NetconfClientSession session;
         private final NetconfStartExiMessage startExiMessage;
 
-        ExiConfirmationInboundHandler(final NetconfClientSession session, final NetconfStartExiMessage startExiMessage) {
+        ExiConfirmationInboundHandler(final NetconfClientSession session,
+                                      final NetconfStartExiMessage startExiMessage) {
             this.session = session;
             this.startExiMessage = startExiMessage;
         }
 
+        @SuppressWarnings("checkstyle:IllegalCatch")
         @Override
         public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
             ctx.pipeline().remove(ExiConfirmationInboundHandler.EXI_CONFIRMED_HANDLER);
@@ -171,20 +189,22 @@ public class NetconfClientSessionNegotiator extends
                     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);
+                    LOG.warn("Unable to start exi communication, Communication will continue without exi on session "
+                            + "{}", session, e);
                 }
 
                 // Error response
-            } else if(NetconfMessageUtil.isErrorMessage(netconfMessage)) {
+            } 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);
+                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);