package org.opendaylight.controller.netconf.mdsal.connector.ops;
import com.google.common.base.Optional;
-import com.google.common.util.concurrent.CheckedFuture;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+import org.opendaylight.controller.config.util.xml.DocumentedException;
+import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorSeverity;
+import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorTag;
+import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorType;
+import org.opendaylight.controller.config.util.xml.XmlElement;
+import org.opendaylight.controller.config.util.xml.XmlUtil;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
-import org.opendaylight.controller.netconf.api.NetconfDocumentedException;
-import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorSeverity;
-import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorTag;
-import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorType;
import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants;
import org.opendaylight.controller.netconf.mdsal.connector.CurrentSchemaContext;
import org.opendaylight.controller.netconf.mdsal.connector.TransactionProvider;
-import org.opendaylight.controller.netconf.util.exception.MissingNameSpaceException;
-import org.opendaylight.controller.netconf.util.exception.UnexpectedNamespaceException;
+import org.opendaylight.controller.netconf.mdsal.connector.ops.DataTreeChangeTracker.DataTreeChange;
import org.opendaylight.controller.netconf.util.mapping.AbstractSingletonNetconfOperation;
-import org.opendaylight.controller.netconf.util.xml.XmlElement;
-import org.opendaylight.controller.netconf.util.xml.XmlUtil;
import org.opendaylight.yangtools.yang.data.api.ModifyAction;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
-import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.DomUtils;
import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory;
-import org.opendaylight.yangtools.yang.data.operations.DataModificationException;
-import org.opendaylight.yangtools.yang.data.operations.DataModificationException.DataExistsException;
-import org.opendaylight.yangtools.yang.data.operations.DataModificationException.DataMissingException;
-import org.opendaylight.yangtools.yang.data.operations.DataOperations;
import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
public class EditConfig extends AbstractSingletonNetconfOperation {
private static final String CONFIG_KEY = "config";
private static final String TARGET_KEY = "target";
private static final String DEFAULT_OPERATION_KEY = "default-operation";
-
-
private final CurrentSchemaContext schemaContext;
private final TransactionProvider transactionProvider;
}
@Override
- protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement operationElement) throws NetconfDocumentedException {
+ protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement operationElement) throws DocumentedException {
final Datastore targetDatastore = extractTargetParameter(operationElement);
if (targetDatastore == Datastore.running) {
- throw new NetconfDocumentedException("edit-config on running datastore is not supported",
+ throw new DocumentedException("edit-config on running datastore is not supported",
ErrorType.protocol,
ErrorTag.operation_not_supported,
ErrorSeverity.error);
for (XmlElement element : configElement.getChildElements()) {
final String ns = element.getNamespace();
final DataSchemaNode schemaNode = getSchemaNodeFromNamespace(ns, element).get();
- YangInstanceIdentifier ident = YangInstanceIdentifier.of(schemaNode.getQName());
- final NormalizedNode storedNode = readStoredNode(LogicalDatastoreType.CONFIGURATION, ident);
- try {
- final Optional<NormalizedNode<?, ?>> newNode = modifyNode(schemaNode, element, storedNode, defaultAction);
- final DOMDataReadWriteTransaction rwTx = transactionProvider.getOrCreateTransaction();
- if (newNode.isPresent()) {
- rwTx.put(LogicalDatastoreType.CONFIGURATION, ident, newNode.get());
- } else {
- rwTx.delete(LogicalDatastoreType.CONFIGURATION, ident);
- }
- } catch (final DataExistsException e) {
- throw new NetconfDocumentedException(e.getMessage(), e, ErrorType.protocol, ErrorTag.data_exists, ErrorSeverity.error);
- } catch (final DataMissingException e) {
- throw new NetconfDocumentedException(e.getMessage(), e, ErrorType.protocol, ErrorTag.data_missing, ErrorSeverity.error);
- } catch (final DataModificationException e) {
- throw new NetconfDocumentedException(e.getMessage(), e, ErrorType.protocol, ErrorTag.operation_failed, ErrorSeverity.error);
- }
+ final DataTreeChangeTracker changeTracker = new DataTreeChangeTracker(defaultAction);
+ final DomToNormalizedNodeParserFactory.BuildingStrategyProvider editOperationStrategyProvider = new EditOperationStrategyProvider(changeTracker);
+
+ parseIntoNormalizedNode(schemaNode, element, editOperationStrategyProvider);
+ executeOperations(changeTracker);
}
return XmlUtil.createElement(document, XmlNetconfConstants.OK, Optional.<String>absent());
}
- private NormalizedNode readStoredNode(final LogicalDatastoreType logicalDatastoreType, final YangInstanceIdentifier path) throws NetconfDocumentedException{
- final DOMDataReadWriteTransaction rwTx = transactionProvider.getOrCreateTransaction();
- final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> readFuture = rwTx.read(logicalDatastoreType, path);
- try {
- if (readFuture.checkedGet().isPresent()) {
- final NormalizedNode node = readFuture.checkedGet().get();
- return node;
- } else {
- LOG.debug("Unable to read node : {} from {} datastore", path, logicalDatastoreType);
+ private void executeOperations(final DataTreeChangeTracker changeTracker) throws DocumentedException {
+ final DOMDataReadWriteTransaction rwTx = transactionProvider.getOrCreateTransaction();
+ final List<DataTreeChange> aa = changeTracker.getDataTreeChanges();
+ final ListIterator<DataTreeChange> iterator = aa.listIterator(aa.size());
+
+ while (iterator.hasPrevious()) {
+ final DataTreeChange dtc = iterator.previous();
+ executeChange(rwTx, dtc);
+ }
+ }
+
+ private void executeChange(final DOMDataReadWriteTransaction rwtx, final DataTreeChange change) throws DocumentedException {
+ switch (change.getAction()) {
+ case NONE:
+ return;
+ case MERGE:
+ rwtx.merge(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath()), change.getChangeRoot());
+ break;
+ case CREATE:
+ try {
+ final Optional<NormalizedNode<?, ?>> readResult = rwtx.read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath())).checkedGet();
+ if (readResult.isPresent()) {
+ throw new DocumentedException("Data already exists, cannot execute CREATE operation", ErrorType.protocol, ErrorTag.data_exists, ErrorSeverity.error);
+ }
+ rwtx.put(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath()), change.getChangeRoot());
+ } catch (ReadFailedException e) {
+ LOG.warn("Read from datastore failed when trying to read data for create operation", change, e);
+ }
+ break;
+ case REPLACE:
+ rwtx.put(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath()), change.getChangeRoot());
+ break;
+ case DELETE:
+ try {
+ final Optional<NormalizedNode<?, ?>> readResult = rwtx.read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath())).checkedGet();
+ if (!readResult.isPresent()) {
+ throw new DocumentedException("Data is missing, cannot execute DELETE operation", ErrorType.protocol, ErrorTag.data_missing, ErrorSeverity.error);
+ }
+ rwtx.delete(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath()));
+ } catch (ReadFailedException e) {
+ LOG.warn("Read from datastore failed when trying to read data for delete operation", change, e);
}
- } catch (final ReadFailedException e) {
- //only log this since DataOperations.modify will handle throwing an exception or writing the node.
- LOG.debug("Unable to read stored data: {}", path, e);
+ break;
+ case REMOVE:
+ rwtx.delete(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath()));
+ break;
+ default:
+ LOG.warn("Unknown/not implemented operation, not executing");
}
+ }
+
+ private NormalizedNode parseIntoNormalizedNode(final DataSchemaNode schemaNode, final XmlElement element,
+ final DomToNormalizedNodeParserFactory.BuildingStrategyProvider editOperationStrategyProvider) {
+
- //we can return null here since DataOperations.modify handles null as input
- return null;
+ if (schemaNode instanceof ContainerSchemaNode) {
+ return DomToNormalizedNodeParserFactory
+ .getInstance(DomUtils.defaultValueCodecProvider(), schemaContext.getCurrentContext(), editOperationStrategyProvider)
+ .getContainerNodeParser()
+ .parse(Collections.singletonList(element.getDomElement()), (ContainerSchemaNode) schemaNode);
+ } else if (schemaNode instanceof ListSchemaNode) {
+ return DomToNormalizedNodeParserFactory
+ .getInstance(DomUtils.defaultValueCodecProvider(), schemaContext.getCurrentContext(), editOperationStrategyProvider)
+ .getMapNodeParser()
+ .parse(Collections.singletonList(element.getDomElement()), (ListSchemaNode) schemaNode);
+ } else {
+ //this should never happen since edit-config on any other node type should not be possible nor makes sense
+ LOG.debug("DataNode from module is not ContainerSchemaNode nor ListSchemaNode, aborting..");
+ }
+ throw new UnsupportedOperationException("implement exception if parse fails");
}
- private Optional<DataSchemaNode> getSchemaNodeFromNamespace(final String namespace, final XmlElement element) throws NetconfDocumentedException{
+ private Optional<DataSchemaNode> getSchemaNodeFromNamespace(final String namespace, final XmlElement element) throws DocumentedException{
Optional<DataSchemaNode> dataSchemaNode = Optional.absent();
try {
//returns module with newest revision since findModuleByNamespace returns a set of modules and we only need the newest one
if (schemaNode != null) {
dataSchemaNode = Optional.of(module.getDataChildByName(element.getName()));
} else {
- throw new NetconfDocumentedException("Unable to find node with namespace: " + namespace + "in module: " + module.toString(),
+ throw new DocumentedException("Unable to find node with namespace: " + namespace + "in module: " + module.toString(),
ErrorType.application,
ErrorTag.unknown_namespace,
ErrorSeverity.error);
}
-
} catch (URISyntaxException e) {
LOG.debug("Unable to create URI for namespace : {}", namespace);
}
return dataSchemaNode;
}
- private Optional<NormalizedNode<?, ?>> modifyNode(final DataSchemaNode schemaNode, final XmlElement element, final NormalizedNode storedNode, final ModifyAction defaultAction) throws DataModificationException{
- if (schemaNode instanceof ContainerSchemaNode) {
- final ContainerNode modifiedNode =
- DomToNormalizedNodeParserFactory
- .getInstance(DomUtils.defaultValueCodecProvider())
- .getContainerNodeParser()
- .parse(Collections.singletonList(element.getDomElement()), (ContainerSchemaNode) schemaNode);
-
- final Optional<ContainerNode> oNode = DataOperations.modify((ContainerSchemaNode) schemaNode, (ContainerNode) storedNode, modifiedNode, defaultAction);
- if (!oNode.isPresent()) {
- return Optional.absent();
- }
-
- final NormalizedNode<?,?> node = oNode.get();
- return Optional.<NormalizedNode<?,?>>of(node);
- } else if (schemaNode instanceof ListSchemaNode) {
- final MapNode modifiedNode =
- DomToNormalizedNodeParserFactory
- .getInstance(DomUtils.defaultValueCodecProvider())
- .getMapNodeParser()
- .parse(Collections.singletonList(element.getDomElement()), (ListSchemaNode) schemaNode);
-
- final Optional<MapNode> oNode = DataOperations.modify((ListSchemaNode) schemaNode, (MapNode) storedNode, modifiedNode, defaultAction);
- if (!oNode.isPresent()) {
- return Optional.absent();
- }
-
- final NormalizedNode<?, ?> node = oNode.get();
- return Optional.<NormalizedNode<?,?>>of(node);
+ private Datastore extractTargetParameter(final XmlElement operationElement) throws DocumentedException {
+ final NodeList elementsByTagName = operationElement.getDomElement().getElementsByTagName(TARGET_KEY);
+ // Direct lookup instead of using XmlElement class due to performance
+ if (elementsByTagName.getLength() == 0) {
+ throw new DocumentedException("Missing target element", ErrorType.rpc, ErrorTag.missing_attribute, ErrorSeverity.error);
+ } else if (elementsByTagName.getLength() > 1) {
+ throw new DocumentedException("Multiple target elements", ErrorType.rpc, ErrorTag.unknown_attribute, ErrorSeverity.error);
} else {
- //this should never happen since edit-config on any other node type should not be possible nor makes sense
- LOG.debug("DataNode from module is not ContainerSchemaNode nor ListSchemaNode, aborting..");
- return Optional.absent();
+ final XmlElement targetChildNode = XmlElement.fromDomElement((Element) elementsByTagName.item(0)).getOnlyChildElement();
+ return Datastore.valueOf(targetChildNode.getName());
}
-
}
- private Datastore extractTargetParameter(final XmlElement operationElement) throws NetconfDocumentedException {
- final XmlElement targetChildNode;
- try {
- final XmlElement targetElement = operationElement.getOnlyChildElementWithSameNamespace(TARGET_KEY);
- targetChildNode = targetElement.getOnlyChildElementWithSameNamespace();
- } catch (final MissingNameSpaceException | UnexpectedNamespaceException e) {
- LOG.trace("Can't get only child element with same namespace", e);
- throw NetconfDocumentedException.wrap(e);
+ private ModifyAction getDefaultOperation(final XmlElement operationElement) throws DocumentedException {
+ final NodeList elementsByTagName = operationElement.getDomElement().getElementsByTagName(DEFAULT_OPERATION_KEY);
+ if(elementsByTagName.getLength() == 0) {
+ return ModifyAction.MERGE;
+ } else if(elementsByTagName.getLength() > 1) {
+ throw new DocumentedException("Multiple " + DEFAULT_OPERATION_KEY + " elements",
+ ErrorType.rpc, ErrorTag.unknown_attribute, ErrorSeverity.error);
+ } else {
+ return ModifyAction.fromXmlValue(elementsByTagName.item(0).getTextContent());
}
- return Datastore.valueOf(targetChildNode.getName());
- }
-
- private ModifyAction getDefaultOperation(final XmlElement operationElement) throws NetconfDocumentedException{
- try {
- return ModifyAction.fromXmlValue(getElement(operationElement, DEFAULT_OPERATION_KEY).getTextContent());
- } catch (NetconfDocumentedException e) {
- if (e.getErrorType() == ErrorType.protocol
- && e.getErrorSeverity() == ErrorSeverity.error
- && e.getErrorTag() == ErrorTag.missing_element) {
- return ModifyAction.MERGE;
- }
- else {
- throw e;
- }
- }
}
- private XmlElement getElement(final XmlElement operationElement, String elementName) throws NetconfDocumentedException {
+ private XmlElement getElement(final XmlElement operationElement, String elementName) throws DocumentedException {
final Optional<XmlElement> childNode = operationElement.getOnlyChildElementOptionally(elementName);
if (!childNode.isPresent()) {
- throw new NetconfDocumentedException(elementName + " element is missing",
+ throw new DocumentedException(elementName + " element is missing",
ErrorType.protocol,
ErrorTag.missing_element,
ErrorSeverity.error);