Skip to content

Commit 5c021c7

Browse files
committed
Add Hello element with parsing and toXML() methods
1 parent ae74df1 commit 5c021c7

File tree

3 files changed

+280
-1
lines changed

3 files changed

+280
-1
lines changed

pom.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
<groupId>net.juniper.netconf</groupId>
99
<artifactId>netconf-java</artifactId>
10-
<version>2.1.1.4</version>
10+
<version>2.1.1.5-SNAPSHOT</version>
1111
<packaging>jar</packaging>
1212

1313
<properties>
@@ -232,5 +232,12 @@
232232
<version>30.0-jre</version>
233233
</dependency>
234234

235+
<dependency>
236+
<groupId>org.xmlunit</groupId>
237+
<artifactId>xmlunit-assertj</artifactId>
238+
<version>2.8.2</version>
239+
<scope>test</scope>
240+
</dependency>
241+
235242
</dependencies>
236243
</project>
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package net.juniper.netconf.element;
2+
3+
import lombok.Builder;
4+
import lombok.Data;
5+
import lombok.EqualsAndHashCode;
6+
import lombok.Singular;
7+
import lombok.ToString;
8+
import lombok.extern.slf4j.Slf4j;
9+
import net.juniper.netconf.NetconfConstants;
10+
import org.w3c.dom.Document;
11+
import org.w3c.dom.Element;
12+
import org.w3c.dom.Node;
13+
import org.w3c.dom.NodeList;
14+
import org.xml.sax.InputSource;
15+
import org.xml.sax.SAXException;
16+
17+
import java.io.IOException;
18+
import java.io.StringReader;
19+
import java.io.StringWriter;
20+
import java.util.List;
21+
import javax.xml.parsers.DocumentBuilderFactory;
22+
import javax.xml.parsers.ParserConfigurationException;
23+
import javax.xml.transform.OutputKeys;
24+
import javax.xml.transform.Transformer;
25+
import javax.xml.transform.TransformerException;
26+
import javax.xml.transform.TransformerFactory;
27+
import javax.xml.transform.dom.DOMSource;
28+
import javax.xml.transform.stream.StreamResult;
29+
import javax.xml.xpath.XPath;
30+
import javax.xml.xpath.XPathConstants;
31+
import javax.xml.xpath.XPathExpressionException;
32+
import javax.xml.xpath.XPathFactory;
33+
34+
/**
35+
* Class to represent a NETCONF hello element - https://datatracker.ietf.org/doc/html/rfc6241#section-8.1
36+
*/
37+
@Data
38+
@Slf4j
39+
public class Hello {
40+
41+
@ToString.Exclude
42+
@EqualsAndHashCode.Exclude
43+
private final Document document;
44+
45+
@ToString.Exclude
46+
private final String xml;
47+
48+
private final String sessionId;
49+
50+
@Singular("capability")
51+
private final List<String> capabilities;
52+
53+
public boolean hasCapability(final String capability) {
54+
return capabilities.contains(capability);
55+
}
56+
57+
public String toXML() {
58+
return xml;
59+
}
60+
61+
/**
62+
* Creates a Hello object based on the supplied XML.
63+
*
64+
* @param xml The XML of the NETCONF &lt;hello&gt;
65+
* @return an new, immutable, Hello object.
66+
* @throws ParserConfigurationException If the XML parser cannot be created
67+
* @throws IOException If the XML cannot be read
68+
* @throws SAXException If the XML cannot be parsed
69+
* @throws XPathExpressionException If there is a problem in the parsing expressions
70+
*/
71+
public static Hello from(final String xml)
72+
throws ParserConfigurationException, IOException, SAXException, XPathExpressionException {
73+
74+
final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
75+
documentBuilderFactory.setNamespaceAware(true);
76+
final Document document = documentBuilderFactory.newDocumentBuilder()
77+
.parse(new InputSource(new StringReader(xml)));
78+
final XPath xPath = XPathFactory.newInstance().newXPath();
79+
final String sessionId = xPath.evaluate("/*[namespace-uri()='urn:ietf:params:xml:ns:netconf:base:1.0' and local-name()='hello']/*[namespace-uri()='urn:ietf:params:xml:ns:netconf:base:1.0' and local-name()='session-id']", document);
80+
final HelloBuilder builder = Hello.builder()
81+
.originalDocument(document)
82+
.sessionId(sessionId);
83+
final NodeList capabilities = (NodeList) xPath.evaluate("/*[namespace-uri()='urn:ietf:params:xml:ns:netconf:base:1.0' and local-name()='hello']/*[namespace-uri()='urn:ietf:params:xml:ns:netconf:base:1.0' and local-name()='capabilities']/*[namespace-uri()='urn:ietf:params:xml:ns:netconf:base:1.0' and local-name()='capability']", document, XPathConstants.NODESET);
84+
for (int i = 0; i < capabilities.getLength(); i++) {
85+
final Node node = capabilities.item(i);
86+
builder.capability(node.getTextContent());
87+
}
88+
final Hello hello = builder.build();
89+
if (log.isInfoEnabled()) {
90+
log.info("hello is: {}", hello.toXML());
91+
}
92+
return hello;
93+
}
94+
95+
@Builder
96+
private Hello(
97+
final Document originalDocument,
98+
final String namespacePrefix,
99+
final String sessionId,
100+
@Singular("capability") final List<String> capabilities) {
101+
this.sessionId = sessionId;
102+
this.capabilities = capabilities;
103+
if (originalDocument != null) {
104+
this.document = originalDocument;
105+
} else {
106+
this.document = createDocument(namespacePrefix, sessionId, capabilities);
107+
}
108+
this.xml = createXml(document);
109+
}
110+
111+
private static Document createDocument(
112+
final String namespacePrefix,
113+
final String sessionId,
114+
final List<String> capabilities) {
115+
116+
final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
117+
documentBuilderFactory.setNamespaceAware(true);
118+
final Document createdDocument;
119+
try {
120+
createdDocument = documentBuilderFactory.newDocumentBuilder().newDocument();
121+
} catch (final ParserConfigurationException e) {
122+
throw new IllegalStateException("Unable to create document builder", e);
123+
}
124+
125+
final Element helloElement
126+
= createdDocument.createElementNS(NetconfConstants.URN_XML_NS_NETCONF_BASE_1_0, "hello");
127+
helloElement.setPrefix(namespacePrefix);
128+
createdDocument.appendChild(helloElement);
129+
130+
final Element capabilitiesElement
131+
= createdDocument.createElementNS(NetconfConstants.URN_XML_NS_NETCONF_BASE_1_0, "capabilities");
132+
capabilitiesElement.setPrefix(namespacePrefix);
133+
capabilities.forEach(capability -> {
134+
final Element capabilityElement =
135+
createdDocument.createElementNS(NetconfConstants.URN_XML_NS_NETCONF_BASE_1_0, "capability");
136+
capabilityElement.setTextContent(capability);
137+
capabilityElement.setPrefix(namespacePrefix);
138+
capabilitiesElement.appendChild(capabilityElement);
139+
});
140+
helloElement.appendChild(capabilitiesElement);
141+
142+
if (sessionId != null) {
143+
final Element sessionIdElement
144+
= createdDocument.createElementNS(NetconfConstants.URN_XML_NS_NETCONF_BASE_1_0, "session-id");
145+
sessionIdElement.setPrefix(namespacePrefix);
146+
sessionIdElement.setTextContent(sessionId);
147+
helloElement.appendChild(sessionIdElement);
148+
}
149+
return createdDocument;
150+
}
151+
152+
private static String createXml(final Document document) {
153+
try {
154+
final TransformerFactory transformerFactory = TransformerFactory.newInstance();
155+
final Transformer transformer = transformerFactory.newTransformer();
156+
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
157+
final StringWriter stringWriter = new StringWriter();
158+
transformer.transform(new DOMSource(document), new StreamResult(stringWriter));
159+
return stringWriter.toString();
160+
} catch (final TransformerException e) {
161+
throw new IllegalStateException("Unable to transform document to XML", e);
162+
}
163+
}
164+
165+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package net.juniper.netconf.element;
2+
3+
import org.junit.Test;
4+
import org.xmlunit.assertj.XmlAssert;
5+
6+
import static org.assertj.core.api.Assertions.assertThat;
7+
8+
public class HelloTest {
9+
10+
// Samples taken from https://www.juniper.net/documentation/us/en/software/junos/netconf/topics/concept/netconf-session-rfc-compliant.html
11+
public static final String HELLO_WITHOUT_NAMESPACE = ""
12+
+ "<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
13+
+ " <capabilities>\n"
14+
+ " <capability>urn:ietf:params:netconf:base:1.0</capability>\n"
15+
+ " <capability>urn:ietf:params:netconf:base:1.0#candidate</capability>\n"
16+
+ " <capability>urn:ietf:params:netconf:base:1.0#confirmed-commit</capability>\n"
17+
+ " <capability>urn:ietf:params:netconf:base:1.0#validate</capability>\n"
18+
+ " <capability>urn:ietf:params:netconf:base:1.0#url?protocol=http,ftp,file</capability>\n"
19+
+ " </capabilities>\n"
20+
+ " <session-id>27700</session-id>\n"
21+
+ "</hello>";
22+
23+
public static final String HELLO_WITH_NAMESPACE = "" +
24+
"<nc:hello xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
25+
+ " <nc:capabilities>\n"
26+
+ " <nc:capability>urn:ietf:params:netconf:base:1.0</nc:capability>\n"
27+
+ " <nc:capability>urn:ietf:params:netconf:base:1.0#candidate</nc:capability>\n"
28+
+ " <nc:capability>urn:ietf:params:netconf:base:1.0#confirmed-commit</nc:capability>\n"
29+
+ " <nc:capability>urn:ietf:params:netconf:base:1.0#validate</nc:capability>\n"
30+
+ " <nc:capability>urn:ietf:params:netconf:base:1.0#url?protocol=http,ftp,file</nc:capability>\n"
31+
+ " </nc:capabilities>\n"
32+
+ " <nc:session-id>27703</nc:session-id>\n"
33+
+ "</nc:hello>";
34+
35+
@Test
36+
public void willCreateAnObjectFromPacketWithoutNamespace() throws Exception {
37+
38+
final Hello hello = Hello.from(HELLO_WITHOUT_NAMESPACE);
39+
40+
assertThat(hello.getSessionId())
41+
.isEqualTo("27700");
42+
assertThat(hello.getCapabilities())
43+
.containsExactly(
44+
"urn:ietf:params:netconf:base:1.0",
45+
"urn:ietf:params:netconf:base:1.0#candidate",
46+
"urn:ietf:params:netconf:base:1.0#confirmed-commit",
47+
"urn:ietf:params:netconf:base:1.0#validate",
48+
"urn:ietf:params:netconf:base:1.0#url?protocol=http,ftp,file");
49+
assertThat(hello.hasCapability("urn:ietf:params:netconf:base:1.0#candidate"))
50+
.isTrue();
51+
}
52+
53+
@Test
54+
public void willCreateAnObjectFromPacketWithNamespace() throws Exception {
55+
56+
final Hello hello = Hello.from(HELLO_WITH_NAMESPACE);
57+
58+
assertThat(hello.getSessionId())
59+
.isEqualTo("27703");
60+
assertThat(hello.getCapabilities())
61+
.containsExactly(
62+
"urn:ietf:params:netconf:base:1.0",
63+
"urn:ietf:params:netconf:base:1.0#candidate",
64+
"urn:ietf:params:netconf:base:1.0#confirmed-commit",
65+
"urn:ietf:params:netconf:base:1.0#validate",
66+
"urn:ietf:params:netconf:base:1.0#url?protocol=http,ftp,file");
67+
assertThat(hello.hasCapability("urn:ietf:params:netconf:base:1.0#candidate"))
68+
.isTrue();
69+
}
70+
71+
@Test
72+
public void willCreateXmlFromAnObject() {
73+
74+
final Hello hello = Hello.builder()
75+
.capability("urn:ietf:params:netconf:base:1.0")
76+
.capability("urn:ietf:params:netconf:base:1.0#candidate")
77+
.capability("urn:ietf:params:netconf:base:1.0#confirmed-commit")
78+
.capability("urn:ietf:params:netconf:base:1.0#validate")
79+
.capability("urn:ietf:params:netconf:base:1.0#url?protocol=http,ftp,file")
80+
.sessionId("27700")
81+
.build();
82+
83+
XmlAssert.assertThat(hello.toXML())
84+
.and(HELLO_WITHOUT_NAMESPACE)
85+
.ignoreWhitespace()
86+
.areIdentical();
87+
}
88+
89+
@Test
90+
public void willCreateXmlWithNamespaceFromAnObject() {
91+
92+
final Hello hello = Hello.builder()
93+
.namespacePrefix("nc")
94+
.capability("urn:ietf:params:netconf:base:1.0")
95+
.capability("urn:ietf:params:netconf:base:1.0#candidate")
96+
.capability("urn:ietf:params:netconf:base:1.0#confirmed-commit")
97+
.capability("urn:ietf:params:netconf:base:1.0#validate")
98+
.capability("urn:ietf:params:netconf:base:1.0#url?protocol=http,ftp,file")
99+
.sessionId("27703")
100+
.build();
101+
102+
XmlAssert.assertThat(hello.toXML())
103+
.and(HELLO_WITH_NAMESPACE)
104+
.ignoreWhitespace()
105+
.areIdentical();
106+
}
107+
}

0 commit comments

Comments
 (0)