Remove DocumentedException.ErrorType
[netconf.git] / netconf / mdsal-netconf-connector / src / main / java / org / opendaylight / netconf / mdsal / connector / ops / CopyConfig.java
1 /*
2  * Copyright (c) 2018 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.mdsal.connector.ops;
9
10 import static org.opendaylight.netconf.api.xml.XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0;
11
12 import java.io.IOException;
13 import java.net.URI;
14 import java.net.URISyntaxException;
15 import java.nio.charset.StandardCharsets;
16 import java.nio.file.Files;
17 import java.nio.file.Path;
18 import java.nio.file.Paths;
19 import java.util.List;
20 import java.util.Optional;
21 import java.util.concurrent.ExecutionException;
22 import javax.xml.stream.XMLOutputFactory;
23 import javax.xml.stream.XMLStreamException;
24 import javax.xml.stream.XMLStreamWriter;
25 import javax.xml.transform.dom.DOMResult;
26 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
27 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
28 import org.opendaylight.netconf.api.DocumentedException;
29 import org.opendaylight.netconf.api.DocumentedException.ErrorTag;
30 import org.opendaylight.netconf.api.xml.XmlElement;
31 import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
32 import org.opendaylight.netconf.api.xml.XmlUtil;
33 import org.opendaylight.netconf.mdsal.connector.CurrentSchemaContext;
34 import org.opendaylight.netconf.mdsal.connector.TransactionProvider;
35 import org.opendaylight.yangtools.yang.common.ErrorSeverity;
36 import org.opendaylight.yangtools.yang.common.ErrorType;
37 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
38 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
39 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
40 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
41 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
42 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
43 import org.opendaylight.yangtools.yang.data.codec.xml.XMLStreamNormalizedNodeStreamWriter;
44 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
45 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
46 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
47 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
49 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
50 import org.w3c.dom.Document;
51 import org.w3c.dom.Element;
52 import org.w3c.dom.Node;
53
54 public final class CopyConfig extends AbstractEdit {
55     private static final String OPERATION_NAME = "copy-config";
56     private static final String SOURCE_KEY = "source";
57     private static final XMLOutputFactory XML_OUTPUT_FACTORY;
58
59     static {
60         XML_OUTPUT_FACTORY = XMLOutputFactory.newFactory();
61         XML_OUTPUT_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
62     }
63
64     // Top-level "data" node without child nodes
65     private static final ContainerNode EMPTY_ROOT_NODE = Builders.containerBuilder()
66         .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(SchemaContext.NAME)).build();
67
68     private final TransactionProvider transactionProvider;
69
70     public CopyConfig(final String netconfSessionIdForReporting, final CurrentSchemaContext schemaContext,
71                       final TransactionProvider transactionProvider) {
72         super(netconfSessionIdForReporting, schemaContext);
73         this.transactionProvider = transactionProvider;
74     }
75
76     @Override
77     protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement operationElement)
78         throws DocumentedException {
79         final XmlElement targetElement = extractTargetElement(operationElement, OPERATION_NAME);
80         final String target = targetElement.getName();
81         if (Datastore.running.toString().equals(target)) {
82             throw new DocumentedException("edit-config on running datastore is not supported",
83                 ErrorType.PROTOCOL, ErrorTag.OPERATION_NOT_SUPPORTED, ErrorSeverity.ERROR);
84         } else if (Datastore.candidate.toString().equals(target)) {
85             copyToCandidate(operationElement);
86         } else if (URL_KEY.equals(target)) {
87             copyToUrl(targetElement, operationElement);
88         } else {
89             throw new DocumentedException("Unsupported target: " + target,
90                 ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT, ErrorSeverity.ERROR);
91         }
92         return document.createElement(XmlNetconfConstants.OK);
93     }
94
95     private void copyToCandidate(final XmlElement operationElement)
96         throws DocumentedException {
97         final XmlElement source = getSourceElement(operationElement);
98         final List<XmlElement> configElements = getConfigElement(source).getChildElements();
99
100         // <copy-config>, unlike <edit-config>, always replaces entire configuration,
101         // so remove old configuration first:
102         final DOMDataTreeReadWriteTransaction rwTx = transactionProvider.getOrCreateTransaction();
103         rwTx.put(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty(), EMPTY_ROOT_NODE);
104
105         // Then create nodes present in the <config> element:
106         for (final XmlElement element : configElements) {
107             final String ns = element.getNamespace();
108             final DataSchemaNode schemaNode = getSchemaNodeFromNamespace(ns, element);
109             final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
110             parseIntoNormalizedNode(schemaNode, element, ImmutableNormalizedNodeStreamWriter.from(resultHolder));
111             final NormalizedNode data = resultHolder.getResult();
112             final YangInstanceIdentifier path = YangInstanceIdentifier.create(data.getIdentifier());
113             // Doing merge instead of put to support top-level list:
114             rwTx.merge(LogicalDatastoreType.CONFIGURATION, path, data);
115         }
116     }
117
118     private static XmlElement getSourceElement(final XmlElement parent) throws DocumentedException {
119         final Optional<XmlElement> sourceElement = parent.getOnlyChildElementOptionally(SOURCE_KEY);
120         if (sourceElement.isEmpty()) {
121             throw new DocumentedException("<source> element is missing",
122                 ErrorType.PROTOCOL, DocumentedException.ErrorTag.MISSING_ELEMENT, ErrorSeverity.ERROR);
123         }
124
125         return sourceElement.get();
126     }
127
128     private void copyToUrl(final XmlElement urlElement, final XmlElement operationElement) throws DocumentedException {
129         final String url = urlElement.getTextContent();
130         if (!url.startsWith("file:")) {
131             throw new DocumentedException("Unsupported <url> protocol: " + url,
132                 ErrorType.PROTOCOL, ErrorTag.OPERATION_NOT_SUPPORTED, ErrorSeverity.ERROR);
133         }
134
135         // Read data from datastore:
136         final XmlElement source = getSourceElement(operationElement).getOnlyChildElement();
137         final ContainerNode data = readData(source);
138
139         // Transform NN to XML:
140         final Document document = operationElement.getDomElement().getOwnerDocument();
141         final Node node = transformNormalizedNode(document, data);
142
143         // Save XML to file:
144         final String xml = XmlUtil.toString((Element) node);
145         try {
146             final Path file = Paths.get(new URI(url));
147             Files.write(file, xml.getBytes(StandardCharsets.UTF_8));
148         } catch (URISyntaxException | IllegalArgumentException e) {
149             throw new DocumentedException("Invalid URI: " + url, e,
150                 ErrorType.RPC, ErrorTag.INVALID_VALUE, ErrorSeverity.ERROR);
151         } catch (IOException e) {
152             throw new DocumentedException("Failed to write : " + url, e,
153                 ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, ErrorSeverity.ERROR);
154         }
155     }
156
157     private ContainerNode readData(final XmlElement source) throws DocumentedException {
158         final Datastore sourceDatastore = getDatastore(source);
159         final DOMDataTreeReadWriteTransaction rwTx = getTransaction(sourceDatastore);
160         final YangInstanceIdentifier dataRoot = YangInstanceIdentifier.empty();
161         try {
162             final Optional<NormalizedNode> normalizedNodeOptional = rwTx.read(
163                 LogicalDatastoreType.CONFIGURATION, dataRoot).get();
164             if (sourceDatastore == Datastore.running) {
165                 transactionProvider.abortRunningTransaction(rwTx);
166             }
167             return (ContainerNode) normalizedNodeOptional.get();
168         } catch (InterruptedException | ExecutionException e) {
169             throw new IllegalStateException("Unable to read data " + dataRoot, e);
170         }
171     }
172
173     private static Datastore getDatastore(final XmlElement source) throws DocumentedException {
174         try {
175             return Datastore.valueOf(source.getName());
176         } catch (IllegalArgumentException e) {
177             throw new DocumentedException("Unsupported source for <url> target", e,
178                 ErrorType.PROTOCOL, ErrorTag.OPERATION_NOT_SUPPORTED, ErrorSeverity.ERROR);
179         }
180     }
181
182     private DOMDataTreeReadWriteTransaction getTransaction(final Datastore datastore) throws DocumentedException {
183         if (datastore == Datastore.candidate) {
184             return transactionProvider.getOrCreateTransaction();
185         } else if (datastore == Datastore.running) {
186             return transactionProvider.createRunningTransaction();
187         }
188         throw new DocumentedException("Incorrect Datastore: ", ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT,
189             ErrorSeverity.ERROR);
190     }
191
192     private Node transformNormalizedNode(final Document document, final ContainerNode data) {
193         final Element configElement = document.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, CONFIG_KEY);
194         final DOMResult result = new DOMResult(configElement);
195         try {
196             final XMLStreamWriter xmlWriter = XML_OUTPUT_FACTORY.createXMLStreamWriter(result);
197             final NormalizedNodeStreamWriter nnStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter,
198                 schemaContext.getCurrentContext(), SchemaPath.ROOT);
199
200             final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(nnStreamWriter, true);
201             for (DataContainerChild child : data.body()) {
202                 nnWriter.write(child);
203             }
204             nnWriter.flush();
205             xmlWriter.flush();
206         } catch (XMLStreamException | IOException e) {
207             throw new RuntimeException(e);
208         }
209         return result.getNode();
210     }
211
212     @Override
213     protected String getOperationName() {
214         return OPERATION_NAME;
215     }
216 }