* 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.Interner;
import com.google.common.collect.Interners;
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 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.checkerframework.checker.index.qual.NonNegative;
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.w3c.dom.Node;
import org.w3c.dom.NodeList;
-public class NetconfClientSessionNegotiator extends
- AbstractNetconfSessionNegotiator<NetconfClientSessionPreferences, NetconfClientSession, NetconfClientSessionListener>
-{
+// Non-final for mocking
+class NetconfClientSessionNegotiator
+ extends AbstractNetconfSessionNegotiator<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";
private static final Interner<Set<String>> INTERNER = Interners.newWeakInterner();
- 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);
+ private final NetconfStartExiMessage startExi;
+
+ NetconfClientSessionNegotiator(final NetconfHelloMessage hello, final NetconfStartExiMessage startExi,
+ final Promise<NetconfClientSession> promise, final Channel channel, final Timer timer,
+ final NetconfClientSessionListener sessionListener, final long connectionTimeoutMillis,
+ final @NonNegative int maximumIncomingChunkSize) {
+ super(hello, promise, channel, timer, sessionListener, connectionTimeoutMillis, maximumIncomingChunkSize);
+ this.startExi = startExi;
}
+ @SuppressWarnings("checkstyle:IllegalCatch")
@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)) {
+ if (startExi != null && shouldUseExi(netconfMessage)) {
LOG.debug("Netconf session {} should use exi.", session);
- NetconfStartExiMessage startExiMessage = (NetconfStartExiMessage) sessionPreferences.getStartExiMessage();
- tryToInitiateExi(session, startExiMessage);
+ tryToInitiateExi(session, startExi);
} else {
// Exi is not supported, release session immediately
LOG.debug("Netconf session {} isn't capable of using exi.", session);
/**
* 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);
}
});
}
private boolean shouldUseExi(final NetconfHelloMessage helloMsg) {
- return containsExi10Capability(helloMsg.getDocument())
- && containsExi10Capability(sessionPreferences.getHelloMessage().getDocument());
+ return containsExi10Capability(helloMsg.getDocument()) && containsExi10Capability(localHello().getDocument());
}
private static boolean containsExi10Capability(final Document doc) {
}
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) {
@Override
protected NetconfClientSession getSession(final NetconfClientSessionListener sessionListener, final Channel channel,
- final NetconfHelloMessage message) throws NetconfDocumentedException {
+ final NetconfHelloMessage message) {
final long sessionId = extractSessionId(message.getDocument());
// Copy here is important: it disconnects the strings from the document
- Set<String> capabilities = ImmutableSet.copyOf(NetconfMessageUtil.extractCapabilitiesFromHello(message.getDocument()));
+ Set<String> capabilities = ImmutableSet.copyOf(NetconfMessageUtil.extractCapabilitiesFromHello(message
+ .getDocument()));
capabilities = INTERNER.intern(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";
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);
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);