Bug 624 - Separate netty and exi specific classes from netconf-util.
[controller.git] / opendaylight / netconf / netconf-client / src / main / java / org / opendaylight / controller / 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.controller.netconf.client;
10
11 import io.netty.channel.Channel;
12 import io.netty.channel.ChannelFuture;
13 import io.netty.channel.ChannelFutureListener;
14 import io.netty.channel.ChannelHandlerContext;
15 import io.netty.channel.ChannelInboundHandlerAdapter;
16 import io.netty.util.Timer;
17 import io.netty.util.concurrent.Promise;
18 import java.util.Collection;
19 import javax.xml.xpath.XPathConstants;
20 import javax.xml.xpath.XPathExpression;
21 import org.opendaylight.controller.netconf.api.NetconfClientSessionPreferences;
22 import org.opendaylight.controller.netconf.api.NetconfDocumentedException;
23 import org.opendaylight.controller.netconf.api.NetconfMessage;
24 import org.opendaylight.controller.netconf.nettyutil.AbstractChannelInitializer;
25 import org.opendaylight.controller.netconf.nettyutil.AbstractNetconfSessionNegotiator;
26 import org.opendaylight.controller.netconf.nettyutil.handler.exi.NetconfStartExiMessage;
27 import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessage;
28 import org.opendaylight.controller.netconf.util.messages.NetconfMessageUtil;
29 import org.opendaylight.controller.netconf.util.xml.XMLNetconfUtil;
30 import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants;
31 import org.opendaylight.controller.netconf.util.xml.XmlUtil;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34 import org.w3c.dom.Document;
35 import org.w3c.dom.Node;
36 import org.w3c.dom.NodeList;
37
38 public class NetconfClientSessionNegotiator extends
39         AbstractNetconfSessionNegotiator<NetconfClientSessionPreferences, NetconfClientSession, NetconfClientSessionListener>
40 {
41     private static final Logger logger = LoggerFactory.getLogger(NetconfClientSessionNegotiator.class);
42
43     private static final XPathExpression sessionIdXPath = XMLNetconfUtil
44             .compileXPath("/netconf:hello/netconf:session-id");
45
46     private static final String EXI_1_0_CAPABILITY_MARKER = "exi:1.0";
47
48     protected NetconfClientSessionNegotiator(NetconfClientSessionPreferences sessionPreferences,
49                                              Promise<NetconfClientSession> promise,
50                                              Channel channel,
51                                              Timer timer,
52                                              NetconfClientSessionListener sessionListener,
53                                              long connectionTimeoutMillis) {
54         super(sessionPreferences, promise, channel, timer, sessionListener, connectionTimeoutMillis);
55     }
56
57     @Override
58     protected void handleMessage(NetconfHelloMessage netconfMessage) throws NetconfDocumentedException {
59         final NetconfClientSession session = getSessionForHelloMessage(netconfMessage);
60         replaceHelloMessageInboundHandler(session);
61
62         // If exi should be used, try to initiate exi communication
63         // Call negotiationSuccessFul after exi negotiation is finished successfully or not
64         if (shouldUseExi(netconfMessage)) {
65             logger.debug("Netconf session {} should use exi.", session);
66             NetconfStartExiMessage startExiMessage = (NetconfStartExiMessage) sessionPreferences.getStartExiMessage();
67             tryToInitiateExi(session, startExiMessage);
68         // Exi is not supported, release session immediately
69         } else {
70             logger.debug("Netconf session {} isn't capable of using exi.", session);
71             negotiationSuccessful(session);
72         }
73     }
74
75     /**
76      * Initiates exi communication by sending start-exi message and waiting for positive/negative response.
77      *
78      * @param startExiMessage
79      */
80     void tryToInitiateExi(final NetconfClientSession session, final NetconfStartExiMessage startExiMessage) {
81         channel.pipeline().addAfter(AbstractChannelInitializer.NETCONF_MESSAGE_DECODER,
82                 ExiConfirmationInboundHandler.EXI_CONFIRMED_HANDLER,
83                 new ExiConfirmationInboundHandler(session, startExiMessage));
84
85         session.sendMessage(startExiMessage).addListener(new ChannelFutureListener() {
86             @Override
87             public void operationComplete(final ChannelFuture f) {
88                 if (!f.isSuccess()) {
89                     logger.warn("Failed to send start-exi message {} on session {}", startExiMessage, this, f.cause());
90                     channel.pipeline().remove(ExiConfirmationInboundHandler.EXI_CONFIRMED_HANDLER);
91                 } else {
92                     logger.trace("Start-exi message {} sent to socket on session {}", startExiMessage, this);
93                 }
94             }
95         });
96     }
97
98     private boolean shouldUseExi(NetconfHelloMessage helloMsg) {
99         return containsExi10Capability(helloMsg.getDocument())
100                 && containsExi10Capability(sessionPreferences.getHelloMessage().getDocument());
101     }
102
103     private boolean containsExi10Capability(final Document doc) {
104         final NodeList nList = doc.getElementsByTagName(XmlNetconfConstants.CAPABILITY);
105         for (int i = 0; i < nList.getLength(); i++) {
106             if (nList.item(i).getTextContent().contains(EXI_1_0_CAPABILITY_MARKER)) {
107                 return true;
108             }
109         }
110         return false;
111     }
112
113     private long extractSessionId(Document doc) {
114         final Node sessionIdNode = (Node) XmlUtil.evaluateXPath(sessionIdXPath, doc, XPathConstants.NODE);
115         String textContent = sessionIdNode.getTextContent();
116         if (textContent == null || textContent.equals("")) {
117             throw new IllegalStateException("Session id not received from server");
118         }
119
120         return Long.valueOf(textContent);
121     }
122
123     @Override
124     protected NetconfClientSession getSession(NetconfClientSessionListener sessionListener, Channel channel,
125             NetconfHelloMessage message) throws NetconfDocumentedException {
126         long sessionId = extractSessionId(message.getDocument());
127         Collection<String> capabilities = NetconfMessageUtil.extractCapabilitiesFromHello(message.getDocument());
128         return new NetconfClientSession(sessionListener, channel, sessionId, capabilities);
129     }
130
131     /**
132      * Handler to process response for start-exi message
133      */
134     private final class ExiConfirmationInboundHandler extends ChannelInboundHandlerAdapter {
135         private static final String EXI_CONFIRMED_HANDLER = "exiConfirmedHandler";
136
137         private final NetconfClientSession session;
138         private NetconfStartExiMessage startExiMessage;
139
140         ExiConfirmationInboundHandler(NetconfClientSession session, final NetconfStartExiMessage startExiMessage) {
141             this.session = session;
142             this.startExiMessage = startExiMessage;
143         }
144
145         @Override
146         public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
147             ctx.pipeline().remove(ExiConfirmationInboundHandler.EXI_CONFIRMED_HANDLER);
148
149             NetconfMessage netconfMessage = (NetconfMessage) msg;
150
151             // Ok response to start-exi, try to add exi handlers
152             if (NetconfMessageUtil.isOKMessage(netconfMessage)) {
153                 logger.trace("Positive response on start-exi call received on session {}", session);
154                 try {
155                     session.startExiCommunication(startExiMessage);
156                 } catch (RuntimeException e) {
157                     // Unable to add exi, continue without exi
158                     logger.warn("Unable to start exi communication, Communication will continue without exi on session {}", session, e);
159                 }
160
161                 // Error response
162             } else if(NetconfMessageUtil.isErrorMessage(netconfMessage)) {
163                 logger.warn(
164                         "Error response to start-exi message {}, Communication will continue without exi on session {}",
165                         XmlUtil.toString(netconfMessage.getDocument()), session);
166
167                 // Unexpected response to start-exi, throwing message away, continue without exi
168             } else {
169                 logger.warn(
170                         "Unexpected response to start-exi message, should be ok, was {}, " +
171                                 "Communication will continue without exi and response message will be thrown away on session {}",
172                         XmlUtil.toString(netconfMessage.getDocument()), session);
173             }
174
175             negotiationSuccessful(session);
176         }
177     }
178
179 }