2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.netconf.client;
10 import com.google.common.base.Strings;
11 import com.google.common.collect.ImmutableSet;
12 import com.google.common.collect.Interner;
13 import com.google.common.collect.Interners;
14 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
15 import io.netty.channel.Channel;
16 import io.netty.channel.ChannelHandlerContext;
17 import io.netty.channel.ChannelInboundHandlerAdapter;
18 import io.netty.util.Timer;
19 import io.netty.util.concurrent.Promise;
21 import javax.xml.xpath.XPathConstants;
22 import javax.xml.xpath.XPathExpression;
23 import org.opendaylight.netconf.api.NetconfClientSessionPreferences;
24 import org.opendaylight.netconf.api.NetconfDocumentedException;
25 import org.opendaylight.netconf.api.NetconfMessage;
26 import org.opendaylight.netconf.api.messages.NetconfHelloMessage;
27 import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
28 import org.opendaylight.netconf.api.xml.XmlUtil;
29 import org.opendaylight.netconf.nettyutil.AbstractChannelInitializer;
30 import org.opendaylight.netconf.nettyutil.AbstractNetconfSessionNegotiator;
31 import org.opendaylight.netconf.nettyutil.handler.exi.NetconfStartExiMessage;
32 import org.opendaylight.netconf.util.messages.NetconfMessageUtil;
33 import org.opendaylight.netconf.util.xml.XMLNetconfUtil;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36 import org.w3c.dom.Document;
37 import org.w3c.dom.Node;
38 import org.w3c.dom.NodeList;
40 // Non-final for mocking
41 class NetconfClientSessionNegotiator
42 extends AbstractNetconfSessionNegotiator<NetconfClientSessionPreferences, NetconfClientSession,
43 NetconfClientSessionListener> {
44 private static final Logger LOG = LoggerFactory.getLogger(NetconfClientSessionNegotiator.class);
46 private static final XPathExpression SESSION_ID_X_PATH = XMLNetconfUtil
47 .compileXPath("/netconf:hello/netconf:session-id");
49 private static final XPathExpression SESSION_ID_X_PATH_NO_NAMESPACE = XMLNetconfUtil
50 .compileXPath("/hello/session-id");
52 private static final String EXI_1_0_CAPABILITY_MARKER = "exi:1.0";
54 private static final Interner<Set<String>> INTERNER = Interners.newWeakInterner();
56 NetconfClientSessionNegotiator(final NetconfClientSessionPreferences sessionPreferences,
57 final Promise<NetconfClientSession> promise, final Channel channel, final Timer timer,
58 final NetconfClientSessionListener sessionListener, final long connectionTimeoutMillis) {
59 super(sessionPreferences, promise, channel, timer, sessionListener, connectionTimeoutMillis);
62 @SuppressWarnings("checkstyle:IllegalCatch")
63 @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST",
64 justification = "SpotBugs does not understand generic cast of sessionPreferences")
66 protected void handleMessage(final NetconfHelloMessage netconfMessage) throws NetconfDocumentedException {
67 if (!ifNegotiatedAlready()) {
68 LOG.debug("Server hello message received, starting negotiation on channel {}", channel);
71 } catch (final Exception e) {
72 LOG.warn("Unexpected negotiation failure on channel {}", channel, e);
77 final NetconfClientSession session = getSessionForHelloMessage(netconfMessage);
78 replaceHelloMessageInboundHandler(session);
80 // If exi should be used, try to initiate exi communication
81 // Call negotiationSuccessFul after exi negotiation is finished successfully or not
82 final NetconfMessage startExiMessage = sessionPreferences.getStartExiMessage();
83 if (shouldUseExi(netconfMessage) && startExiMessage instanceof NetconfStartExiMessage) {
84 LOG.debug("Netconf session {} should use exi.", session);
85 tryToInitiateExi(session, (NetconfStartExiMessage) startExiMessage);
87 // Exi is not supported, release session immediately
88 LOG.debug("Netconf session {} isn't capable of using exi.", session);
89 negotiationSuccessful(session);
94 * Initiates exi communication by sending start-exi message and waiting for positive/negative response.
96 * @param startExiMessage Exi message for initilization of exi communication.
98 void tryToInitiateExi(final NetconfClientSession session, final NetconfStartExiMessage startExiMessage) {
99 channel.pipeline().addAfter(AbstractChannelInitializer.NETCONF_MESSAGE_DECODER,
100 ExiConfirmationInboundHandler.EXI_CONFIRMED_HANDLER,
101 new ExiConfirmationInboundHandler(session, startExiMessage));
103 session.sendMessage(startExiMessage).addListener(channelFuture -> {
104 if (!channelFuture.isSuccess()) {
105 LOG.warn("Failed to send start-exi message {} on session {}", startExiMessage, session,
106 channelFuture.cause());
107 channel.pipeline().remove(ExiConfirmationInboundHandler.EXI_CONFIRMED_HANDLER);
109 LOG.trace("Start-exi message {} sent to socket on session {}", startExiMessage, session);
114 @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST",
115 justification = "SpotBugs does not understand generic cast of sessionPreferences")
116 private boolean shouldUseExi(final NetconfHelloMessage helloMsg) {
117 return containsExi10Capability(helloMsg.getDocument())
118 && containsExi10Capability(sessionPreferences.getHelloMessage().getDocument());
121 private static boolean containsExi10Capability(final Document doc) {
122 final NodeList nList = doc.getElementsByTagName(XmlNetconfConstants.CAPABILITY);
123 for (int i = 0; i < nList.getLength(); i++) {
124 if (nList.item(i).getTextContent().contains(EXI_1_0_CAPABILITY_MARKER)) {
131 private static long extractSessionId(final Document doc) {
132 String textContent = getSessionIdWithXPath(doc, SESSION_ID_X_PATH);
133 if (Strings.isNullOrEmpty(textContent)) {
134 textContent = getSessionIdWithXPath(doc, SESSION_ID_X_PATH_NO_NAMESPACE);
135 if (Strings.isNullOrEmpty(textContent)) {
136 throw new IllegalStateException("Session id not received from server, hello message: " + XmlUtil
141 return Long.parseLong(textContent);
144 private static String getSessionIdWithXPath(final Document doc, final XPathExpression sessionIdXPath) {
145 final Node sessionIdNode = (Node) XmlUtil.evaluateXPath(sessionIdXPath, doc, XPathConstants.NODE);
146 return sessionIdNode != null ? sessionIdNode.getTextContent() : null;
150 protected NetconfClientSession getSession(final NetconfClientSessionListener sessionListener, final Channel channel,
151 final NetconfHelloMessage message) {
152 final long sessionId = extractSessionId(message.getDocument());
154 // Copy here is important: it disconnects the strings from the document
155 Set<String> capabilities = ImmutableSet.copyOf(NetconfMessageUtil.extractCapabilitiesFromHello(message
158 capabilities = INTERNER.intern(capabilities);
160 return new NetconfClientSession(sessionListener, channel, sessionId, capabilities);
164 * Handler to process response for start-exi message.
166 private final class ExiConfirmationInboundHandler extends ChannelInboundHandlerAdapter {
167 private static final String EXI_CONFIRMED_HANDLER = "exiConfirmedHandler";
169 private final NetconfClientSession session;
170 private final NetconfStartExiMessage startExiMessage;
172 ExiConfirmationInboundHandler(final NetconfClientSession session,
173 final NetconfStartExiMessage startExiMessage) {
174 this.session = session;
175 this.startExiMessage = startExiMessage;
178 @SuppressWarnings("checkstyle:IllegalCatch")
180 public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
181 ctx.pipeline().remove(ExiConfirmationInboundHandler.EXI_CONFIRMED_HANDLER);
183 NetconfMessage netconfMessage = (NetconfMessage) msg;
185 // Ok response to start-exi, try to add exi handlers
186 if (NetconfMessageUtil.isOKMessage(netconfMessage)) {
187 LOG.trace("Positive response on start-exi call received on session {}", session);
189 session.startExiCommunication(startExiMessage);
190 } catch (RuntimeException e) {
191 // Unable to add exi, continue without exi
192 LOG.warn("Unable to start exi communication, Communication will continue without exi on session "
197 } else if (NetconfMessageUtil.isErrorMessage(netconfMessage)) {
199 "Error response to start-exi message {}, Communication will continue without exi on session {}",
200 netconfMessage, session);
202 // Unexpected response to start-exi, throwing message away, continue without exi
204 LOG.warn("Unexpected response to start-exi message, should be ok, was {}, "
205 + "Communication will continue without exi "
206 + "and response message will be thrown away on session {}",
207 netconfMessage, session);
210 negotiationSuccessful(session);