Skip to content

Commit 1dd5847

Browse files
committed
Re-factor Hello and introduce parsing of RpcReply
1 parent 80e6eca commit 1dd5847

File tree

9 files changed

+1015
-61
lines changed

9 files changed

+1015
-61
lines changed

src/main/java/net/juniper/netconf/Device.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ private String createHelloRPC(List<String> capabilities) {
170170
return Hello.builder()
171171
.capabilities(capabilities)
172172
.build()
173-
.toXML()
173+
.getXml()
174174
+ NetconfConstants.DEVICE_PROMPT;
175175
}
176176

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 lombok.EqualsAndHashCode;
4+
import lombok.ToString;
5+
import lombok.Value;
6+
import lombok.experimental.NonFinal;
7+
import net.juniper.netconf.NetconfConstants;
8+
import org.w3c.dom.Document;
9+
import org.w3c.dom.Element;
10+
11+
import javax.xml.parsers.DocumentBuilderFactory;
12+
import javax.xml.parsers.ParserConfigurationException;
13+
import javax.xml.transform.OutputKeys;
14+
import javax.xml.transform.Transformer;
15+
import javax.xml.transform.TransformerException;
16+
import javax.xml.transform.TransformerFactory;
17+
import javax.xml.transform.dom.DOMSource;
18+
import javax.xml.transform.stream.StreamResult;
19+
import java.io.StringWriter;
20+
21+
import static java.lang.String.format;
22+
23+
@Value
24+
@NonFinal
25+
public abstract class AbstractNetconfElement {
26+
27+
@ToString.Exclude
28+
@EqualsAndHashCode.Exclude
29+
Document document;
30+
31+
@ToString.Exclude
32+
String xml;
33+
34+
protected AbstractNetconfElement(final Document document) {
35+
this.document = document;
36+
this.xml = createXml(document);
37+
}
38+
39+
protected static Document createBlankDocument() {
40+
try {
41+
return createDocumentBuilderFactory().newDocumentBuilder().newDocument();
42+
} catch (final ParserConfigurationException e) {
43+
throw new IllegalStateException("Unable to create document builder", e);
44+
}
45+
}
46+
47+
protected static DocumentBuilderFactory createDocumentBuilderFactory() {
48+
final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
49+
documentBuilderFactory.setNamespaceAware(true);
50+
return documentBuilderFactory;
51+
}
52+
53+
protected static String createXml(final Document document) {
54+
try {
55+
final TransformerFactory transformerFactory = TransformerFactory.newInstance();
56+
final Transformer transformer = transformerFactory.newTransformer();
57+
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
58+
final StringWriter stringWriter = new StringWriter();
59+
transformer.transform(new DOMSource(document), new StreamResult(stringWriter));
60+
return stringWriter.toString();
61+
} catch (final TransformerException e) {
62+
throw new IllegalStateException("Unable to transform document to XML", e);
63+
}
64+
}
65+
66+
protected static String getXpathFor(final String elementName) {
67+
return format("/*[namespace-uri()='urn:ietf:params:xml:ns:netconf:base:1.0' and local-name()='%s']", elementName);
68+
}
69+
70+
protected static Element appendElementWithText(
71+
final Document document,
72+
final Element parentElement,
73+
final String namespacePrefix,
74+
final String elementName,
75+
final String text) {
76+
77+
if (text != null) {
78+
final Element childElement = document.createElementNS(NetconfConstants.URN_XML_NS_NETCONF_BASE_1_0, elementName);
79+
childElement.setPrefix(namespacePrefix);
80+
childElement.setTextContent(text);
81+
parentElement.appendChild(childElement);
82+
return childElement;
83+
} else {
84+
return null;
85+
}
86+
}
87+
88+
protected static String getAttribute(final Element element, final String attributeName) {
89+
if (element != null && element.hasAttribute(attributeName)) {
90+
return element.getAttribute(attributeName);
91+
} else {
92+
return null;
93+
}
94+
}
95+
96+
protected static String getTextContent(final Element element) {
97+
if (element == null) {
98+
return null;
99+
} else {
100+
return trim(element.getTextContent());
101+
}
102+
}
103+
104+
protected static String trim(final String string) {
105+
return string == null ? null : string.trim();
106+
}
107+
}
Lines changed: 25 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
package net.juniper.netconf.element;
22

33
import lombok.Builder;
4-
import lombok.Data;
54
import lombok.EqualsAndHashCode;
65
import lombok.Singular;
7-
import lombok.ToString;
6+
import lombok.Value;
87
import lombok.extern.slf4j.Slf4j;
98
import net.juniper.netconf.NetconfConstants;
109
import org.w3c.dom.Document;
@@ -14,50 +13,37 @@
1413
import org.xml.sax.InputSource;
1514
import org.xml.sax.SAXException;
1615

17-
import javax.xml.parsers.DocumentBuilderFactory;
1816
import javax.xml.parsers.ParserConfigurationException;
19-
import javax.xml.transform.OutputKeys;
20-
import javax.xml.transform.Transformer;
21-
import javax.xml.transform.TransformerException;
22-
import javax.xml.transform.TransformerFactory;
23-
import javax.xml.transform.dom.DOMSource;
24-
import javax.xml.transform.stream.StreamResult;
2517
import javax.xml.xpath.XPath;
2618
import javax.xml.xpath.XPathConstants;
2719
import javax.xml.xpath.XPathExpressionException;
2820
import javax.xml.xpath.XPathFactory;
2921
import java.io.IOException;
3022
import java.io.StringReader;
31-
import java.io.StringWriter;
3223
import java.util.List;
3324

3425
/**
3526
* Class to represent a NETCONF hello element - https://datatracker.ietf.org/doc/html/rfc6241#section-8.1
3627
*/
37-
@Data
3828
@Slf4j
39-
public class Hello {
29+
@Value
30+
@EqualsAndHashCode(callSuper = true)
31+
public class Hello extends AbstractNetconfElement {
4032

41-
@ToString.Exclude
42-
@EqualsAndHashCode.Exclude
43-
private final Document document;
33+
private static final String XPATH_HELLO = getXpathFor("hello");
34+
private static final String XPATH_HELLO_SESSION_ID = XPATH_HELLO + getXpathFor("session-id");
35+
private static final String XPATH_HELLO_CAPABILITIES = XPATH_HELLO + getXpathFor("capabilities");
36+
private static final String XPATH_HELLO_CAPABILITIES_CAPABILITY = XPATH_HELLO_CAPABILITIES + getXpathFor("capability");
4437

45-
@ToString.Exclude
46-
private final String xml;
47-
48-
private final String sessionId;
38+
String sessionId;
4939

5040
@Singular("capability")
51-
private final List<String> capabilities;
41+
List<String> capabilities;
5242

5343
public boolean hasCapability(final String capability) {
5444
return capabilities.contains(capability);
5545
}
5646

57-
public String toXML() {
58-
return xml;
59-
}
60-
6147
/**
6248
* Creates a Hello object based on the supplied XML.
6349
*
@@ -71,24 +57,20 @@ public String toXML() {
7157
public static Hello from(final String xml)
7258
throws ParserConfigurationException, IOException, SAXException, XPathExpressionException {
7359

74-
final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
75-
documentBuilderFactory.setNamespaceAware(true);
76-
final Document document = documentBuilderFactory.newDocumentBuilder()
60+
final Document document = createDocumentBuilderFactory().newDocumentBuilder()
7761
.parse(new InputSource(new StringReader(xml)));
7862
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);
63+
final String sessionId = xPath.evaluate(XPATH_HELLO_SESSION_ID, document);
8064
final HelloBuilder builder = Hello.builder()
8165
.originalDocument(document)
8266
.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);
67+
final NodeList capabilities = (NodeList) xPath.evaluate(XPATH_HELLO_CAPABILITIES_CAPABILITY, document, XPathConstants.NODESET);
8468
for (int i = 0; i < capabilities.getLength(); i++) {
8569
final Node node = capabilities.item(i);
8670
builder.capability(node.getTextContent());
8771
}
8872
final Hello hello = builder.build();
89-
if (log.isInfoEnabled()) {
90-
log.info("hello is: {}", hello.toXML());
91-
}
73+
log.info("hello is: {}", hello.getXml());
9274
return hello;
9375
}
9476

@@ -98,29 +80,29 @@ private Hello(
9880
final String namespacePrefix,
9981
final String sessionId,
10082
@Singular("capability") final List<String> capabilities) {
83+
super(getDocument(originalDocument, namespacePrefix, sessionId, capabilities));
10184
this.sessionId = sessionId;
10285
this.capabilities = capabilities;
86+
}
87+
88+
private static Document getDocument(
89+
final Document originalDocument,
90+
final String namespacePrefix,
91+
final String sessionId,
92+
final List<String> capabilities) {
10393
if (originalDocument != null) {
104-
this.document = originalDocument;
94+
return originalDocument;
10595
} else {
106-
this.document = createDocument(namespacePrefix, sessionId, capabilities);
96+
return createDocument(namespacePrefix, sessionId, capabilities);
10797
}
108-
this.xml = createXml(document);
10998
}
11099

111100
private static Document createDocument(
112101
final String namespacePrefix,
113102
final String sessionId,
114103
final List<String> capabilities) {
115104

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-
}
105+
final Document createdDocument = createBlankDocument();
124106

125107
final Element helloElement
126108
= createdDocument.createElementNS(NetconfConstants.URN_XML_NS_NETCONF_BASE_1_0, "hello");
@@ -149,17 +131,4 @@ private static Document createDocument(
149131
return createdDocument;
150132
}
151133

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-
165134
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package net.juniper.netconf.element;
2+
3+
import lombok.AccessLevel;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
import lombok.RequiredArgsConstructor;
7+
import lombok.Value;
8+
9+
/**
10+
* Class to represent a NETCONF rpc-error element - https://datatracker.ietf.org/doc/html/rfc6241#section-4.3
11+
*/
12+
@Value
13+
@Builder
14+
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
15+
public class RpcError {
16+
17+
ErrorType errorType;
18+
ErrorTag errorTag;
19+
ErrorSeverity errorSeverity;
20+
String errorPath;
21+
String errorMessage;
22+
String errorMessageLanguage;
23+
RpcErrorInfo errorInfo;
24+
25+
@Getter
26+
@RequiredArgsConstructor
27+
public enum ErrorType {
28+
TRANSPORT("transport"), RPC("rpc"), PROTOCOL("protocol"), APPLICATION("application");
29+
30+
private final String textContent;
31+
32+
public static ErrorType from(final String textContent) {
33+
for (final ErrorType errorType : ErrorType.values()) {
34+
if (errorType.textContent.equals(textContent)) {
35+
return errorType;
36+
}
37+
}
38+
return null;
39+
}
40+
}
41+
42+
@Getter
43+
@RequiredArgsConstructor
44+
public enum ErrorTag {
45+
IN_USE("in-use"),
46+
INVALID_VALUE("invalid-value"),
47+
TOO_BIG("too-big"),
48+
MISSING_ATTRIBUTE("missing-attribute"),
49+
BAD_ATTRIBUTE("bad-attribute"),
50+
UNKNOWN_ATTRIBUTE("unknown-attribute"),
51+
MISSING_ELEMENT("missing-element"),
52+
BAD_ELEMENT("bad-element"),
53+
UNKNOWN_ELEMENT("unknown-element"),
54+
UNKNOWN_NAMESPACE("unknown-namespace"),
55+
ACCESS_DENIED("access-denied"),
56+
LOCK_DENIED("lock-denied"),
57+
DATA_EXISTS("data-exists"),
58+
DATA_MISSING("data-missing"),
59+
OPERATION_NOT_SUPPORTED("operation-not-supported"),
60+
OPERATION_FAILED("operation-failed"),
61+
PARTIAL_OPERATION("partial-operation"),
62+
MALFORMED_MESSAGE("malformed-message");
63+
64+
private final String textContent;
65+
66+
public static ErrorTag from(final String textContent) {
67+
for (final ErrorTag errorTag : ErrorTag.values()) {
68+
if (errorTag.textContent.equals(textContent)) {
69+
return errorTag;
70+
}
71+
}
72+
return null;
73+
}
74+
}
75+
76+
@Getter
77+
@RequiredArgsConstructor
78+
public enum ErrorSeverity {
79+
ERROR("error"), WARNING("warning");
80+
81+
private final String textContent;
82+
83+
public static ErrorSeverity from(final String textContent) {
84+
for (final ErrorSeverity errorSeverity : ErrorSeverity.values()) {
85+
if (errorSeverity.textContent.equals(textContent)) {
86+
return errorSeverity;
87+
}
88+
}
89+
return null;
90+
}
91+
}
92+
93+
/**
94+
* Class to represent a NETCONF rpc error-info element - https://datatracker.ietf.org/doc/html/rfc6241#section-4.3
95+
*/
96+
@Value
97+
@Builder
98+
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
99+
public static class RpcErrorInfo {
100+
101+
String badAttribute;
102+
String badElement;
103+
String badNamespace;
104+
String sessionId;
105+
String okElement;
106+
String errElement;
107+
String noOpElement;
108+
109+
}
110+
}

0 commit comments

Comments
 (0)