1d27ff7125af951ae73f41c30303c5a69a304349
[netconf.git] / protocol / netconf-api / src / main / java / org / opendaylight / netconf / api / messages / NetconfMessage.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 package org.opendaylight.netconf.api.messages;
9
10 import static java.util.Objects.requireNonNull;
11
12 import java.io.StringWriter;
13 import java.util.Map;
14 import javax.xml.transform.OutputKeys;
15 import javax.xml.transform.Transformer;
16 import javax.xml.transform.TransformerConfigurationException;
17 import javax.xml.transform.TransformerException;
18 import javax.xml.transform.dom.DOMSource;
19 import javax.xml.transform.stream.StreamResult;
20 import org.eclipse.jdt.annotation.NonNull;
21 import org.opendaylight.netconf.api.DocumentedException;
22 import org.opendaylight.netconf.api.NamespaceURN;
23 import org.opendaylight.netconf.api.xml.XmlUtil;
24 import org.opendaylight.yangtools.yang.common.ErrorSeverity;
25 import org.opendaylight.yangtools.yang.common.ErrorTag;
26 import org.opendaylight.yangtools.yang.common.ErrorType;
27 import org.w3c.dom.Document;
28
29 /**
30  * NetconfMessage represents a wrapper around {@link Document}.
31  */
32 public class NetconfMessage {
33     private static final Transformer TRANSFORMER;
34
35     static {
36         final Transformer t;
37         try {
38             t = XmlUtil.newIndentingTransformer();
39         } catch (TransformerConfigurationException e) {
40             throw new ExceptionInInitializerError(e);
41         }
42         t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
43
44         TRANSFORMER = t;
45     }
46
47     private final @NonNull Document document;
48
49     public NetconfMessage(final Document document) {
50         this.document = requireNonNull(document);
51     }
52
53     /**
54      * Create a new {@link NetconfMessage} based on supplied document.
55      *
56      * @param document A {@link Document} backing the message
57      * @return A {@link NetconfMessage}
58      * @throws NullPointerException if {@code document} is {@code null}
59      * @throws DocumentedException if the {@code document} does not match a known {@link NetconfMessage}
60      */
61     public static @NonNull NetconfMessage of(final Document document) throws DocumentedException {
62         final var root = document.getDocumentElement();
63         final var rootName = root.getLocalName();
64         final var rootNs = root.getNamespaceURI();
65
66         if (rootNs != null) {
67             switch (rootNs) {
68                 case NamespaceURN.BASE:
69                     switch (rootName) {
70                         case HelloMessage.ELEMENT_NAME:
71                             return new HelloMessage(document);
72                         case RpcMessage.ELEMENT_NAME:
73                             return RpcMessage.ofChecked(document);
74                         case RpcReplyMessage.ELEMENT_NAME:
75                             return new RpcReplyMessage(document);
76                         default:
77                             break;
78                     }
79                     break;
80                 case NamespaceURN.NOTIFICATION:
81                     switch (rootName) {
82                         case NotificationMessage.ELEMENT_NAME:
83                             return NotificationMessage.ofChecked(document);
84                         default:
85                             break;
86                     }
87                     break;
88                 default:
89                     throw new DocumentedException("Unhandled namespace " + rootNs, ErrorType.PROTOCOL,
90                         ErrorTag.UNKNOWN_NAMESPACE, ErrorSeverity.ERROR, Map.of("bad-element", rootName));
91
92             }
93         } else if (HelloMessage.ELEMENT_NAME.equals(rootName)) {
94             // accept even if hello has no namespace
95             return new HelloMessage(document);
96         }
97         throw new DocumentedException("Unknown element " + rootName, ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT,
98             ErrorSeverity.ERROR, Map.of("bad-element", rootName));
99     }
100
101     public final @NonNull Document getDocument() {
102         return document;
103     }
104
105     @Override
106     public final String toString() {
107         final var result = new StreamResult(new StringWriter());
108         final var source = new DOMSource(document.getDocumentElement());
109
110         try {
111             // Slight critical section is a tradeoff. This should be reasonably fast.
112             synchronized (TRANSFORMER) {
113                 TRANSFORMER.transform(source, result);
114             }
115         } catch (TransformerException e) {
116             throw new IllegalStateException("Failed to encode document", e);
117         }
118
119         return result.getWriter().toString();
120     }
121 }