Merge "caching the same capabilities for devices using Interner."
[netconf.git] / netconf / netconf-client / src / main / java / org / opendaylight / netconf / client / NetconfClientSessionNegotiator.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.netconf.client;
10
11 import com.google.common.base.Strings;
12 import com.google.common.collect.ImmutableSet;
13 import com.google.common.collect.Interner;
14 import com.google.common.collect.Interners;
15 import io.netty.channel.Channel;
16 import io.netty.channel.ChannelFuture;
17 import io.netty.channel.ChannelFutureListener;
18 import io.netty.channel.ChannelHandlerContext;
19 import io.netty.channel.ChannelInboundHandlerAdapter;
20 import io.netty.util.Timer;
21 import io.netty.util.concurrent.Promise;
22 import java.util.Set;
23 import javax.xml.xpath.XPathConstants;
24 import javax.xml.xpath.XPathExpression;
25 import org.opendaylight.controller.config.util.xml.XmlUtil;
26 import org.opendaylight.netconf.api.NetconfClientSessionPreferences;
27 import org.opendaylight.netconf.api.NetconfDocumentedException;
28 import org.opendaylight.netconf.api.NetconfMessage;
29 import org.opendaylight.netconf.api.messages.NetconfHelloMessage;
30 import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
31 import org.opendaylight.netconf.nettyutil.AbstractChannelInitializer;
32 import org.opendaylight.netconf.nettyutil.AbstractNetconfSessionNegotiator;
33 import org.opendaylight.netconf.nettyutil.handler.exi.NetconfStartExiMessage;
34 import org.opendaylight.netconf.util.messages.NetconfMessageUtil;
35 import org.opendaylight.netconf.util.xml.XMLNetconfUtil;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38 import org.w3c.dom.Document;
39 import org.w3c.dom.Node;
40 import org.w3c.dom.NodeList;
41
42 public class NetconfClientSessionNegotiator extends
43         AbstractNetconfSessionNegotiator<NetconfClientSessionPreferences, NetconfClientSession, NetconfClientSessionListener>
44 {
45     private static final Logger LOG = LoggerFactory.getLogger(NetconfClientSessionNegotiator.class);
46
47     private static final XPathExpression sessionIdXPath = XMLNetconfUtil
48             .compileXPath("/netconf:hello/netconf:session-id");
49
50     private static final XPathExpression sessionIdXPathNoNamespace = XMLNetconfUtil
51             .compileXPath("/hello/session-id");
52
53     private static final String EXI_1_0_CAPABILITY_MARKER = "exi:1.0";
54
55     private static final Interner<Set<String>> INTERNER = Interners.newWeakInterner();
56
57     protected NetconfClientSessionNegotiator(final NetconfClientSessionPreferences sessionPreferences,
58                                              final Promise<NetconfClientSession> promise,
59                                              final Channel channel,
60                                              final Timer timer,
61                                              final NetconfClientSessionListener sessionListener,
62                                              final long connectionTimeoutMillis) {
63         super(sessionPreferences, promise, channel, timer, sessionListener, connectionTimeoutMillis);
64     }
65
66     @Override
67     protected void handleMessage(final NetconfHelloMessage netconfMessage) throws NetconfDocumentedException {
68         final NetconfClientSession session = getSessionForHelloMessage(netconfMessage);
69         replaceHelloMessageInboundHandler(session);
70
71         // If exi should be used, try to initiate exi communication
72         // Call negotiationSuccessFul after exi negotiation is finished successfully or not
73         if (shouldUseExi(netconfMessage)) {
74             LOG.debug("Netconf session {} should use exi.", session);
75             NetconfStartExiMessage startExiMessage = (NetconfStartExiMessage) sessionPreferences.getStartExiMessage();
76             tryToInitiateExi(session, startExiMessage);
77         } else {
78             // Exi is not supported, release session immediately
79             LOG.debug("Netconf session {} isn't capable of using exi.", session);
80             negotiationSuccessful(session);
81         }
82     }
83
84     /**
85      * Initiates exi communication by sending start-exi message and waiting for positive/negative response.
86      *
87      * @param startExiMessage
88      */
89     void tryToInitiateExi(final NetconfClientSession session, final NetconfStartExiMessage startExiMessage) {
90         channel.pipeline().addAfter(AbstractChannelInitializer.NETCONF_MESSAGE_DECODER,
91                 ExiConfirmationInboundHandler.EXI_CONFIRMED_HANDLER,
92                 new ExiConfirmationInboundHandler(session, startExiMessage));
93
94         session.sendMessage(startExiMessage).addListener(new ChannelFutureListener() {
95             @Override
96             public void operationComplete(final ChannelFuture f) {
97                 if (!f.isSuccess()) {
98                     LOG.warn("Failed to send start-exi message {} on session {}", startExiMessage, this, f.cause());
99                     channel.pipeline().remove(ExiConfirmationInboundHandler.EXI_CONFIRMED_HANDLER);
100                 } else {
101                     LOG.trace("Start-exi message {} sent to socket on session {}", startExiMessage, this);
102                 }
103             }
104         });
105     }
106
107     private boolean shouldUseExi(final NetconfHelloMessage helloMsg) {
108         return containsExi10Capability(helloMsg.getDocument())
109                 && containsExi10Capability(sessionPreferences.getHelloMessage().getDocument());
110     }
111
112     private static boolean containsExi10Capability(final Document doc) {
113         final NodeList nList = doc.getElementsByTagName(XmlNetconfConstants.CAPABILITY);
114         for (int i = 0; i < nList.getLength(); i++) {
115             if (nList.item(i).getTextContent().contains(EXI_1_0_CAPABILITY_MARKER)) {
116                 return true;
117             }
118         }
119         return false;
120     }
121
122     private static long extractSessionId(final Document doc) {
123         String textContent = getSessionIdWithXPath(doc, sessionIdXPath);
124         if (Strings.isNullOrEmpty(textContent)) {
125             textContent = getSessionIdWithXPath(doc, sessionIdXPathNoNamespace);
126             if (Strings.isNullOrEmpty(textContent)) {
127                 throw new IllegalStateException("Session id not received from server, hello message: " + XmlUtil.toString(doc));
128             }
129         }
130
131         return Long.valueOf(textContent);
132     }
133
134     private static String getSessionIdWithXPath(final Document doc, final XPathExpression sessionIdXPath) {
135         final Node sessionIdNode = (Node) XmlUtil.evaluateXPath(sessionIdXPath, doc, XPathConstants.NODE);
136         return sessionIdNode != null ? sessionIdNode.getTextContent() : null;
137     }
138
139     @Override
140     protected NetconfClientSession getSession(final NetconfClientSessionListener sessionListener, final Channel channel,
141                                               final NetconfHelloMessage message) throws NetconfDocumentedException {
142         final long sessionId = extractSessionId(message.getDocument());
143
144         // Copy here is important: it disconnects the strings from the document
145         Set<String> capabilities = ImmutableSet.copyOf(NetconfMessageUtil.extractCapabilitiesFromHello(message.getDocument()));
146
147         capabilities = INTERNER.intern(capabilities);
148
149         return new NetconfClientSession(sessionListener, channel, sessionId, capabilities);
150     }
151
152     /**
153      * Handler to process response for start-exi message
154      */
155     private final class ExiConfirmationInboundHandler extends ChannelInboundHandlerAdapter {
156         private static final String EXI_CONFIRMED_HANDLER = "exiConfirmedHandler";
157
158         private final NetconfClientSession session;
159         private final NetconfStartExiMessage startExiMessage;
160
161         ExiConfirmationInboundHandler(final NetconfClientSession session, final NetconfStartExiMessage startExiMessage) {
162             this.session = session;
163             this.startExiMessage = startExiMessage;
164         }
165
166         @Override
167         public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
168             ctx.pipeline().remove(ExiConfirmationInboundHandler.EXI_CONFIRMED_HANDLER);
169
170             NetconfMessage netconfMessage = (NetconfMessage) msg;
171
172             // Ok response to start-exi, try to add exi handlers
173             if (NetconfMessageUtil.isOKMessage(netconfMessage)) {
174                 LOG.trace("Positive response on start-exi call received on session {}", session);
175                 try {
176                     session.startExiCommunication(startExiMessage);
177                 } catch (RuntimeException e) {
178                     // Unable to add exi, continue without exi
179                     LOG.warn("Unable to start exi communication, Communication will continue without exi on session {}", session, e);
180                 }
181
182                 // Error response
183             } else if(NetconfMessageUtil.isErrorMessage(netconfMessage)) {
184                 LOG.warn(
185                         "Error response to start-exi message {}, Communication will continue without exi on session {}",
186                         netconfMessage, session);
187
188                 // Unexpected response to start-exi, throwing message away, continue without exi
189             } else {
190                 LOG.warn("Unexpected response to start-exi message, should be ok, was {}, " +
191                          "Communication will continue without exi and response message will be thrown away on session {}",
192                          netconfMessage, session);
193             }
194
195             negotiationSuccessful(session);
196         }
197     }
198
199 }