Clean-up mdsal-netconf-connector
[netconf.git] / netconf / mdsal-netconf-connector / src / main / java / org / opendaylight / netconf / mdsal / connector / ops / EditConfig.java
1 /*
2  * Copyright (c) 2015 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
9 package org.opendaylight.netconf.mdsal.connector.ops;
10
11 import com.google.common.base.Optional;
12 import java.net.URI;
13 import java.net.URISyntaxException;
14 import java.util.Collections;
15 import java.util.List;
16 import java.util.ListIterator;
17 import org.opendaylight.controller.config.util.xml.DocumentedException;
18 import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorSeverity;
19 import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorTag;
20 import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorType;
21 import org.opendaylight.controller.config.util.xml.XmlElement;
22 import org.opendaylight.controller.config.util.xml.XmlUtil;
23 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
24 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
25 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
26 import org.opendaylight.netconf.api.NetconfDocumentedException;
27 import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
28 import org.opendaylight.netconf.mdsal.connector.CurrentSchemaContext;
29 import org.opendaylight.netconf.mdsal.connector.TransactionProvider;
30 import org.opendaylight.netconf.mdsal.connector.ops.DataTreeChangeTracker.DataTreeChange;
31 import org.opendaylight.netconf.util.mapping.AbstractSingletonNetconfOperation;
32 import org.opendaylight.yangtools.yang.common.QName;
33 import org.opendaylight.yangtools.yang.data.api.ModifyAction;
34 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
35 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
36 import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.DomUtils;
37 import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory;
38 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
39 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.Module;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44 import org.w3c.dom.Document;
45 import org.w3c.dom.Element;
46 import org.w3c.dom.NodeList;
47
48 public class EditConfig extends AbstractSingletonNetconfOperation {
49
50     private static final Logger LOG = LoggerFactory.getLogger(EditConfig.class);
51
52     private static final String OPERATION_NAME = "edit-config";
53     private static final String CONFIG_KEY = "config";
54     private static final String TARGET_KEY = "target";
55     private static final String DEFAULT_OPERATION_KEY = "default-operation";
56     private final CurrentSchemaContext schemaContext;
57     private final TransactionProvider transactionProvider;
58
59     public EditConfig(final String netconfSessionIdForReporting, final CurrentSchemaContext schemaContext, final TransactionProvider transactionProvider) {
60         super(netconfSessionIdForReporting);
61         this.schemaContext = schemaContext;
62         this.transactionProvider = transactionProvider;
63     }
64
65     @Override
66     protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement operationElement) throws DocumentedException {
67         final Datastore targetDatastore = extractTargetParameter(operationElement);
68         if (targetDatastore == Datastore.running) {
69             throw new DocumentedException("edit-config on running datastore is not supported",
70                     ErrorType.protocol,
71                     ErrorTag.operation_not_supported,
72                     ErrorSeverity.error);
73         }
74
75         final ModifyAction defaultAction = getDefaultOperation(operationElement);
76
77         final XmlElement configElement = getElement(operationElement, CONFIG_KEY);
78
79         for (XmlElement element : configElement.getChildElements()) {
80             final String ns = element.getNamespace();
81             final DataSchemaNode schemaNode = getSchemaNodeFromNamespace(ns, element).get();
82
83             final DataTreeChangeTracker changeTracker = new DataTreeChangeTracker(defaultAction);
84             final DomToNormalizedNodeParserFactory.BuildingStrategyProvider editOperationStrategyProvider = new EditOperationStrategyProvider(changeTracker);
85
86             parseIntoNormalizedNode(schemaNode, element, editOperationStrategyProvider);
87             executeOperations(changeTracker);
88         }
89
90         return XmlUtil.createElement(document, XmlNetconfConstants.OK, Optional.absent());
91     }
92
93     private void executeOperations(final DataTreeChangeTracker changeTracker) throws DocumentedException {
94         final DOMDataReadWriteTransaction rwTx = transactionProvider.getOrCreateTransaction();
95         final List<DataTreeChange> aa = changeTracker.getDataTreeChanges();
96         final ListIterator<DataTreeChange> iterator = aa.listIterator(aa.size());
97
98         while (iterator.hasPrevious()) {
99             final DataTreeChange dtc = iterator.previous();
100             executeChange(rwTx, dtc);
101         }
102     }
103
104     private void executeChange(final DOMDataReadWriteTransaction rwtx, final DataTreeChange change) throws DocumentedException {
105         switch (change.getAction()) {
106         case NONE:
107             return;
108         case MERGE:
109             rwtx.merge(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath()), change.getChangeRoot());
110             break;
111         case CREATE:
112             try {
113                 final Optional<NormalizedNode<?, ?>> readResult = rwtx.read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath())).checkedGet();
114                 if (readResult.isPresent()) {
115                     throw new DocumentedException("Data already exists, cannot execute CREATE operation", ErrorType.protocol, ErrorTag.data_exists, ErrorSeverity.error);
116                 }
117                 rwtx.put(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath()), change.getChangeRoot());
118             } catch (ReadFailedException e) {
119                 LOG.warn("Read from datastore failed when trying to read data for create operation", change, e);
120             }
121             break;
122         case REPLACE:
123             rwtx.put(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath()), change.getChangeRoot());
124             break;
125         case DELETE:
126             try {
127                 final Optional<NormalizedNode<?, ?>> readResult = rwtx.read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath())).checkedGet();
128                 if (!readResult.isPresent()) {
129                     throw new DocumentedException("Data is missing, cannot execute DELETE operation", ErrorType.protocol, ErrorTag.data_missing, ErrorSeverity.error);
130                 }
131                 rwtx.delete(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath()));
132             } catch (ReadFailedException e) {
133                 LOG.warn("Read from datastore failed when trying to read data for delete operation", change, e);
134             }
135             break;
136         case REMOVE:
137             rwtx.delete(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath()));
138             break;
139         default:
140             LOG.warn("Unknown/not implemented operation, not executing");
141         }
142     }
143
144     private NormalizedNode parseIntoNormalizedNode(final DataSchemaNode schemaNode, final XmlElement element,
145                                                    final DomToNormalizedNodeParserFactory.BuildingStrategyProvider editOperationStrategyProvider) {
146
147
148         if (schemaNode instanceof ContainerSchemaNode) {
149             return DomToNormalizedNodeParserFactory
150                     .getInstance(DomUtils.defaultValueCodecProvider(), schemaContext.getCurrentContext(), editOperationStrategyProvider)
151                     .getContainerNodeParser()
152                     .parse(Collections.singletonList(element.getDomElement()), (ContainerSchemaNode) schemaNode);
153         } else if (schemaNode instanceof ListSchemaNode) {
154             return DomToNormalizedNodeParserFactory
155                     .getInstance(DomUtils.defaultValueCodecProvider(), schemaContext.getCurrentContext(), editOperationStrategyProvider)
156                     .getMapNodeParser()
157                     .parse(Collections.singletonList(element.getDomElement()), (ListSchemaNode) schemaNode);
158         } else {
159             //this should never happen since edit-config on any other node type should not be possible nor makes sense
160             LOG.debug("DataNode from module is not ContainerSchemaNode nor ListSchemaNode, aborting..");
161         }
162         throw new UnsupportedOperationException("implement exception if parse fails");
163     }
164
165     private Optional<DataSchemaNode> getSchemaNodeFromNamespace(final String namespace, final XmlElement element) throws DocumentedException{
166         Optional<DataSchemaNode> dataSchemaNode = Optional.absent();
167         try {
168             //returns module with newest revision since findModuleByNamespace returns a set of modules and we only need the newest one
169             final Module module = schemaContext.getCurrentContext().findModuleByNamespaceAndRevision(new URI(namespace), null);
170             if (module == null) {
171                 // no module is present with this namespace
172                 throw new NetconfDocumentedException("Unable to find module by namespace: " + namespace,
173                         ErrorType.application, ErrorTag.unknown_namespace, ErrorSeverity.error);
174             }
175             DataSchemaNode schemaNode =
176                     module.getDataChildByName(QName.create(module.getQNameModule(), element.getName()));
177             if (schemaNode != null) {
178                 dataSchemaNode = Optional.of(schemaNode);
179             } else {
180                 throw new DocumentedException("Unable to find node with namespace: " + namespace + "in module: " + module.toString(),
181                         ErrorType.application,
182                         ErrorTag.unknown_namespace,
183                         ErrorSeverity.error);
184             }
185         } catch (URISyntaxException e) {
186             LOG.debug("Unable to create URI for namespace : {}", namespace);
187         }
188
189         return dataSchemaNode;
190     }
191
192     private Datastore extractTargetParameter(final XmlElement operationElement) throws DocumentedException {
193         final NodeList elementsByTagName = operationElement.getDomElement().getElementsByTagName(TARGET_KEY);
194         // Direct lookup instead of using XmlElement class due to performance
195         if (elementsByTagName.getLength() == 0) {
196             throw new DocumentedException("Missing target element", ErrorType.rpc, ErrorTag.missing_attribute, ErrorSeverity.error);
197         } else if (elementsByTagName.getLength() > 1) {
198             throw new DocumentedException("Multiple target elements", ErrorType.rpc, ErrorTag.unknown_attribute, ErrorSeverity.error);
199         } else {
200             final XmlElement targetChildNode = XmlElement.fromDomElement((Element) elementsByTagName.item(0)).getOnlyChildElement();
201             return Datastore.valueOf(targetChildNode.getName());
202         }
203     }
204
205     private ModifyAction getDefaultOperation(final XmlElement operationElement) throws DocumentedException {
206         final NodeList elementsByTagName = operationElement.getDomElement().getElementsByTagName(DEFAULT_OPERATION_KEY);
207         if(elementsByTagName.getLength() == 0) {
208             return ModifyAction.MERGE;
209         } else if(elementsByTagName.getLength() > 1) {
210             throw new DocumentedException("Multiple " + DEFAULT_OPERATION_KEY + " elements",
211                     ErrorType.rpc, ErrorTag.unknown_attribute, ErrorSeverity.error);
212         } else {
213             return ModifyAction.fromXmlValue(elementsByTagName.item(0).getTextContent());
214         }
215
216     }
217
218     private XmlElement getElement(final XmlElement operationElement, String elementName) throws DocumentedException {
219         final Optional<XmlElement> childNode = operationElement.getOnlyChildElementOptionally(elementName);
220         if (!childNode.isPresent()) {
221             throw new DocumentedException(elementName + " element is missing",
222                     ErrorType.protocol,
223                     ErrorTag.missing_element,
224                     ErrorSeverity.error);
225         }
226
227         return childNode.get();
228     }
229
230     @Override
231     protected String getOperationName() {
232         return OPERATION_NAME;
233     }
234
235 }