summaryrefslogtreecommitdiffstats
path: root/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl
diff options
context:
space:
mode:
Diffstat (limited to 'netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl')
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BatchedExistenceCheck.java90
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/Bierman02RestConfWiring.java75
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BrokerFacade.java1277
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/ControllerContext.java1006
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/DataNormalizationException.java16
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/DataNormalizationOperation.java526
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/DataNormalizer.java81
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/JSONRestconfServiceImpl.java291
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/NormalizedDataPrunner.java143
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PutResult.java51
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/QueryParametersParser.java69
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestCodec.java380
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfImpl.java1546
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfProviderImpl.java111
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/StatisticsRestconfServiceWrapper.java301
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Config.java81
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Delete.java69
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Get.java68
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Operational.java38
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Post.java68
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Put.java67
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/RestConnectorRuntimeMXBean.java16
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Rpcs.java67
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/package-info.java13
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/package-info.java8
25 files changed, 6458 insertions, 0 deletions
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BatchedExistenceCheck.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BatchedExistenceCheck.java
new file mode 100644
index 0000000..4efc6a8
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BatchedExistenceCheck.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.Collection;
+import java.util.Map.Entry;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.common.api.ReadFailedException;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeReadOperations;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+final class BatchedExistenceCheck {
+ private static final AtomicIntegerFieldUpdater<BatchedExistenceCheck> UPDATER =
+ AtomicIntegerFieldUpdater.newUpdater(BatchedExistenceCheck.class, "outstanding");
+
+ private final SettableFuture<Entry<YangInstanceIdentifier, ReadFailedException>> future = SettableFuture.create();
+
+ @SuppressWarnings("unused")
+ private volatile int outstanding;
+
+ private BatchedExistenceCheck(final int total) {
+ this.outstanding = total;
+ }
+
+ static BatchedExistenceCheck start(final DOMDataTreeReadOperations readTx,
+ final LogicalDatastoreType datastore, final YangInstanceIdentifier parentPath,
+ final Collection<? extends NormalizedNode> children) {
+ final BatchedExistenceCheck ret = new BatchedExistenceCheck(children.size());
+ for (NormalizedNode child : children) {
+ final YangInstanceIdentifier path = parentPath.node(child.getIdentifier());
+ readTx.exists(datastore, path).addCallback(new FutureCallback<Boolean>() {
+ @Override
+ public void onSuccess(final Boolean result) {
+ ret.complete(path, result);
+ }
+
+ @Override
+ @SuppressFBWarnings("BC_UNCONFIRMED_CAST_OF_RETURN_VALUE")
+ public void onFailure(final Throwable throwable) {
+ final Exception e;
+ if (throwable instanceof Exception) {
+ e = (Exception) throwable;
+ } else {
+ e = new ExecutionException(throwable);
+ }
+
+ ret.complete(path, ReadFailedException.MAPPER.apply(e));
+ }
+ }, MoreExecutors.directExecutor());
+ }
+
+ return ret;
+ }
+
+ Entry<YangInstanceIdentifier, ReadFailedException> getFailure() throws InterruptedException {
+ try {
+ return future.get();
+ } catch (ExecutionException e) {
+ // This should never happen
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private void complete(final YangInstanceIdentifier childPath, final boolean present) {
+ final int count = UPDATER.decrementAndGet(this);
+ if (present) {
+ future.set(new SimpleImmutableEntry<>(childPath, null));
+ } else if (count == 0) {
+ future.set(null);
+ }
+ }
+
+ private void complete(final YangInstanceIdentifier childPath, final ReadFailedException cause) {
+ UPDATER.decrementAndGet(this);
+ future.set(new SimpleImmutableEntry<>(childPath, cause));
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/Bierman02RestConfWiring.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/Bierman02RestConfWiring.java
new file mode 100644
index 0000000..18d19e4
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/Bierman02RestConfWiring.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2019 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.inject.Inject;
+import javax.servlet.ServletException;
+import org.opendaylight.mdsal.dom.api.DOMDataBroker;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
+import org.opendaylight.mdsal.dom.api.DOMNotificationService;
+import org.opendaylight.mdsal.dom.api.DOMRpcService;
+import org.opendaylight.mdsal.dom.api.DOMSchemaService;
+import org.opendaylight.netconf.sal.rest.impl.RestconfApplication;
+import org.opendaylight.netconf.sal.restconf.api.RestConfConfig;
+import org.opendaylight.netconf.sal.restconf.web.WebInitializer;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddressBuilder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.PortNumber;
+import org.opendaylight.yangtools.yang.common.Uint16;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Standalone wiring for RESTCONF.
+ *
+ * <p>ACK: Some lines here were originally inspired by the RestConfWiring class in
+ * opendaylight-simple which in turn was inspired by the CommunityRestConf class
+ * from lighty.io. The differences include (1) that this class is "pure Java"
+ * without depending on any binding framework utility classes; (2) we do not mix
+ * bierman02 and rfc8040 for proper modularity; (3) we simply use {@literal @}Inject
+ * instead of manual object wiring, where possible.
+ *
+ * @author Michael Vorburger.ch (see ACK note for history)
+ */
+@SuppressWarnings("deprecation")
+// NOT @Singleton, to avoid that the blueprint-maven-plugin generates <bean>, which we don't want for this
+public class Bierman02RestConfWiring {
+
+ private static final Logger LOG = LoggerFactory.getLogger(Bierman02RestConfWiring.class);
+
+ private final RestconfProviderImpl webSocketServer;
+
+ @Inject
+ // The point of all the arguments here is simply to make your chosen Dependency Injection (DI) framework init. them
+ public Bierman02RestConfWiring(final RestConfConfig config,
+ final DOMSchemaService domSchemaService, final DOMMountPointService domMountPointService,
+ final DOMRpcService domRpcService, final DOMDataBroker domDataBroker,
+ final DOMNotificationService domNotificationService, final ControllerContext controllerContext,
+ final RestconfApplication application, final BrokerFacade broker, final RestconfImpl restconf,
+ final StatisticsRestconfServiceWrapper stats, final JSONRestconfServiceImpl jsonRestconfServiceImpl,
+ final WebInitializer webInitializer) {
+
+ // WebSocket
+ LOG.info("webSocketAddress = {}, webSocketPort = {}", config.webSocketAddress(), config.webSocketPort());
+ IpAddress wsIpAddress = IpAddressBuilder.getDefaultInstance(config.webSocketAddress().getHostAddress());
+ this.webSocketServer = new RestconfProviderImpl(stats, wsIpAddress,
+ new PortNumber(Uint16.valueOf(config.webSocketPort())));
+ }
+
+ @PostConstruct
+ public void start() throws ServletException {
+ this.webSocketServer.start();
+ }
+
+ @PreDestroy
+ public void stop() {
+ this.webSocketServer.close();
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BrokerFacade.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BrokerFacade.java
new file mode 100644
index 0000000..ca867cb
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BrokerFacade.java
@@ -0,0 +1,1277 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+import static org.opendaylight.mdsal.common.api.LogicalDatastoreType.CONFIGURATION;
+import static org.opendaylight.mdsal.common.api.LogicalDatastoreType.OPERATIONAL;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.FluentFuture;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.io.Closeable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import javax.annotation.PreDestroy;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.ws.rs.core.Response.Status;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.common.api.ReadFailedException;
+import org.opendaylight.mdsal.dom.api.DOMDataBroker;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeService;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeReadOperations;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
+import org.opendaylight.mdsal.dom.api.DOMMountPoint;
+import org.opendaylight.mdsal.dom.api.DOMNotificationListener;
+import org.opendaylight.mdsal.dom.api.DOMNotificationService;
+import org.opendaylight.mdsal.dom.api.DOMRpcResult;
+import org.opendaylight.mdsal.dom.api.DOMRpcService;
+import org.opendaylight.mdsal.dom.api.DOMSchemaService;
+import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter;
+import org.opendaylight.netconf.sal.streams.listeners.NotificationListenerAdapter;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchEditOperation;
+import org.opendaylight.restconf.common.patch.PatchEntity;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+import org.opendaylight.restconf.common.patch.PatchStatusEntity;
+import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.CreateDataChangeEventSubscriptionInput1.Scope;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.SystemMapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UserLeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UserMapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.CollectionNodeBuilder;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.NormalizedNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.SchemaAwareBuilders;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Singleton
+public class BrokerFacade implements Closeable {
+ private static final Logger LOG = LoggerFactory.getLogger(BrokerFacade.class);
+
+ private final ThreadLocal<Boolean> isMounted = new ThreadLocal<>();
+ private final DOMNotificationService domNotification;
+ private final ControllerContext controllerContext;
+ private final DOMDataBroker domDataBroker;
+
+ private volatile DOMRpcService rpcService;
+
+ @Inject
+ public BrokerFacade(final DOMRpcService rpcService, final DOMDataBroker domDataBroker,
+ final DOMNotificationService domNotification, final ControllerContext controllerContext) {
+ this.rpcService = requireNonNull(rpcService);
+ this.domDataBroker = requireNonNull(domDataBroker);
+ this.domNotification = requireNonNull(domNotification);
+ this.controllerContext = requireNonNull(controllerContext);
+ }
+
+ /**
+ * Factory method.
+ *
+ * @deprecated Just use
+ * {@link #BrokerFacade(DOMRpcService, DOMDataBroker, DOMNotificationService, ControllerContext)}
+ * constructor instead.
+ */
+ @Deprecated
+ public static BrokerFacade newInstance(final DOMRpcService rpcService, final DOMDataBroker domDataBroker,
+ final DOMNotificationService domNotification, final ControllerContext controllerContext) {
+ return new BrokerFacade(rpcService, domDataBroker, domNotification, controllerContext);
+ }
+
+ @Override
+ @PreDestroy
+ public void close() {
+ }
+
+ /**
+ * Read config data by path.
+ *
+ * @param path
+ * path of data
+ * @return read date
+ */
+ public NormalizedNode readConfigurationData(final YangInstanceIdentifier path) {
+ return readConfigurationData(path, null);
+ }
+
+ /**
+ * Read config data by path.
+ *
+ * @param path
+ * path of data
+ * @param withDefa
+ * value of with-defaults parameter
+ * @return read date
+ */
+ public NormalizedNode readConfigurationData(final YangInstanceIdentifier path, final String withDefa) {
+ try (DOMDataTreeReadTransaction tx = domDataBroker.newReadOnlyTransaction()) {
+ return readDataViaTransaction(tx, CONFIGURATION, path, withDefa);
+ }
+ }
+
+ /**
+ * Read config data from mount point by path.
+ *
+ * @param mountPoint
+ * mount point for reading data
+ * @param path
+ * path of data
+ * @return read data
+ */
+ public NormalizedNode readConfigurationData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
+ return readConfigurationData(mountPoint, path, null);
+ }
+
+ /**
+ * Read config data from mount point by path.
+ *
+ * @param mountPoint
+ * mount point for reading data
+ * @param path
+ * path of data
+ * @param withDefa
+ * value of with-defaults parameter
+ * @return read data
+ */
+ public NormalizedNode readConfigurationData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path,
+ final String withDefa) {
+ final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
+ if (domDataBrokerService.isPresent()) {
+ try (DOMDataTreeReadTransaction tx = domDataBrokerService.get().newReadOnlyTransaction()) {
+ return readDataViaTransaction(tx, CONFIGURATION, path, withDefa);
+ }
+ }
+ throw dataBrokerUnavailable(path);
+ }
+
+ /**
+ * Read operational data by path.
+ *
+ * @param path
+ * path of data
+ * @return read data
+ */
+ public NormalizedNode readOperationalData(final YangInstanceIdentifier path) {
+ try (DOMDataTreeReadTransaction tx = domDataBroker.newReadOnlyTransaction()) {
+ return readDataViaTransaction(tx, OPERATIONAL, path);
+ }
+ }
+
+ /**
+ * Read operational data from mount point by path.
+ *
+ * @param mountPoint
+ * mount point for reading data
+ * @param path
+ * path of data
+ * @return read data
+ */
+ public NormalizedNode readOperationalData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
+ final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
+ if (domDataBrokerService.isPresent()) {
+ try (DOMDataTreeReadTransaction tx = domDataBrokerService.get().newReadOnlyTransaction()) {
+ return readDataViaTransaction(tx, OPERATIONAL, path);
+ }
+ }
+ throw dataBrokerUnavailable(path);
+ }
+
+ /**
+ * <b>PUT configuration data</b>
+ *
+ * <p>
+ * Prepare result(status) for PUT operation and PUT data via transaction.
+ * Return wrapped status and future from PUT.
+ *
+ * @param globalSchema
+ * used by merge parents (if contains list)
+ * @param path
+ * path of node
+ * @param payload
+ * input data
+ * @param point
+ * point
+ * @param insert
+ * insert
+ * @return wrapper of status and future of PUT
+ */
+ public PutResult commitConfigurationDataPut(final EffectiveModelContext globalSchema,
+ final YangInstanceIdentifier path, final NormalizedNode payload, final String insert, final String point) {
+ requireNonNull(globalSchema);
+ requireNonNull(path);
+ requireNonNull(payload);
+
+ isMounted.set(false);
+ final DOMDataTreeReadWriteTransaction newReadWriteTransaction = domDataBroker.newReadWriteTransaction();
+ final Status status = readDataViaTransaction(newReadWriteTransaction, CONFIGURATION, path) != null ? Status.OK
+ : Status.CREATED;
+ final FluentFuture<? extends CommitInfo> future = putDataViaTransaction(
+ newReadWriteTransaction, CONFIGURATION, path, payload, globalSchema, insert, point);
+ isMounted.remove();
+ return new PutResult(status, future);
+ }
+
+ /**
+ * <b>PUT configuration data (Mount point)</b>
+ *
+ * <p>
+ * Prepare result(status) for PUT operation and PUT data via transaction.
+ * Return wrapped status and future from PUT.
+ *
+ * @param mountPoint
+ * mount point for getting transaction for operation and schema
+ * context for merging parents(if contains list)
+ * @param path
+ * path of node
+ * @param payload
+ * input data
+ * @param point
+ * point
+ * @param insert
+ * insert
+ * @return wrapper of status and future of PUT
+ */
+ public PutResult commitMountPointDataPut(final DOMMountPoint mountPoint, final YangInstanceIdentifier path,
+ final NormalizedNode payload, final String insert, final String point) {
+ requireNonNull(mountPoint);
+ requireNonNull(path);
+ requireNonNull(payload);
+
+ isMounted.set(true);
+ final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
+ if (domDataBrokerService.isPresent()) {
+ final DOMDataTreeReadWriteTransaction newReadWriteTransaction =
+ domDataBrokerService.get().newReadWriteTransaction();
+ final Status status = readDataViaTransaction(newReadWriteTransaction, CONFIGURATION, path) != null
+ ? Status.OK : Status.CREATED;
+ final FluentFuture<? extends CommitInfo> future = putDataViaTransaction(
+ newReadWriteTransaction, CONFIGURATION, path, payload, modelContext(mountPoint), insert, point);
+ isMounted.remove();
+ return new PutResult(status, future);
+ }
+ isMounted.remove();
+ throw dataBrokerUnavailable(path);
+ }
+
+ public PatchStatusContext patchConfigurationDataWithinTransaction(final PatchContext patchContext)
+ throws Exception {
+ final DOMMountPoint mountPoint = patchContext.getInstanceIdentifierContext().getMountPoint();
+
+ // get new transaction and schema context on server or on mounted device
+ final EffectiveModelContext schemaContext;
+ final DOMDataTreeReadWriteTransaction patchTransaction;
+ if (mountPoint == null) {
+ schemaContext = patchContext.getInstanceIdentifierContext().getSchemaContext();
+ patchTransaction = domDataBroker.newReadWriteTransaction();
+ } else {
+ schemaContext = modelContext(mountPoint);
+
+ final Optional<DOMDataBroker> optional = mountPoint.getService(DOMDataBroker.class);
+
+ if (optional.isPresent()) {
+ patchTransaction = optional.get().newReadWriteTransaction();
+ } else {
+ // if mount point does not have broker it is not possible to continue and global error is reported
+ LOG.error("Http Patch {} has failed - device {} does not support broker service",
+ patchContext.getPatchId(), mountPoint.getIdentifier());
+ return new PatchStatusContext(
+ patchContext.getPatchId(),
+ null,
+ false,
+ ImmutableList.of(new RestconfError(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED,
+ "DOM data broker service isn't available for mount point " + mountPoint.getIdentifier()))
+ );
+ }
+ }
+
+ final List<PatchStatusEntity> editCollection = new ArrayList<>();
+ List<RestconfError> editErrors;
+ boolean withoutError = true;
+
+ for (final PatchEntity patchEntity : patchContext.getData()) {
+ final PatchEditOperation operation = patchEntity.getOperation();
+ switch (operation) {
+ case CREATE:
+ if (withoutError) {
+ try {
+ postDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity.getTargetNode(),
+ patchEntity.getNode(), schemaContext);
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
+ } catch (final RestconfDocumentedException e) {
+ LOG.error("Error call http Patch operation {} on target {}",
+ operation,
+ patchEntity.getTargetNode().toString());
+
+ editErrors = new ArrayList<>();
+ editErrors.addAll(e.getErrors());
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), false, editErrors));
+ withoutError = false;
+ }
+ }
+ break;
+ case REPLACE:
+ if (withoutError) {
+ try {
+ putDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
+ .getTargetNode(), patchEntity.getNode(), schemaContext);
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
+ } catch (final RestconfDocumentedException e) {
+ LOG.error("Error call http Patch operation {} on target {}",
+ operation,
+ patchEntity.getTargetNode().toString());
+
+ editErrors = new ArrayList<>();
+ editErrors.addAll(e.getErrors());
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), false, editErrors));
+ withoutError = false;
+ }
+ }
+ break;
+ case DELETE:
+ case REMOVE:
+ if (withoutError) {
+ try {
+ deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
+ .getTargetNode());
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
+ } catch (final RestconfDocumentedException e) {
+ LOG.error("Error call http Patch operation {} on target {}",
+ operation,
+ patchEntity.getTargetNode().toString());
+
+ editErrors = new ArrayList<>();
+ editErrors.addAll(e.getErrors());
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), false, editErrors));
+ withoutError = false;
+ }
+ }
+ break;
+ case MERGE:
+ if (withoutError) {
+ try {
+ mergeDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity.getTargetNode(),
+ patchEntity.getNode(), schemaContext);
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
+ } catch (final RestconfDocumentedException e) {
+ LOG.error("Error call http Patch operation {} on target {}",
+ operation,
+ patchEntity.getTargetNode().toString());
+
+ editErrors = new ArrayList<>();
+ editErrors.addAll(e.getErrors());
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), false, editErrors));
+ withoutError = false;
+ }
+ }
+ break;
+ default:
+ LOG.error("Unsupported http Patch operation {} on target {}",
+ operation,
+ patchEntity.getTargetNode().toString());
+ break;
+ }
+ }
+
+ // if errors then cancel transaction and return error status
+ if (!withoutError) {
+ patchTransaction.cancel();
+ return new PatchStatusContext(patchContext.getPatchId(), ImmutableList.copyOf(editCollection), false, null);
+ }
+
+ // if no errors commit transaction
+ final CountDownLatch waiter = new CountDownLatch(1);
+ final FluentFuture<? extends CommitInfo> future = patchTransaction.commit();
+ final PatchStatusContextHelper status = new PatchStatusContextHelper();
+
+ future.addCallback(new FutureCallback<CommitInfo>() {
+ @Override
+ public void onSuccess(final CommitInfo result) {
+ status.setStatus(new PatchStatusContext(patchContext.getPatchId(), ImmutableList.copyOf(editCollection),
+ true, null));
+ waiter.countDown();
+ }
+
+ @Override
+ public void onFailure(final Throwable throwable) {
+ // if commit failed it is global error
+ LOG.error("Http Patch {} transaction commit has failed", patchContext.getPatchId());
+ status.setStatus(new PatchStatusContext(patchContext.getPatchId(), ImmutableList.copyOf(editCollection),
+ false, ImmutableList.of(
+ new RestconfError(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, throwable.getMessage()))));
+ waiter.countDown();
+ }
+ }, MoreExecutors.directExecutor());
+
+ waiter.await();
+ return status.getStatus();
+ }
+
+ // POST configuration
+ public FluentFuture<? extends CommitInfo> commitConfigurationDataPost(
+ final EffectiveModelContext globalSchema, final YangInstanceIdentifier path,
+ final NormalizedNode payload, final String insert, final String point) {
+ isMounted.set(false);
+ FluentFuture<? extends CommitInfo> future =
+ postDataViaTransaction(domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload,
+ globalSchema, insert, point);
+ isMounted.remove();
+ return future;
+ }
+
+ public FluentFuture<? extends CommitInfo> commitConfigurationDataPost(
+ final DOMMountPoint mountPoint, final YangInstanceIdentifier path, final NormalizedNode payload,
+ final String insert, final String point) {
+ isMounted.set(true);
+ final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
+ if (domDataBrokerService.isPresent()) {
+ FluentFuture<? extends CommitInfo> future =
+ postDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path,
+ payload, modelContext(mountPoint), insert, point);
+ isMounted.remove();
+ return future;
+ }
+ isMounted.remove();
+ throw dataBrokerUnavailable(path);
+ }
+
+ // DELETE configuration
+ public FluentFuture<? extends CommitInfo> commitConfigurationDataDelete(final YangInstanceIdentifier path) {
+ return deleteDataViaTransaction(domDataBroker.newReadWriteTransaction(), CONFIGURATION, path);
+ }
+
+ public FluentFuture<? extends CommitInfo> commitConfigurationDataDelete(
+ final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
+ final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
+ if (domDataBrokerService.isPresent()) {
+ return deleteDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path);
+ }
+ throw dataBrokerUnavailable(path);
+ }
+
+ // RPC
+ public ListenableFuture<? extends DOMRpcResult> invokeRpc(final @NonNull QName type,
+ final @NonNull NormalizedNode input) {
+ if (rpcService == null) {
+ throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
+ }
+ LOG.trace("Invoke RPC {} with input: {}", type, input);
+ return rpcService.invokeRpc(type, input);
+ }
+
+ public void registerToListenDataChanges(final LogicalDatastoreType datastore, final Scope scope,
+ final ListenerAdapter listener) {
+ if (listener.isListening()) {
+ return;
+ }
+
+ final YangInstanceIdentifier path = listener.getPath();
+ DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
+ .getInstance(DOMDataTreeChangeService.class);
+ if (changeService == null) {
+ throw new UnsupportedOperationException("DOMDataBroker does not support the DOMDataTreeChangeService"
+ + domDataBroker);
+ }
+ DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(datastore, path);
+ ListenerRegistration<ListenerAdapter> registration =
+ changeService.registerDataTreeChangeListener(root, listener);
+ listener.setRegistration(registration);
+ }
+
+ private NormalizedNode readDataViaTransaction(final DOMDataTreeReadOperations transaction,
+ final LogicalDatastoreType datastore, final YangInstanceIdentifier path) {
+ return readDataViaTransaction(transaction, datastore, path, null);
+ }
+
+ private NormalizedNode readDataViaTransaction(final DOMDataTreeReadOperations transaction,
+ final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final String withDefa) {
+ LOG.trace("Read {} via Restconf: {}", datastore.name(), path);
+
+ try {
+ final Optional<NormalizedNode> optional = transaction.read(datastore, path).get();
+ return optional.map(normalizedNode -> withDefa == null ? normalizedNode :
+ prepareDataByParamWithDef(normalizedNode, path, withDefa)).orElse(null);
+ } catch (InterruptedException e) {
+ LOG.warn("Error reading {} from datastore {}", path, datastore.name(), e);
+ throw new RestconfDocumentedException("Error reading data.", e);
+ } catch (ExecutionException e) {
+ LOG.warn("Error reading {} from datastore {}", path, datastore.name(), e);
+ throw RestconfDocumentedException.decodeAndThrow("Error reading data.", Throwables.getCauseAs(e,
+ ReadFailedException.class));
+ }
+ }
+
+ private NormalizedNode prepareDataByParamWithDef(final NormalizedNode result,
+ final YangInstanceIdentifier path, final String withDefa) {
+ boolean trim;
+ switch (withDefa) {
+ case "trim":
+ trim = true;
+ break;
+ case "explicit":
+ trim = false;
+ break;
+ default:
+ throw new RestconfDocumentedException("Bad value used with with-defaults parameter : " + withDefa);
+ }
+
+ final EffectiveModelContext ctx = controllerContext.getGlobalSchema();
+ final DataSchemaContextTree baseSchemaCtxTree = DataSchemaContextTree.from(ctx);
+ final DataSchemaNode baseSchemaNode = baseSchemaCtxTree.findChild(path).orElseThrow().getDataSchemaNode();
+ if (result instanceof ContainerNode) {
+ final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> builder =
+ SchemaAwareBuilders.containerBuilder((ContainerSchemaNode) baseSchemaNode);
+ buildCont(builder, (ContainerNode) result, baseSchemaCtxTree, path, trim);
+ return builder.build();
+ }
+
+ final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> builder =
+ SchemaAwareBuilders.mapEntryBuilder((ListSchemaNode) baseSchemaNode);
+ buildMapEntryBuilder(builder, (MapEntryNode) result, baseSchemaCtxTree, path, trim,
+ ((ListSchemaNode) baseSchemaNode).getKeyDefinition());
+ return builder.build();
+ }
+
+ private void buildMapEntryBuilder(
+ final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> builder,
+ final MapEntryNode result, final DataSchemaContextTree baseSchemaCtxTree,
+ final YangInstanceIdentifier actualPath, final boolean trim, final List<QName> keys) {
+ for (final DataContainerChild child : result.body()) {
+ final YangInstanceIdentifier path = actualPath.node(child.getIdentifier());
+ final DataSchemaNode childSchema = baseSchemaCtxTree.findChild(path).orElseThrow().getDataSchemaNode();
+ if (child instanceof ContainerNode) {
+ final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> childBuilder =
+ SchemaAwareBuilders.containerBuilder((ContainerSchemaNode) childSchema);
+ buildCont(childBuilder, (ContainerNode) child, baseSchemaCtxTree, path, trim);
+ builder.withChild(childBuilder.build());
+ } else if (child instanceof MapNode) {
+ final CollectionNodeBuilder<MapEntryNode, SystemMapNode> childBuilder =
+ SchemaAwareBuilders.mapBuilder((ListSchemaNode) childSchema);
+ buildList(childBuilder, (MapNode) child, baseSchemaCtxTree, path, trim,
+ ((ListSchemaNode) childSchema).getKeyDefinition());
+ builder.withChild(childBuilder.build());
+ } else if (child instanceof LeafNode) {
+ final Object defaultVal = ((LeafSchemaNode) childSchema).getType().getDefaultValue().orElse(null);
+ final Object nodeVal = child.body();
+ final NormalizedNodeBuilder<NodeIdentifier, Object, LeafNode<Object>> leafBuilder =
+ SchemaAwareBuilders.leafBuilder((LeafSchemaNode) childSchema);
+ if (keys.contains(child.getIdentifier().getNodeType())) {
+ leafBuilder.withValue(child.body());
+ builder.withChild(leafBuilder.build());
+ } else {
+ if (trim) {
+ if (defaultVal == null || !defaultVal.equals(nodeVal)) {
+ leafBuilder.withValue(child.body());
+ builder.withChild(leafBuilder.build());
+ }
+ } else {
+ if (defaultVal != null && defaultVal.equals(nodeVal)) {
+ leafBuilder.withValue(child.body());
+ builder.withChild(leafBuilder.build());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void buildList(final CollectionNodeBuilder<MapEntryNode, SystemMapNode> builder, final MapNode result,
+ final DataSchemaContextTree baseSchemaCtxTree, final YangInstanceIdentifier path, final boolean trim,
+ final List<QName> keys) {
+ for (final MapEntryNode mapEntryNode : result.body()) {
+ final YangInstanceIdentifier actualNode = path.node(mapEntryNode.getIdentifier());
+ final DataSchemaNode childSchema = baseSchemaCtxTree.findChild(actualNode).orElseThrow()
+ .getDataSchemaNode();
+ final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder =
+ SchemaAwareBuilders.mapEntryBuilder((ListSchemaNode) childSchema);
+ buildMapEntryBuilder(mapEntryBuilder, mapEntryNode, baseSchemaCtxTree, actualNode, trim, keys);
+ builder.withChild(mapEntryBuilder.build());
+ }
+ }
+
+ private void buildCont(final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> builder,
+ final ContainerNode result, final DataSchemaContextTree baseSchemaCtxTree,
+ final YangInstanceIdentifier actualPath, final boolean trim) {
+ for (final DataContainerChild child : result.body()) {
+ final YangInstanceIdentifier path = actualPath.node(child.getIdentifier());
+ final DataSchemaNode childSchema = baseSchemaCtxTree.findChild(path).orElseThrow().getDataSchemaNode();
+ if (child instanceof ContainerNode) {
+ final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> builderChild =
+ SchemaAwareBuilders.containerBuilder((ContainerSchemaNode) childSchema);
+ buildCont(builderChild, result, baseSchemaCtxTree, actualPath, trim);
+ builder.withChild(builderChild.build());
+ } else if (child instanceof MapNode) {
+ final CollectionNodeBuilder<MapEntryNode, SystemMapNode> childBuilder =
+ SchemaAwareBuilders.mapBuilder((ListSchemaNode) childSchema);
+ buildList(childBuilder, (MapNode) child, baseSchemaCtxTree, path, trim,
+ ((ListSchemaNode) childSchema).getKeyDefinition());
+ builder.withChild(childBuilder.build());
+ } else if (child instanceof LeafNode) {
+ final Object defaultVal = ((LeafSchemaNode) childSchema).getType().getDefaultValue().orElse(null);
+ final Object nodeVal = child.body();
+ final NormalizedNodeBuilder<NodeIdentifier, Object, LeafNode<Object>> leafBuilder =
+ SchemaAwareBuilders.leafBuilder((LeafSchemaNode) childSchema);
+ if (trim) {
+ if (defaultVal == null || !defaultVal.equals(nodeVal)) {
+ leafBuilder.withValue(child.body());
+ builder.withChild(leafBuilder.build());
+ }
+ } else {
+ if (defaultVal != null && defaultVal.equals(nodeVal)) {
+ leafBuilder.withValue(child.body());
+ builder.withChild(leafBuilder.build());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * POST data and submit transaction {@link DOMDataReadWriteTransaction}.
+ */
+ private FluentFuture<? extends CommitInfo> postDataViaTransaction(
+ final DOMDataTreeReadWriteTransaction rwTransaction, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path, final NormalizedNode payload,
+ final EffectiveModelContext schemaContext, final String insert, final String point) {
+ LOG.trace("POST {} via Restconf: {} with payload {}", datastore.name(), path, payload);
+ postData(rwTransaction, datastore, path, payload, schemaContext, insert, point);
+ return rwTransaction.commit();
+ }
+
+ /**
+ * POST data and do NOT submit transaction {@link DOMDataReadWriteTransaction}.
+ */
+ private void postDataWithinTransaction(
+ final DOMDataTreeReadWriteTransaction rwTransaction, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path, final NormalizedNode payload,
+ final EffectiveModelContext schemaContext) {
+ LOG.trace("POST {} within Restconf Patch: {} with payload {}", datastore.name(), path, payload);
+ postData(rwTransaction, datastore, path, payload, schemaContext, null, null);
+ }
+
+ private void postData(final DOMDataTreeReadWriteTransaction rwTransaction, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path, final NormalizedNode payload,
+ final EffectiveModelContext schemaContext, final String insert, final String point) {
+ if (insert == null) {
+ makeNormalPost(rwTransaction, datastore, path, payload, schemaContext);
+ return;
+ }
+
+ final DataSchemaNode schemaNode = checkListAndOrderedType(schemaContext, path);
+ checkItemDoesNotExists(rwTransaction, datastore, path);
+ switch (insert) {
+ case "first":
+ if (schemaNode instanceof ListSchemaNode) {
+ final UserMapNode readList =
+ (UserMapNode) this.readConfigurationData(path.getParent().getParent());
+ if (readList == null || readList.isEmpty()) {
+ simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
+ } else {
+ rwTransaction.delete(datastore, path.getParent().getParent());
+ simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
+ makeNormalPost(rwTransaction, datastore, path.getParent().getParent(), readList,
+ schemaContext);
+ }
+ } else {
+ final UserLeafSetNode<?> readLeafList =
+ (UserLeafSetNode<?>) readConfigurationData(path.getParent());
+ if (readLeafList == null || readLeafList.isEmpty()) {
+ simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
+ } else {
+ rwTransaction.delete(datastore, path.getParent());
+ simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
+ makeNormalPost(rwTransaction, datastore, path.getParent().getParent(), readLeafList,
+ schemaContext);
+ }
+ }
+ break;
+ case "last":
+ simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
+ break;
+ case "before":
+ if (schemaNode instanceof ListSchemaNode) {
+ final UserMapNode readList =
+ (UserMapNode) this.readConfigurationData(path.getParent().getParent());
+ if (readList == null || readList.isEmpty()) {
+ simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
+ } else {
+ insertWithPointListPost(rwTransaction, datastore, path, payload, schemaContext, point,
+ readList,
+ true);
+ }
+ } else {
+ final UserLeafSetNode<?> readLeafList =
+ (UserLeafSetNode<?>) readConfigurationData(path.getParent());
+ if (readLeafList == null || readLeafList.isEmpty()) {
+ simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
+ } else {
+ insertWithPointLeafListPost(rwTransaction, datastore, path, payload, schemaContext, point,
+ readLeafList, true);
+ }
+ }
+ break;
+ case "after":
+ if (schemaNode instanceof ListSchemaNode) {
+ final UserMapNode readList =
+ (UserMapNode) this.readConfigurationData(path.getParent().getParent());
+ if (readList == null || readList.isEmpty()) {
+ simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
+ } else {
+ insertWithPointListPost(rwTransaction, datastore, path, payload, schemaContext, point,
+ readList,
+ false);
+ }
+ } else {
+ final UserLeafSetNode<?> readLeafList =
+ (UserLeafSetNode<?>) readConfigurationData(path.getParent());
+ if (readLeafList == null || readLeafList.isEmpty()) {
+ simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
+ } else {
+ insertWithPointLeafListPost(rwTransaction, datastore, path, payload, schemaContext, point,
+ readLeafList, false);
+ }
+ }
+ break;
+ default:
+ throw new RestconfDocumentedException(
+ "Used bad value of insert parameter. Possible values are first, last, before or after, "
+ + "but was: " + insert);
+ }
+ }
+
+ private void insertWithPointLeafListPost(final DOMDataTreeReadWriteTransaction rwTransaction,
+ final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode payload,
+ final EffectiveModelContext schemaContext, final String point, final UserLeafSetNode<?> readLeafList,
+ final boolean before) {
+ rwTransaction.delete(datastore, path.getParent().getParent());
+ final InstanceIdentifierContext instanceIdentifier = controllerContext.toInstanceIdentifier(point);
+ int lastItemPosition = 0;
+ for (final LeafSetEntryNode<?> nodeChild : readLeafList.body()) {
+ if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
+ break;
+ }
+ lastItemPosition++;
+ }
+ if (!before) {
+ lastItemPosition++;
+ }
+ int lastInsertedPosition = 0;
+ final NormalizedNode emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent().getParent());
+ rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+ for (final LeafSetEntryNode<?> nodeChild : readLeafList.body()) {
+ if (lastInsertedPosition == lastItemPosition) {
+ checkItemDoesNotExists(rwTransaction, datastore, path);
+ simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
+ }
+ final YangInstanceIdentifier childPath = path.getParent().getParent().node(nodeChild.getIdentifier());
+ checkItemDoesNotExists(rwTransaction, datastore, childPath);
+ rwTransaction.put(datastore, childPath, nodeChild);
+ lastInsertedPosition++;
+ }
+ }
+
+ private void insertWithPointListPost(final DOMDataTreeReadWriteTransaction rwTransaction,
+ final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path, final NormalizedNode payload, final EffectiveModelContext schemaContext,
+ final String point, final MapNode readList, final boolean before) {
+ rwTransaction.delete(datastore, path.getParent().getParent());
+ final InstanceIdentifierContext instanceIdentifier = controllerContext.toInstanceIdentifier(point);
+ int lastItemPosition = 0;
+ for (final MapEntryNode mapEntryNode : readList.body()) {
+ if (mapEntryNode.getIdentifier()
+ .equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
+ break;
+ }
+ lastItemPosition++;
+ }
+ if (!before) {
+ lastItemPosition++;
+ }
+ int lastInsertedPosition = 0;
+ final NormalizedNode emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent().getParent());
+ rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+ for (final MapEntryNode mapEntryNode : readList.body()) {
+ if (lastInsertedPosition == lastItemPosition) {
+ checkItemDoesNotExists(rwTransaction, datastore, path);
+ simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
+ }
+ final YangInstanceIdentifier childPath = path.getParent().getParent().node(mapEntryNode.getIdentifier());
+ checkItemDoesNotExists(rwTransaction, datastore, childPath);
+ rwTransaction.put(datastore, childPath, mapEntryNode);
+ lastInsertedPosition++;
+ }
+ }
+
+ private static DataSchemaNode checkListAndOrderedType(final EffectiveModelContext ctx,
+ final YangInstanceIdentifier path) {
+ final YangInstanceIdentifier parent = path.getParent();
+ final DataSchemaContextNode<?> node = DataSchemaContextTree.from(ctx).findChild(parent).orElseThrow();
+ final DataSchemaNode dataSchemaNode = node.getDataSchemaNode();
+
+ if (dataSchemaNode instanceof ListSchemaNode) {
+ if (!((ListSchemaNode) dataSchemaNode).isUserOrdered()) {
+ throw new RestconfDocumentedException("Insert parameter can be used only with ordered-by user list.");
+ }
+ return dataSchemaNode;
+ }
+ if (dataSchemaNode instanceof LeafListSchemaNode) {
+ if (!((LeafListSchemaNode) dataSchemaNode).isUserOrdered()) {
+ throw new RestconfDocumentedException(
+ "Insert parameter can be used only with ordered-by user leaf-list.");
+ }
+ return dataSchemaNode;
+ }
+ throw new RestconfDocumentedException("Insert parameter can be used only with list or leaf-list");
+ }
+
+ private void makeNormalPost(final DOMDataTreeReadWriteTransaction rwTransaction,
+ final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode payload,
+ final EffectiveModelContext schemaContext) {
+ final Collection<? extends NormalizedNode> children;
+ if (payload instanceof MapNode) {
+ children = ((MapNode) payload).body();
+ } else if (payload instanceof LeafSetNode) {
+ children = ((LeafSetNode<?>) payload).body();
+ } else {
+ simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
+ return;
+ }
+
+ final NormalizedNode emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
+ if (children.isEmpty()) {
+ if (isMounted != null && !isMounted.get()) {
+
+ rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()),
+ emptySubtree);
+ ensureParentsByMerge(datastore, path, rwTransaction, schemaContext);
+ }
+ return;
+ }
+
+ // Kick off batch existence check first...
+ final BatchedExistenceCheck check = BatchedExistenceCheck.start(rwTransaction, datastore, path, children);
+
+ // ... now enqueue modifications. This relies on proper ordering of requests, i.e. these will not affect the
+ // result of the existence checks...
+ if (isMounted != null && !isMounted.get()) {
+
+ rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+ ensureParentsByMerge(datastore, path, rwTransaction, schemaContext);
+ }
+ for (final NormalizedNode child : children) {
+ // FIXME: we really want a create(YangInstanceIdentifier, NormalizedNode) method in the transaction,
+ // as that would allow us to skip the existence checks
+ rwTransaction.put(datastore, path.node(child.getIdentifier()), child);
+ }
+
+ // ... finally collect existence checks and abort the transaction if any of them failed.
+ final Entry<YangInstanceIdentifier, ReadFailedException> failure;
+ try {
+ failure = check.getFailure();
+ } catch (InterruptedException e) {
+ rwTransaction.cancel();
+ throw new RestconfDocumentedException("Could not determine the existence of path " + path, e);
+ }
+
+ if (failure != null) {
+ rwTransaction.cancel();
+ final ReadFailedException e = failure.getValue();
+ if (e == null) {
+ throw new RestconfDocumentedException("Data already exists for path: " + failure.getKey(),
+ ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS);
+ }
+
+ throw new RestconfDocumentedException("Could not determine the existence of path " + failure.getKey(), e,
+ e.getErrorList());
+ }
+ }
+
+ private void simplePostPut(final DOMDataTreeReadWriteTransaction rwTransaction,
+ final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode payload,
+ final EffectiveModelContext schemaContext) {
+ checkItemDoesNotExists(rwTransaction, datastore, path);
+ if (isMounted != null && !isMounted.get()) {
+ ensureParentsByMerge(datastore, path, rwTransaction, schemaContext);
+ }
+ rwTransaction.put(datastore, path, payload);
+ }
+
+ private static boolean doesItemExist(final DOMDataTreeReadWriteTransaction rwTransaction,
+ final LogicalDatastoreType store, final YangInstanceIdentifier path) {
+ try {
+ return rwTransaction.exists(store, path).get();
+ } catch (InterruptedException e) {
+ rwTransaction.cancel();
+ throw new RestconfDocumentedException("Could not determine the existence of path " + path, e);
+ } catch (ExecutionException e) {
+ rwTransaction.cancel();
+ throw RestconfDocumentedException.decodeAndThrow("Could not determine the existence of path " + path,
+ Throwables.getCauseAs(e, ReadFailedException.class));
+ }
+ }
+
+ /**
+ * Check if item already exists. Throws error if it does NOT already exist.
+ * @param rwTransaction Current transaction
+ * @param store Used datastore
+ * @param path Path to item to verify its existence
+ */
+ private static void checkItemExists(final DOMDataTreeReadWriteTransaction rwTransaction,
+ final LogicalDatastoreType store, final YangInstanceIdentifier path) {
+ if (!doesItemExist(rwTransaction, store, path)) {
+ LOG.trace("Operation via Restconf was not executed because data at {} does not exist", path);
+ rwTransaction.cancel();
+ throw new RestconfDocumentedException("Data does not exist for path: " + path, ErrorType.PROTOCOL,
+ ErrorTag.DATA_MISSING);
+ }
+ }
+
+ /**
+ * Check if item does NOT already exist. Throws error if it already exists.
+ * @param rwTransaction Current transaction
+ * @param store Used datastore
+ * @param path Path to item to verify its existence
+ */
+ private static void checkItemDoesNotExists(final DOMDataTreeReadWriteTransaction rwTransaction,
+ final LogicalDatastoreType store, final YangInstanceIdentifier path) {
+ if (doesItemExist(rwTransaction, store, path)) {
+ LOG.trace("Operation via Restconf was not executed because data at {} already exists", path);
+ rwTransaction.cancel();
+ throw new RestconfDocumentedException("Data already exists for path: " + path, ErrorType.PROTOCOL,
+ ErrorTag.DATA_EXISTS);
+ }
+ }
+
+ /**
+ * PUT data and submit {@link DOMDataReadWriteTransaction}.
+ *
+ * @param point
+ * point
+ * @param insert
+ * insert
+ */
+ private FluentFuture<? extends CommitInfo> putDataViaTransaction(
+ final DOMDataTreeReadWriteTransaction readWriteTransaction, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path, final NormalizedNode payload,
+ final EffectiveModelContext schemaContext, final String insert, final String point) {
+ LOG.trace("Put {} via Restconf: {} with payload {}", datastore.name(), path, payload);
+ putData(readWriteTransaction, datastore, path, payload, schemaContext, insert, point);
+ return readWriteTransaction.commit();
+ }
+
+ /**
+ * PUT data and do NOT submit {@link DOMDataReadWriteTransaction}.
+ */
+ private void putDataWithinTransaction(
+ final DOMDataTreeReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path, final NormalizedNode payload,
+ final EffectiveModelContext schemaContext) {
+ LOG.trace("Put {} within Restconf Patch: {} with payload {}", datastore.name(), path, payload);
+ putData(writeTransaction, datastore, path, payload, schemaContext, null, null);
+ }
+
+ // FIXME: This is doing correct put for container and list children, not sure if this will work for choice case
+ private void putData(final DOMDataTreeReadWriteTransaction rwTransaction, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path, final NormalizedNode payload,
+ final EffectiveModelContext schemaContext, final String insert, final String point) {
+ if (insert == null) {
+ makePut(rwTransaction, datastore, path, payload, schemaContext);
+ return;
+ }
+
+ final DataSchemaNode schemaNode = checkListAndOrderedType(schemaContext, path);
+ checkItemDoesNotExists(rwTransaction, datastore, path);
+ switch (insert) {
+ case "first":
+ if (schemaNode instanceof ListSchemaNode) {
+ final UserMapNode readList = (UserMapNode) this.readConfigurationData(path.getParent());
+ if (readList == null || readList.isEmpty()) {
+ simplePut(datastore, path, rwTransaction, schemaContext, payload);
+ } else {
+ rwTransaction.delete(datastore, path.getParent());
+ simplePut(datastore, path, rwTransaction, schemaContext, payload);
+ makePut(rwTransaction, datastore, path.getParent(), readList, schemaContext);
+ }
+ } else {
+ final UserLeafSetNode<?> readLeafList =
+ (UserLeafSetNode<?>) readConfigurationData(path.getParent());
+ if (readLeafList == null || readLeafList.isEmpty()) {
+ simplePut(datastore, path, rwTransaction, schemaContext, payload);
+ } else {
+ rwTransaction.delete(datastore, path.getParent());
+ simplePut(datastore, path, rwTransaction, schemaContext, payload);
+ makePut(rwTransaction, datastore, path.getParent(), readLeafList,
+ schemaContext);
+ }
+ }
+ break;
+ case "last":
+ simplePut(datastore, path, rwTransaction, schemaContext, payload);
+ break;
+ case "before":
+ if (schemaNode instanceof ListSchemaNode) {
+ final UserMapNode readList = (UserMapNode) this.readConfigurationData(path.getParent());
+ if (readList == null || readList.isEmpty()) {
+ simplePut(datastore, path, rwTransaction, schemaContext, payload);
+ } else {
+ insertWithPointListPut(rwTransaction, datastore, path, payload, schemaContext, point,
+ readList, true);
+ }
+ } else {
+ final UserLeafSetNode<?> readLeafList =
+ (UserLeafSetNode<?>) readConfigurationData(path.getParent());
+ if (readLeafList == null || readLeafList.isEmpty()) {
+ simplePut(datastore, path, rwTransaction, schemaContext, payload);
+ } else {
+ insertWithPointLeafListPut(rwTransaction, datastore, path, payload, schemaContext, point,
+ readLeafList, true);
+ }
+ }
+ break;
+ case "after":
+ if (schemaNode instanceof ListSchemaNode) {
+ final UserMapNode readList = (UserMapNode) this.readConfigurationData(path.getParent());
+ if (readList == null || readList.isEmpty()) {
+ simplePut(datastore, path, rwTransaction, schemaContext, payload);
+ } else {
+ insertWithPointListPut(rwTransaction, datastore, path, payload, schemaContext, point,
+ readList, false);
+ }
+ } else {
+ final UserLeafSetNode<?> readLeafList =
+ (UserLeafSetNode<?>) readConfigurationData(path.getParent());
+ if (readLeafList == null || readLeafList.isEmpty()) {
+ simplePut(datastore, path, rwTransaction, schemaContext, payload);
+ } else {
+ insertWithPointLeafListPut(rwTransaction, datastore, path, payload, schemaContext, point,
+ readLeafList, false);
+ }
+ }
+ break;
+ default:
+ throw new RestconfDocumentedException(
+ "Used bad value of insert parameter. Possible values are first, last, before or after, but was: "
+ + insert);
+ }
+ }
+
+ private void insertWithPointLeafListPut(final DOMDataTreeWriteTransaction tx,
+ final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode payload,
+ final EffectiveModelContext schemaContext, final String point, final UserLeafSetNode<?> readLeafList,
+ final boolean before) {
+ tx.delete(datastore, path.getParent());
+ final InstanceIdentifierContext instanceIdentifier = controllerContext.toInstanceIdentifier(point);
+ int index1 = 0;
+ for (final LeafSetEntryNode<?> nodeChild : readLeafList.body()) {
+ if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
+ break;
+ }
+ index1++;
+ }
+ if (!before) {
+ index1++;
+ }
+ int index2 = 0;
+ final NormalizedNode emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
+ tx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+ for (final LeafSetEntryNode<?> nodeChild : readLeafList.body()) {
+ if (index2 == index1) {
+ simplePut(datastore, path, tx, schemaContext, payload);
+ }
+ final YangInstanceIdentifier childPath = path.getParent().node(nodeChild.getIdentifier());
+ tx.put(datastore, childPath, nodeChild);
+ index2++;
+ }
+ }
+
+ private void insertWithPointListPut(final DOMDataTreeWriteTransaction tx, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path, final NormalizedNode payload, final EffectiveModelContext schemaContext,
+ final String point, final UserMapNode readList, final boolean before) {
+ tx.delete(datastore, path.getParent());
+ final InstanceIdentifierContext instanceIdentifier = controllerContext.toInstanceIdentifier(point);
+ int index1 = 0;
+ for (final MapEntryNode mapEntryNode : readList.body()) {
+ if (mapEntryNode.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
+ break;
+ }
+ index1++;
+ }
+ if (!before) {
+ index1++;
+ }
+ int index2 = 0;
+ final NormalizedNode emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
+ tx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+ for (final MapEntryNode mapEntryNode : readList.body()) {
+ if (index2 == index1) {
+ simplePut(datastore, path, tx, schemaContext, payload);
+ }
+ final YangInstanceIdentifier childPath = path.getParent().node(mapEntryNode.getIdentifier());
+ tx.put(datastore, childPath, mapEntryNode);
+ index2++;
+ }
+ }
+
+ private void makePut(final DOMDataTreeWriteTransaction tx, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path, final NormalizedNode payload,
+ final EffectiveModelContext schemaContext) {
+ if (payload instanceof MapNode) {
+ final NormalizedNode emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
+ if (isMounted != null && !isMounted.get()) {
+ tx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+ ensureParentsByMerge(datastore, path, tx, schemaContext);
+ }
+ for (final MapEntryNode child : ((MapNode) payload).body()) {
+ final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
+ tx.put(datastore, childPath, child);
+ }
+ } else {
+ simplePut(datastore, path, tx, schemaContext, payload);
+ }
+ }
+
+ private void simplePut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
+ final DOMDataTreeWriteTransaction tx, final EffectiveModelContext schemaContext,
+ final NormalizedNode payload) {
+ if (isMounted != null && !isMounted.get()) {
+ ensureParentsByMerge(datastore, path, tx, schemaContext);
+ }
+ tx.put(datastore, path, payload);
+ }
+
+ private static FluentFuture<? extends CommitInfo> deleteDataViaTransaction(
+ final DOMDataTreeReadWriteTransaction readWriteTransaction, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path) {
+ LOG.trace("Delete {} via Restconf: {}", datastore.name(), path);
+ checkItemExists(readWriteTransaction, datastore, path);
+ readWriteTransaction.delete(datastore, path);
+ return readWriteTransaction.commit();
+ }
+
+ private static void deleteDataWithinTransaction(final DOMDataTreeWriteTransaction tx,
+ final LogicalDatastoreType datastore, final YangInstanceIdentifier path) {
+ LOG.trace("Delete {} within Restconf Patch: {}", datastore.name(), path);
+ tx.delete(datastore, path);
+ }
+
+ private static void mergeDataWithinTransaction(final DOMDataTreeWriteTransaction tx,
+ final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode payload,
+ final EffectiveModelContext schemaContext) {
+ LOG.trace("Merge {} within Restconf Patch: {} with payload {}", datastore.name(), path, payload);
+ ensureParentsByMerge(datastore, path, tx, schemaContext);
+
+ // Since YANG Patch provides the option to specify what kind of operation for each edit,
+ // OpenDaylight should not change it.
+ tx.merge(datastore, path, payload);
+ }
+
+ public void registerToListenNotification(final NotificationListenerAdapter listener) {
+ if (listener.isListening()) {
+ return;
+ }
+
+ final ListenerRegistration<DOMNotificationListener> registration = domNotification
+ .registerNotificationListener(listener, listener.getSchemaPath());
+
+ listener.setRegistration(registration);
+ }
+
+ private static void ensureParentsByMerge(final LogicalDatastoreType store,
+ final YangInstanceIdentifier normalizedPath, final DOMDataTreeWriteTransaction tx,
+ final EffectiveModelContext schemaContext) {
+ final List<PathArgument> normalizedPathWithoutChildArgs = new ArrayList<>();
+ YangInstanceIdentifier rootNormalizedPath = null;
+
+ final Iterator<PathArgument> it = normalizedPath.getPathArguments().iterator();
+
+ while (it.hasNext()) {
+ final PathArgument pathArgument = it.next();
+ if (rootNormalizedPath == null) {
+ rootNormalizedPath = YangInstanceIdentifier.create(pathArgument);
+ }
+
+ if (it.hasNext()) {
+ normalizedPathWithoutChildArgs.add(pathArgument);
+ }
+ }
+
+ if (normalizedPathWithoutChildArgs.isEmpty()) {
+ return;
+ }
+
+ checkArgument(rootNormalizedPath != null, "Empty path received");
+ tx.merge(store, rootNormalizedPath, ImmutableNodes.fromInstanceId(schemaContext,
+ YangInstanceIdentifier.create(normalizedPathWithoutChildArgs)));
+ }
+
+ private static RestconfDocumentedException dataBrokerUnavailable(final YangInstanceIdentifier path) {
+ LOG.warn("DOM data broker service is not available for mount point {}", path);
+ return new RestconfDocumentedException("DOM data broker service is not available for mount point " + path);
+ }
+
+ private static EffectiveModelContext modelContext(final DOMMountPoint mountPoint) {
+ return mountPoint.getService(DOMSchemaService.class)
+ .flatMap(svc -> Optional.ofNullable(svc.getGlobalContext()))
+ .orElse(null);
+ }
+
+ private static final class PatchStatusContextHelper {
+ PatchStatusContext status;
+
+ public PatchStatusContext getStatus() {
+ return status;
+ }
+
+ public void setStatus(final PatchStatusContext status) {
+ this.status = status;
+ }
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/ControllerContext.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/ControllerContext.java
new file mode 100644
index 0000000..8e05bf1
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/ControllerContext.java
@@ -0,0 +1,1006 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.base.Verify.verify;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.io.Closeable;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.annotation.PreDestroy;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.ws.rs.core.Response.Status;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.mdsal.dom.api.DOMMountPoint;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
+import org.opendaylight.mdsal.dom.api.DOMSchemaService;
+import org.opendaylight.mdsal.dom.api.DOMYangTextSourceProvider;
+import org.opendaylight.netconf.sal.rest.api.Draft02;
+import org.opendaylight.netconf.sal.rest.api.Draft02.RestConfModule;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.util.RestUtil;
+import org.opendaylight.yangtools.concepts.IllegalArgumentCodec;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.Revision;
+import org.opendaylight.yangtools.yang.common.XMLNamespace;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.InstanceIdentifierBuilder;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContextListener;
+import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Singleton
+public final class ControllerContext implements EffectiveModelContextListener, Closeable {
+ // FIXME: this should be in md-sal somewhere
+ public static final String MOUNT = "yang-ext:mount";
+
+ private static final Logger LOG = LoggerFactory.getLogger(ControllerContext.class);
+
+ private static final String NULL_VALUE = "null";
+
+ private static final String MOUNT_MODULE = "yang-ext";
+
+ private static final String MOUNT_NODE = "mount";
+
+ private static final Splitter SLASH_SPLITTER = Splitter.on('/');
+
+ private final AtomicReference<Map<QName, RpcDefinition>> qnameToRpc = new AtomicReference<>(Collections.emptyMap());
+
+ private final DOMMountPointService mountService;
+ private final DOMYangTextSourceProvider yangTextSourceProvider;
+ private final ListenerRegistration<?> listenerRegistration;
+ private volatile EffectiveModelContext globalSchema;
+ private volatile DataNormalizer dataNormalizer;
+
+ @Inject
+ public ControllerContext(final DOMSchemaService schemaService, final DOMMountPointService mountService,
+ final DOMSchemaService domSchemaService) {
+ this.mountService = mountService;
+ yangTextSourceProvider = domSchemaService.getExtensions().getInstance(DOMYangTextSourceProvider.class);
+
+ onModelContextUpdated(schemaService.getGlobalContext());
+ listenerRegistration = schemaService.registerSchemaContextListener(this);
+ }
+
+ /**
+ * Factory method.
+ *
+ * @deprecated Just use the
+ * {@link #ControllerContext(DOMSchemaService, DOMMountPointService, DOMSchemaService)}
+ * constructor instead.
+ */
+ @Deprecated
+ public static ControllerContext newInstance(final DOMSchemaService schemaService,
+ final DOMMountPointService mountService, final DOMSchemaService domSchemaService) {
+ return new ControllerContext(schemaService, mountService, domSchemaService);
+ }
+
+ private void setGlobalSchema(final EffectiveModelContext globalSchema) {
+ this.globalSchema = globalSchema;
+ dataNormalizer = new DataNormalizer(globalSchema);
+ }
+
+ public DOMYangTextSourceProvider getYangTextSourceProvider() {
+ return yangTextSourceProvider;
+ }
+
+ private void checkPreconditions() {
+ if (globalSchema == null) {
+ throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
+ }
+ }
+
+ @Override
+ @PreDestroy
+ public void close() {
+ listenerRegistration.close();
+ }
+
+ public void setSchemas(final EffectiveModelContext schemas) {
+ onModelContextUpdated(schemas);
+ }
+
+ public InstanceIdentifierContext toInstanceIdentifier(final String restconfInstance) {
+ return toIdentifier(restconfInstance, false);
+ }
+
+ public EffectiveModelContext getGlobalSchema() {
+ return globalSchema;
+ }
+
+ public InstanceIdentifierContext toMountPointIdentifier(final String restconfInstance) {
+ return toIdentifier(restconfInstance, true);
+ }
+
+ private InstanceIdentifierContext toIdentifier(final String restconfInstance,
+ final boolean toMountPointIdentifier) {
+ checkPreconditions();
+
+ if (restconfInstance == null) {
+ return InstanceIdentifierContext.ofLocalRoot(globalSchema);
+ }
+
+ final List<String> pathArgs = urlPathArgsDecode(SLASH_SPLITTER.split(restconfInstance));
+ omitFirstAndLastEmptyString(pathArgs);
+ if (pathArgs.isEmpty()) {
+ return null;
+ }
+
+ final String first = pathArgs.iterator().next();
+ final String startModule = toModuleName(first);
+ if (startModule == null) {
+ throw new RestconfDocumentedException("First node in URI has to be in format \"moduleName:nodeName\"",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ final Collection<? extends Module> latestModule = globalSchema.findModules(startModule);
+ if (latestModule.isEmpty()) {
+ throw new RestconfDocumentedException("The module named '" + startModule + "' does not exist.",
+ ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
+ }
+
+ final InstanceIdentifierContext iiWithSchemaNode = collectPathArguments(YangInstanceIdentifier.builder(),
+ new ArrayDeque<>(), pathArgs, latestModule.iterator().next(), null, toMountPointIdentifier);
+
+ if (iiWithSchemaNode == null) {
+ throw new RestconfDocumentedException("URI has bad format", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ return iiWithSchemaNode;
+ }
+
+ private static List<String> omitFirstAndLastEmptyString(final List<String> list) {
+ if (list.isEmpty()) {
+ return list;
+ }
+
+ final String head = list.iterator().next();
+ if (head.isEmpty()) {
+ list.remove(0);
+ }
+
+ if (list.isEmpty()) {
+ return list;
+ }
+
+ final String last = list.get(list.size() - 1);
+ if (last.isEmpty()) {
+ list.remove(list.size() - 1);
+ }
+
+ return list;
+ }
+
+ public Module findModuleByName(final String moduleName) {
+ checkPreconditions();
+ checkArgument(moduleName != null && !moduleName.isEmpty());
+ return globalSchema.findModules(moduleName).stream().findFirst().orElse(null);
+ }
+
+ public static Module findModuleByName(final DOMMountPoint mountPoint, final String moduleName) {
+ checkArgument(moduleName != null && mountPoint != null);
+
+ final EffectiveModelContext mountPointSchema = getModelContext(mountPoint);
+ return mountPointSchema == null ? null
+ : mountPointSchema.findModules(moduleName).stream().findFirst().orElse(null);
+ }
+
+ public Module findModuleByNamespace(final XMLNamespace namespace) {
+ checkPreconditions();
+ checkArgument(namespace != null);
+ return globalSchema.findModules(namespace).stream().findFirst().orElse(null);
+ }
+
+ public static Module findModuleByNamespace(final DOMMountPoint mountPoint, final XMLNamespace namespace) {
+ checkArgument(namespace != null && mountPoint != null);
+
+ final EffectiveModelContext mountPointSchema = getModelContext(mountPoint);
+ return mountPointSchema == null ? null
+ : mountPointSchema.findModules(namespace).stream().findFirst().orElse(null);
+ }
+
+ public Module findModuleByNameAndRevision(final String name, final Revision revision) {
+ checkPreconditions();
+ checkArgument(name != null && revision != null);
+
+ return globalSchema.findModule(name, revision).orElse(null);
+ }
+
+ public Module findModuleByNameAndRevision(final DOMMountPoint mountPoint, final String name,
+ final Revision revision) {
+ checkPreconditions();
+ checkArgument(name != null && revision != null && mountPoint != null);
+
+ final EffectiveModelContext schemaContext = getModelContext(mountPoint);
+ return schemaContext == null ? null : schemaContext.findModule(name, revision).orElse(null);
+ }
+
+ public DataNodeContainer getDataNodeContainerFor(final YangInstanceIdentifier path) {
+ checkPreconditions();
+
+ final Iterable<PathArgument> elements = path.getPathArguments();
+ final PathArgument head = elements.iterator().next();
+ final QName startQName = head.getNodeType();
+ final Module initialModule = globalSchema.findModule(startQName.getModule()).orElse(null);
+ DataNodeContainer node = initialModule;
+ for (final PathArgument element : elements) {
+ final QName _nodeType = element.getNodeType();
+ final DataSchemaNode potentialNode = childByQName(node, _nodeType);
+ if (potentialNode == null || !isListOrContainer(potentialNode)) {
+ return null;
+ }
+ node = (DataNodeContainer) potentialNode;
+ }
+
+ return node;
+ }
+
+ public String toFullRestconfIdentifier(final YangInstanceIdentifier path, final DOMMountPoint mount) {
+ checkPreconditions();
+
+ final Iterable<PathArgument> elements = path.getPathArguments();
+ final StringBuilder builder = new StringBuilder();
+ final PathArgument head = elements.iterator().next();
+ final QName startQName = head.getNodeType();
+ final EffectiveModelContext schemaContext;
+ if (mount != null) {
+ schemaContext = getModelContext(mount);
+ } else {
+ schemaContext = globalSchema;
+ }
+ final Module initialModule = schemaContext.findModule(startQName.getModule()).orElse(null);
+ DataNodeContainer node = initialModule;
+ for (final PathArgument element : elements) {
+ if (!(element instanceof AugmentationIdentifier)) {
+ final QName _nodeType = element.getNodeType();
+ final DataSchemaNode potentialNode = childByQName(node, _nodeType);
+ if ((!(element instanceof NodeIdentifier) || !(potentialNode instanceof ListSchemaNode))
+ && !(potentialNode instanceof ChoiceSchemaNode)) {
+ builder.append(convertToRestconfIdentifier(element, potentialNode, mount));
+ if (potentialNode instanceof DataNodeContainer) {
+ node = (DataNodeContainer) potentialNode;
+ }
+ }
+ }
+ }
+
+ return builder.toString();
+ }
+
+ public String findModuleNameByNamespace(final XMLNamespace namespace) {
+ checkPreconditions();
+
+ final Module module = findModuleByNamespace(namespace);
+ return module == null ? null : module.getName();
+ }
+
+ public static String findModuleNameByNamespace(final DOMMountPoint mountPoint, final XMLNamespace namespace) {
+ final Module module = findModuleByNamespace(mountPoint, namespace);
+ return module == null ? null : module.getName();
+ }
+
+ public XMLNamespace findNamespaceByModuleName(final String moduleName) {
+ final Module module = findModuleByName(moduleName);
+ return module == null ? null : module.getNamespace();
+ }
+
+ public static XMLNamespace findNamespaceByModuleName(final DOMMountPoint mountPoint, final String moduleName) {
+ final Module module = findModuleByName(mountPoint, moduleName);
+ return module == null ? null : module.getNamespace();
+ }
+
+ public Collection<? extends Module> getAllModules(final DOMMountPoint mountPoint) {
+ checkPreconditions();
+
+ final EffectiveModelContext schemaContext = mountPoint == null ? null : getModelContext(mountPoint);
+ return schemaContext == null ? null : schemaContext.getModules();
+ }
+
+ public Collection<? extends Module> getAllModules() {
+ checkPreconditions();
+ return globalSchema.getModules();
+ }
+
+ private static String toRestconfIdentifier(final EffectiveModelContext context, final QName qname) {
+ final Module schema = context.findModule(qname.getModule()).orElse(null);
+ return schema == null ? null : schema.getName() + ':' + qname.getLocalName();
+ }
+
+ public String toRestconfIdentifier(final QName qname, final DOMMountPoint mountPoint) {
+ return mountPoint != null ? toRestconfIdentifier(getModelContext(mountPoint), qname)
+ : toRestconfIdentifier(qname);
+ }
+
+ public String toRestconfIdentifier(final QName qname) {
+ checkPreconditions();
+
+ return toRestconfIdentifier(globalSchema, qname);
+ }
+
+ public static String toRestconfIdentifier(final DOMMountPoint mountPoint, final QName qname) {
+ return mountPoint == null ? null : toRestconfIdentifier(getModelContext(mountPoint), qname);
+ }
+
+ public Module getRestconfModule() {
+ return findModuleByNameAndRevision(Draft02.RestConfModule.NAME, Revision.of(Draft02.RestConfModule.REVISION));
+ }
+
+ public Entry<SchemaInferenceStack, ContainerSchemaNode> getRestconfModuleErrorsSchemaNode() {
+ checkPreconditions();
+
+ final var schema = globalSchema;
+ final var namespace = QNameModule.create(XMLNamespace.of(Draft02.RestConfModule.NAMESPACE),
+ Revision.of(Draft02.RestConfModule.REVISION));
+ if (schema.findModule(namespace).isEmpty()) {
+ return null;
+ }
+
+ final var stack = SchemaInferenceStack.of(globalSchema);
+ stack.enterGrouping(RestConfModule.ERRORS_QNAME);
+ final var stmt = stack.enterSchemaTree(RestConfModule.ERRORS_QNAME);
+ verify(stmt instanceof ContainerSchemaNode, "Unexpected statement %s", stmt);
+ return Map.entry(stack, (ContainerSchemaNode) stmt);
+ }
+
+ public DataSchemaNode getRestconfModuleRestConfSchemaNode(final Module inRestconfModule,
+ final String schemaNodeName) {
+ Module restconfModule = inRestconfModule;
+ if (restconfModule == null) {
+ restconfModule = getRestconfModule();
+ }
+
+ if (restconfModule == null) {
+ return null;
+ }
+
+ final Collection<? extends GroupingDefinition> groupings = restconfModule.getGroupings();
+ final Iterable<? extends GroupingDefinition> filteredGroups = Iterables.filter(groupings,
+ g -> RestConfModule.RESTCONF_GROUPING_SCHEMA_NODE.equals(g.getQName().getLocalName()));
+ final GroupingDefinition restconfGrouping = Iterables.getFirst(filteredGroups, null);
+
+ final var instanceDataChildrenByName = findInstanceDataChildrenByName(restconfGrouping,
+ RestConfModule.RESTCONF_CONTAINER_SCHEMA_NODE);
+ final DataSchemaNode restconfContainer = getFirst(instanceDataChildrenByName);
+
+ if (RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE.equals(schemaNodeName)) {
+ final var instances = findInstanceDataChildrenByName(
+ (DataNodeContainer) restconfContainer, RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
+ return getFirst(instances);
+ } else if (RestConfModule.STREAM_LIST_SCHEMA_NODE.equals(schemaNodeName)) {
+ var instances = findInstanceDataChildrenByName(
+ (DataNodeContainer) restconfContainer, RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
+ final DataSchemaNode modules = getFirst(instances);
+ instances = findInstanceDataChildrenByName((DataNodeContainer) modules,
+ RestConfModule.STREAM_LIST_SCHEMA_NODE);
+ return getFirst(instances);
+ } else if (RestConfModule.MODULES_CONTAINER_SCHEMA_NODE.equals(schemaNodeName)) {
+ final var instances = findInstanceDataChildrenByName(
+ (DataNodeContainer) restconfContainer, RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
+ return getFirst(instances);
+ } else if (RestConfModule.MODULE_LIST_SCHEMA_NODE.equals(schemaNodeName)) {
+ var instances = findInstanceDataChildrenByName(
+ (DataNodeContainer) restconfContainer, RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
+ final DataSchemaNode modules = getFirst(instances);
+ instances = findInstanceDataChildrenByName((DataNodeContainer) modules,
+ RestConfModule.MODULE_LIST_SCHEMA_NODE);
+ return getFirst(instances);
+ } else if (RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE.equals(schemaNodeName)) {
+ final var instances = findInstanceDataChildrenByName(
+ (DataNodeContainer) restconfContainer, RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
+ return getFirst(instances);
+ }
+
+ return null;
+ }
+
+ public static @Nullable DataSchemaNode getFirst(final List<FoundChild> children) {
+ return children.isEmpty() ? null : children.get(0).child;
+ }
+
+ private static DataSchemaNode childByQName(final ChoiceSchemaNode container, final QName name) {
+ for (final CaseSchemaNode caze : container.getCases()) {
+ final DataSchemaNode ret = childByQName(caze, name);
+ if (ret != null) {
+ return ret;
+ }
+ }
+
+ return null;
+ }
+
+ private static DataSchemaNode childByQName(final CaseSchemaNode container, final QName name) {
+ return container.dataChildByName(name);
+ }
+
+ private static DataSchemaNode childByQName(final ContainerSchemaNode container, final QName name) {
+ return dataNodeChildByQName(container, name);
+ }
+
+ private static DataSchemaNode childByQName(final ListSchemaNode container, final QName name) {
+ return dataNodeChildByQName(container, name);
+ }
+
+ private static DataSchemaNode childByQName(final Module container, final QName name) {
+ return dataNodeChildByQName(container, name);
+ }
+
+ private static DataSchemaNode childByQName(final DataSchemaNode container, final QName name) {
+ return null;
+ }
+
+
+ private static DataSchemaNode childByQName(final Object container, final QName name) {
+ if (container instanceof CaseSchemaNode) {
+ return childByQName((CaseSchemaNode) container, name);
+ } else if (container instanceof ChoiceSchemaNode) {
+ return childByQName((ChoiceSchemaNode) container, name);
+ } else if (container instanceof ContainerSchemaNode) {
+ return childByQName((ContainerSchemaNode) container, name);
+ } else if (container instanceof ListSchemaNode) {
+ return childByQName((ListSchemaNode) container, name);
+ } else if (container instanceof DataSchemaNode) {
+ return childByQName((DataSchemaNode) container, name);
+ } else if (container instanceof Module) {
+ return childByQName((Module) container, name);
+ } else {
+ throw new IllegalArgumentException("Unhandled parameter types: "
+ + Arrays.asList(container, name).toString());
+ }
+ }
+
+ private static DataSchemaNode dataNodeChildByQName(final DataNodeContainer container, final QName name) {
+ final DataSchemaNode ret = container.dataChildByName(name);
+ if (ret == null) {
+ for (final DataSchemaNode node : container.getChildNodes()) {
+ if (node instanceof ChoiceSchemaNode) {
+ final ChoiceSchemaNode choiceNode = (ChoiceSchemaNode) node;
+ final DataSchemaNode childByQName = childByQName(choiceNode, name);
+ if (childByQName != null) {
+ return childByQName;
+ }
+ }
+ }
+ }
+ return ret;
+ }
+
+ private String toUriString(final Object object, final LeafSchemaNode leafNode, final DOMMountPoint mount)
+ throws UnsupportedEncodingException {
+ final IllegalArgumentCodec<Object, Object> codec = RestCodec.from(leafNode.getType(), mount, this);
+ return object == null ? "" : URLEncoder.encode(codec.serialize(object).toString(), StandardCharsets.UTF_8);
+ }
+
+ @SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", justification = "Unrecognised NullableDecl")
+ private InstanceIdentifierContext collectPathArguments(final InstanceIdentifierBuilder builder,
+ final Deque<QName> schemaPath, final List<String> strings, final DataNodeContainer parentNode,
+ final DOMMountPoint mountPoint, final boolean returnJustMountPoint) {
+ requireNonNull(strings);
+
+ if (parentNode == null) {
+ return null;
+ }
+
+ if (strings.isEmpty()) {
+ return createContext(builder.build(), (DataSchemaNode) parentNode, mountPoint,
+ mountPoint != null ? getModelContext(mountPoint) : globalSchema);
+ }
+
+ final String head = strings.iterator().next();
+
+ if (head.isEmpty()) {
+ final List<String> remaining = strings.subList(1, strings.size());
+ return collectPathArguments(builder, schemaPath, remaining, parentNode, mountPoint, returnJustMountPoint);
+ }
+
+ final String nodeName = toNodeName(head);
+ final String moduleName = toModuleName(head);
+
+ DataSchemaNode targetNode = null;
+ if (!Strings.isNullOrEmpty(moduleName)) {
+ if (MOUNT_MODULE.equals(moduleName) && MOUNT_NODE.equals(nodeName)) {
+ if (mountPoint != null) {
+ throw new RestconfDocumentedException("Restconf supports just one mount point in URI.",
+ ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED);
+ }
+
+ if (mountService == null) {
+ throw new RestconfDocumentedException(
+ "MountService was not found. Finding behind mount points does not work.",
+ ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED);
+ }
+
+ final YangInstanceIdentifier partialPath = dataNormalizer.toNormalized(builder.build()).getKey();
+ final Optional<DOMMountPoint> mountOpt = mountService.getMountPoint(partialPath);
+ if (mountOpt.isEmpty()) {
+ LOG.debug("Instance identifier to missing mount point: {}", partialPath);
+ throw new RestconfDocumentedException("Mount point does not exist.", ErrorType.PROTOCOL,
+ ErrorTag.DATA_MISSING);
+ }
+ final DOMMountPoint mount = mountOpt.get();
+
+ final EffectiveModelContext mountPointSchema = getModelContext(mount);
+ if (mountPointSchema == null) {
+ throw new RestconfDocumentedException("Mount point does not contain any schema with modules.",
+ ErrorType.APPLICATION, ErrorTag.UNKNOWN_ELEMENT);
+ }
+
+ if (returnJustMountPoint || strings.size() == 1) {
+ return InstanceIdentifierContext.ofMountPointRoot(mount, mountPointSchema);
+ }
+
+ final String moduleNameBehindMountPoint = toModuleName(strings.get(1));
+ if (moduleNameBehindMountPoint == null) {
+ throw new RestconfDocumentedException(
+ "First node after mount point in URI has to be in format \"moduleName:nodeName\"",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ final Iterator<? extends Module> it = mountPointSchema.findModules(moduleNameBehindMountPoint)
+ .iterator();
+ if (!it.hasNext()) {
+ throw new RestconfDocumentedException("\"" + moduleNameBehindMountPoint
+ + "\" module does not exist in mount point.", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
+ }
+
+ final List<String> subList = strings.subList(1, strings.size());
+ return collectPathArguments(YangInstanceIdentifier.builder(), new ArrayDeque<>(), subList, it.next(),
+ mount, returnJustMountPoint);
+ }
+
+ Module module = null;
+ if (mountPoint == null) {
+ checkPreconditions();
+ module = globalSchema.findModules(moduleName).stream().findFirst().orElse(null);
+ if (module == null) {
+ throw new RestconfDocumentedException("\"" + moduleName + "\" module does not exist.",
+ ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
+ }
+ } else {
+ final EffectiveModelContext schemaContext = getModelContext(mountPoint);
+ if (schemaContext != null) {
+ module = schemaContext.findModules(moduleName).stream().findFirst().orElse(null);
+ } else {
+ module = null;
+ }
+ if (module == null) {
+ throw new RestconfDocumentedException("\"" + moduleName
+ + "\" module does not exist in mount point.", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
+ }
+ }
+
+ final var found = findInstanceDataChildByNameAndNamespace(parentNode, nodeName, module.getNamespace());
+ if (found == null) {
+ if (parentNode instanceof Module) {
+ final RpcDefinition rpc;
+ if (mountPoint == null) {
+ rpc = getRpcDefinition(head, module.getRevision());
+ } else {
+ rpc = getRpcDefinition(module, toNodeName(head));
+ }
+ if (rpc != null) {
+ final var ctx = mountPoint == null ? globalSchema : getModelContext(mountPoint);
+ return InstanceIdentifierContext.ofRpcInput(ctx, rpc, mountPoint);
+ }
+ }
+ throw new RestconfDocumentedException("URI has bad format. Possible reasons:\n" + " 1. \"" + head
+ + "\" was not found in parent data node.\n" + " 2. \"" + head
+ + "\" is behind mount point. Then it should be in format \"/" + MOUNT + "/" + head + "\".",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ targetNode = found.child;
+ schemaPath.addAll(found.intermediate);
+ schemaPath.add(targetNode.getQName());
+ } else {
+ final var potentialSchemaNodes = findInstanceDataChildrenByName(parentNode, nodeName);
+ if (potentialSchemaNodes.size() > 1) {
+ final StringBuilder strBuilder = new StringBuilder();
+ for (var potentialNodeSchema : potentialSchemaNodes) {
+ strBuilder.append(" ").append(potentialNodeSchema.child.getQName().getNamespace()).append("\n");
+ }
+
+ throw new RestconfDocumentedException(
+ "URI has bad format. Node \""
+ + nodeName + "\" is added as augment from more than one module. "
+ + "Therefore the node must have module name "
+ + "and it has to be in format \"moduleName:nodeName\"."
+ + "\nThe node is added as augment from modules with namespaces:\n"
+ + strBuilder.toString(), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ if (potentialSchemaNodes.isEmpty()) {
+ throw new RestconfDocumentedException("\"" + nodeName + "\" in URI was not found in parent data node",
+ ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
+ }
+
+ final var found = potentialSchemaNodes.get(0);
+ targetNode = found.child;
+ schemaPath.addAll(found.intermediate);
+ schemaPath.add(targetNode.getQName());
+ }
+
+ if (!isListOrContainer(targetNode)) {
+ throw new RestconfDocumentedException("URI has bad format. Node \"" + head
+ + "\" must be Container or List yang type.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ int consumed = 1;
+ if (targetNode instanceof ListSchemaNode) {
+ final ListSchemaNode listNode = (ListSchemaNode) targetNode;
+ final int keysSize = listNode.getKeyDefinition().size();
+ if (strings.size() - consumed < keysSize) {
+ throw new RestconfDocumentedException("Missing key for list \"" + listNode.getQName().getLocalName()
+ + "\".", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING);
+ }
+
+ final List<String> uriKeyValues = strings.subList(consumed, consumed + keysSize);
+ final HashMap<QName, Object> keyValues = new HashMap<>();
+ int index = 0;
+ for (final QName key : listNode.getKeyDefinition()) {
+ final String uriKeyValue = uriKeyValues.get(index);
+ if (uriKeyValue.equals(NULL_VALUE)) {
+ throw new RestconfDocumentedException("URI has bad format. List \""
+ + listNode.getQName().getLocalName() + "\" cannot contain \"null\" value as a key.",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ final var keyChild = listNode.getDataChildByName(key);
+ schemaPath.addLast(keyChild.getQName());
+ addKeyValue(keyValues, schemaPath, keyChild, uriKeyValue, mountPoint);
+ schemaPath.removeLast();
+ index++;
+ }
+
+ consumed = consumed + index;
+ builder.nodeWithKey(targetNode.getQName(), keyValues);
+ } else {
+ builder.node(targetNode.getQName());
+ }
+
+ if (targetNode instanceof DataNodeContainer) {
+ final List<String> remaining = strings.subList(consumed, strings.size());
+ return collectPathArguments(builder, schemaPath, remaining, (DataNodeContainer) targetNode, mountPoint,
+ returnJustMountPoint);
+ }
+
+ return createContext(builder.build(), targetNode, mountPoint,
+ mountPoint != null ? getModelContext(mountPoint) : globalSchema);
+ }
+
+ private static InstanceIdentifierContext createContext(final YangInstanceIdentifier instance,
+ final DataSchemaNode dataSchemaNode, final DOMMountPoint mountPoint,
+ final EffectiveModelContext schemaContext) {
+ final var normalized = new DataNormalizer(schemaContext).toNormalized(instance);
+ return InstanceIdentifierContext.ofPath(normalized.getValue(), dataSchemaNode, normalized.getKey(), mountPoint);
+ }
+
+ public static @Nullable FoundChild findInstanceDataChildByNameAndNamespace(final DataNodeContainer container,
+ final String name, final XMLNamespace namespace) {
+ requireNonNull(namespace);
+
+ for (var node : findInstanceDataChildrenByName(container, name)) {
+ if (namespace.equals(node.child.getQName().getNamespace())) {
+ return node;
+ }
+ }
+ return null;
+ }
+
+ public static List<FoundChild> findInstanceDataChildrenByName(final DataNodeContainer container,
+ final String name) {
+ final List<FoundChild> instantiatedDataNodeContainers = new ArrayList<>();
+ collectInstanceDataNodeContainers(instantiatedDataNodeContainers, requireNonNull(container),
+ requireNonNull(name), List.of());
+ return instantiatedDataNodeContainers;
+ }
+
+ private static void collectInstanceDataNodeContainers(final List<FoundChild> potentialSchemaNodes,
+ final DataNodeContainer container, final String name, final List<QName> intermediate) {
+ // We perform two iterations to retain breadth-first ordering
+ for (var child : container.getChildNodes()) {
+ if (name.equals(child.getQName().getLocalName()) && isInstantiatedDataSchema(child)) {
+ potentialSchemaNodes.add(new FoundChild(child, intermediate));
+ }
+ }
+
+ for (var child : container.getChildNodes()) {
+ if (child instanceof ChoiceSchemaNode) {
+ for (var caze : ((ChoiceSchemaNode) child).getCases()) {
+ collectInstanceDataNodeContainers(potentialSchemaNodes, caze, name,
+ ImmutableList.<QName>builderWithExpectedSize(intermediate.size() + 2)
+ .addAll(intermediate).add(child.getQName()).add(caze.getQName())
+ .build());
+ }
+ }
+ }
+ }
+
+ public static boolean isInstantiatedDataSchema(final DataSchemaNode node) {
+ return node instanceof LeafSchemaNode || node instanceof LeafListSchemaNode
+ || node instanceof ContainerSchemaNode || node instanceof ListSchemaNode
+ || node instanceof AnyxmlSchemaNode;
+ }
+
+ private void addKeyValue(final HashMap<QName, Object> map, final Deque<QName> schemaPath, final DataSchemaNode node,
+ final String uriValue, final DOMMountPoint mountPoint) {
+ checkArgument(node instanceof LeafSchemaNode);
+
+ final EffectiveModelContext schemaContext = mountPoint == null ? globalSchema : getModelContext(mountPoint);
+ final String urlDecoded = urlPathArgDecode(requireNonNull(uriValue));
+ TypeDefinition<?> typedef = ((LeafSchemaNode) node).getType();
+ final TypeDefinition<?> baseType = RestUtil.resolveBaseTypeFrom(typedef);
+ if (baseType instanceof LeafrefTypeDefinition) {
+ final var stack = SchemaInferenceStack.of(schemaContext);
+ schemaPath.forEach(stack::enterSchemaTree);
+ typedef = stack.resolveLeafref((LeafrefTypeDefinition) baseType);
+ }
+ final IllegalArgumentCodec<Object, Object> codec = RestCodec.from(typedef, mountPoint, this);
+ Object decoded = codec.deserialize(urlDecoded);
+ String additionalInfo = "";
+ if (decoded == null) {
+ if (typedef instanceof IdentityrefTypeDefinition) {
+ decoded = toQName(schemaContext, urlDecoded);
+ additionalInfo =
+ "For key which is of type identityref it should be in format module_name:identity_name.";
+ }
+ }
+
+ if (decoded == null) {
+ throw new RestconfDocumentedException(uriValue + " from URI can't be resolved. " + additionalInfo,
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ map.put(node.getQName(), decoded);
+ }
+
+ private static String toModuleName(final String str) {
+ final int idx = str.indexOf(':');
+ if (idx == -1) {
+ return null;
+ }
+
+ // Make sure there is only one occurrence
+ if (str.indexOf(':', idx + 1) != -1) {
+ return null;
+ }
+
+ return str.substring(0, idx);
+ }
+
+ private static String toNodeName(final String str) {
+ final int idx = str.indexOf(':');
+ if (idx == -1) {
+ return str;
+ }
+
+ // Make sure there is only one occurrence
+ if (str.indexOf(':', idx + 1) != -1) {
+ return str;
+ }
+
+ return str.substring(idx + 1);
+ }
+
+ private QName toQName(final EffectiveModelContext schemaContext, final String name,
+ final Optional<Revision> revisionDate) {
+ checkPreconditions();
+ final String module = toModuleName(name);
+ final String node = toNodeName(name);
+ final Module m = schemaContext.findModule(module, revisionDate).orElse(null);
+ return m == null ? null : QName.create(m.getQNameModule(), node);
+ }
+
+ private QName toQName(final EffectiveModelContext schemaContext, final String name) {
+ checkPreconditions();
+ final String module = toModuleName(name);
+ final String node = toNodeName(name);
+ final Collection<? extends Module> modules = schemaContext.findModules(module);
+ return modules.isEmpty() ? null : QName.create(modules.iterator().next().getQNameModule(), node);
+ }
+
+ private static boolean isListOrContainer(final DataSchemaNode node) {
+ return node instanceof ListSchemaNode || node instanceof ContainerSchemaNode;
+ }
+
+ public RpcDefinition getRpcDefinition(final String name, final Optional<Revision> revisionDate) {
+ final QName validName = toQName(globalSchema, name, revisionDate);
+ return validName == null ? null : qnameToRpc.get().get(validName);
+ }
+
+ public RpcDefinition getRpcDefinition(final String name) {
+ final QName validName = toQName(globalSchema, name);
+ return validName == null ? null : qnameToRpc.get().get(validName);
+ }
+
+ private static RpcDefinition getRpcDefinition(final Module module, final String rpcName) {
+ final QName rpcQName = QName.create(module.getQNameModule(), rpcName);
+ for (final RpcDefinition rpcDefinition : module.getRpcs()) {
+ if (rpcQName.equals(rpcDefinition.getQName())) {
+ return rpcDefinition;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void onModelContextUpdated(final EffectiveModelContext context) {
+ if (context != null) {
+ final Collection<? extends RpcDefinition> defs = context.getOperations();
+ final Map<QName, RpcDefinition> newMap = new HashMap<>(defs.size());
+
+ for (final RpcDefinition operation : defs) {
+ newMap.put(operation.getQName(), operation);
+ }
+
+ // FIXME: still not completely atomic
+ qnameToRpc.set(ImmutableMap.copyOf(newMap));
+ setGlobalSchema(context);
+ }
+ }
+
+ private static List<String> urlPathArgsDecode(final Iterable<String> strings) {
+ final List<String> decodedPathArgs = new ArrayList<>();
+ for (final String pathArg : strings) {
+ final String _decode = URLDecoder.decode(pathArg, StandardCharsets.UTF_8);
+ decodedPathArgs.add(_decode);
+ }
+ return decodedPathArgs;
+ }
+
+ static String urlPathArgDecode(final String pathArg) {
+ if (pathArg == null) {
+ return null;
+ }
+ return URLDecoder.decode(pathArg, StandardCharsets.UTF_8);
+ }
+
+ private String convertToRestconfIdentifier(final PathArgument argument, final DataSchemaNode node,
+ final DOMMountPoint mount) {
+ if (argument instanceof NodeIdentifier) {
+ return convertToRestconfIdentifier((NodeIdentifier) argument, mount);
+ } else if (argument instanceof NodeIdentifierWithPredicates && node instanceof ListSchemaNode) {
+ return convertToRestconfIdentifierWithPredicates((NodeIdentifierWithPredicates) argument,
+ (ListSchemaNode) node, mount);
+ } else if (argument != null && node != null) {
+ throw new IllegalArgumentException("Conversion of generic path argument is not supported");
+ } else {
+ throw new IllegalArgumentException("Unhandled parameter types: " + Arrays.asList(argument, node));
+ }
+ }
+
+ private String convertToRestconfIdentifier(final NodeIdentifier argument, final DOMMountPoint node) {
+ return "/" + toRestconfIdentifier(argument.getNodeType(), node);
+ }
+
+ private String convertToRestconfIdentifierWithPredicates(final NodeIdentifierWithPredicates argument,
+ final ListSchemaNode node, final DOMMountPoint mount) {
+ final QName nodeType = argument.getNodeType();
+ final String nodeIdentifier = toRestconfIdentifier(nodeType, mount);
+
+ final StringBuilder builder = new StringBuilder().append('/').append(nodeIdentifier).append('/');
+
+ final List<QName> keyDefinition = node.getKeyDefinition();
+ boolean hasElements = false;
+ for (final QName key : keyDefinition) {
+ for (final DataSchemaNode listChild : node.getChildNodes()) {
+ if (listChild.getQName().equals(key)) {
+ if (!hasElements) {
+ hasElements = true;
+ } else {
+ builder.append('/');
+ }
+
+ checkState(listChild instanceof LeafSchemaNode,
+ "List key has to consist of leaves, not %s", listChild);
+
+ final Object value = argument.getValue(key);
+ try {
+ builder.append(toUriString(value, (LeafSchemaNode)listChild, mount));
+ } catch (final UnsupportedEncodingException e) {
+ LOG.error("Error parsing URI: {}", value, e);
+ return null;
+ }
+ break;
+ }
+ }
+ }
+
+ return builder.toString();
+ }
+
+ public YangInstanceIdentifier toXpathRepresentation(final YangInstanceIdentifier instanceIdentifier) {
+ if (dataNormalizer == null) {
+ throw new RestconfDocumentedException("Data normalizer isn't set. Normalization isn't possible");
+ }
+
+ try {
+ return dataNormalizer.toLegacy(instanceIdentifier);
+ } catch (final DataNormalizationException e) {
+ throw new RestconfDocumentedException("Data normalizer failed. Normalization isn't possible", e);
+ }
+ }
+
+ public boolean isNodeMixin(final YangInstanceIdentifier path) {
+ final DataNormalizationOperation<?> operation;
+ try {
+ operation = dataNormalizer.getOperation(path);
+ } catch (final DataNormalizationException e) {
+ throw new RestconfDocumentedException("Data normalizer failed. Normalization isn't possible", e);
+ }
+ return operation.isMixin();
+ }
+
+ private static EffectiveModelContext getModelContext(final DOMMountPoint mountPoint) {
+ return mountPoint.getService(DOMSchemaService.class)
+ .flatMap(svc -> Optional.ofNullable(svc.getGlobalContext()))
+ .orElse(null);
+ }
+
+ public static final class FoundChild {
+ // Intermediate schema tree children, usually empty
+ public final @NonNull List<QName> intermediate;
+ public final @NonNull DataSchemaNode child;
+
+ private FoundChild(final DataSchemaNode child, final List<QName> intermediate) {
+ this.child = requireNonNull(child);
+ this.intermediate = requireNonNull(intermediate);
+ }
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/DataNormalizationException.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/DataNormalizationException.java
new file mode 100644
index 0000000..b9e7214
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/DataNormalizationException.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+final class DataNormalizationException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ DataNormalizationException(final String message) {
+ super(message);
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/DataNormalizationOperation.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/DataNormalizationOperation.java
new file mode 100644
index 0000000..eb41c78
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/DataNormalizationOperation.java
@@ -0,0 +1,526 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import static com.google.common.base.Verify.verifyNotNull;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.concepts.Identifiable;
+import org.opendaylight.yangtools.yang.common.Empty;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
+import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
+import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ContainerLike;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.util.EffectiveAugmentationSchema;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+
+abstract class DataNormalizationOperation<T extends PathArgument> implements Identifiable<T> {
+ private final T identifier;
+
+ DataNormalizationOperation(final T identifier) {
+ this.identifier = identifier;
+ }
+
+ static DataNormalizationOperation<?> from(final EffectiveModelContext ctx) {
+ return new ContainerNormalization(ctx);
+ }
+
+ @Override
+ public T getIdentifier() {
+ return identifier;
+ }
+
+ boolean isMixin() {
+ return false;
+ }
+
+ Set<QName> getQNameIdentifiers() {
+ return ImmutableSet.of(identifier.getNodeType());
+ }
+
+ abstract DataNormalizationOperation<?> getChild(PathArgument child) throws DataNormalizationException;
+
+ abstract DataNormalizationOperation<?> getChild(QName child) throws DataNormalizationException;
+
+ abstract DataNormalizationOperation<?> enterChild(QName child, SchemaInferenceStack stack)
+ throws DataNormalizationException;
+
+ abstract DataNormalizationOperation<?> enterChild(PathArgument child, SchemaInferenceStack stack)
+ throws DataNormalizationException;
+
+ void pushToStack(final SchemaInferenceStack stack) {
+ // Accurate for most subclasses
+ stack.enterSchemaTree(getIdentifier().getNodeType());
+ }
+
+ private abstract static class SimpleTypeNormalization<T extends PathArgument>
+ extends DataNormalizationOperation<T> {
+ SimpleTypeNormalization(final T identifier) {
+ super(identifier);
+ }
+
+ @Override
+ final DataNormalizationOperation<?> getChild(final PathArgument child) {
+ return null;
+ }
+
+ @Override
+ final DataNormalizationOperation<?> getChild(final QName child) {
+ return null;
+ }
+
+ @Override
+ final DataNormalizationOperation<?> enterChild(final QName child, final SchemaInferenceStack stack) {
+ return null;
+ }
+
+ @Override
+ final DataNormalizationOperation<?> enterChild(final PathArgument child, final SchemaInferenceStack stack) {
+ return null;
+ }
+ }
+
+ private static final class LeafNormalization extends SimpleTypeNormalization<NodeIdentifier> {
+ LeafNormalization(final LeafSchemaNode potential) {
+ super(new NodeIdentifier(potential.getQName()));
+ }
+ }
+
+ private static final class LeafListEntryNormalization extends SimpleTypeNormalization<NodeWithValue> {
+ LeafListEntryNormalization(final LeafListSchemaNode potential) {
+ super(new NodeWithValue<>(potential.getQName(), Empty.value()));
+ }
+
+ @Override
+ protected void pushToStack(final SchemaInferenceStack stack) {
+ // No-op
+ }
+ }
+
+ private abstract static class DataContainerNormalizationOperation<T extends PathArgument>
+ extends DataNormalizationOperation<T> {
+ private final DataNodeContainer schema;
+ private final Map<QName, DataNormalizationOperation<?>> byQName = new ConcurrentHashMap<>();
+ private final Map<PathArgument, DataNormalizationOperation<?>> byArg = new ConcurrentHashMap<>();
+
+ DataContainerNormalizationOperation(final T identifier, final DataNodeContainer schema) {
+ super(identifier);
+ this.schema = schema;
+ }
+
+ @Override
+ DataNormalizationOperation<?> getChild(final PathArgument child) throws DataNormalizationException {
+ DataNormalizationOperation<?> potential = byArg.get(child);
+ if (potential != null) {
+ return potential;
+ }
+ potential = fromLocalSchema(child);
+ return register(potential);
+ }
+
+ @Override
+ DataNormalizationOperation<?> getChild(final QName child) throws DataNormalizationException {
+ DataNormalizationOperation<?> potential = byQName.get(child);
+ if (potential != null) {
+ return potential;
+ }
+ potential = fromLocalSchemaAndQName(schema, child);
+ return register(potential);
+ }
+
+ @Override
+ final DataNormalizationOperation<?> enterChild(final QName child, final SchemaInferenceStack stack)
+ throws DataNormalizationException {
+ return pushToStack(getChild(child), stack);
+ }
+
+ @Override
+ final DataNormalizationOperation<?> enterChild(final PathArgument child, final SchemaInferenceStack stack)
+ throws DataNormalizationException {
+ return pushToStack(getChild(child), stack);
+ }
+
+ private static DataNormalizationOperation<?> pushToStack(final DataNormalizationOperation<?> child,
+ final SchemaInferenceStack stack) {
+ if (child != null) {
+ child.pushToStack(stack);
+ }
+ return child;
+ }
+
+ private DataNormalizationOperation<?> fromLocalSchema(final PathArgument child)
+ throws DataNormalizationException {
+ if (child instanceof AugmentationIdentifier) {
+ return fromSchemaAndQNameChecked(schema, ((AugmentationIdentifier) child).getPossibleChildNames()
+ .iterator().next());
+ }
+ return fromSchemaAndQNameChecked(schema, child.getNodeType());
+ }
+
+ DataNormalizationOperation<?> fromLocalSchemaAndQName(final DataNodeContainer schema2,
+ final QName child) throws DataNormalizationException {
+ return fromSchemaAndQNameChecked(schema2, child);
+ }
+
+ private DataNormalizationOperation<?> register(final DataNormalizationOperation<?> potential) {
+ if (potential != null) {
+ byArg.put(potential.getIdentifier(), potential);
+ for (final QName qname : potential.getQNameIdentifiers()) {
+ byQName.put(qname, potential);
+ }
+ }
+ return potential;
+ }
+
+ private static DataNormalizationOperation<?> fromSchemaAndQNameChecked(final DataNodeContainer schema,
+ final QName child) throws DataNormalizationException {
+
+ final DataSchemaNode result = findChildSchemaNode(schema, child);
+ if (result == null) {
+ throw new DataNormalizationException(String.format(
+ "Supplied QName %s is not valid according to schema %s, potential children nodes: %s", child,
+ schema,schema.getChildNodes()));
+ }
+
+ // We try to look up if this node was added by augmentation
+ if (schema instanceof DataSchemaNode && result.isAugmenting()) {
+ return fromAugmentation(schema, (AugmentationTarget) schema, result);
+ }
+ return fromDataSchemaNode(result);
+ }
+ }
+
+ private static final class ListItemNormalization extends
+ DataContainerNormalizationOperation<NodeIdentifierWithPredicates> {
+ ListItemNormalization(final NodeIdentifierWithPredicates identifier, final ListSchemaNode schema) {
+ super(identifier, schema);
+ }
+
+ @Override
+ protected void pushToStack(final SchemaInferenceStack stack) {
+ // No-op
+ }
+ }
+
+ private static final class UnkeyedListItemNormalization
+ extends DataContainerNormalizationOperation<NodeIdentifier> {
+ UnkeyedListItemNormalization(final ListSchemaNode schema) {
+ super(new NodeIdentifier(schema.getQName()), schema);
+ }
+
+ @Override
+ protected void pushToStack(final SchemaInferenceStack stack) {
+ // No-op
+ }
+ }
+
+ private static final class ContainerNormalization extends DataContainerNormalizationOperation<NodeIdentifier> {
+ ContainerNormalization(final ContainerLike schema) {
+ super(new NodeIdentifier(schema.getQName()), schema);
+ }
+ }
+
+ private abstract static class MixinNormalizationOp<T extends PathArgument> extends DataNormalizationOperation<T> {
+ MixinNormalizationOp(final T identifier) {
+ super(identifier);
+ }
+
+ @Override
+ final boolean isMixin() {
+ return true;
+ }
+ }
+
+ private abstract static class ListLikeNormalizationOp<T extends PathArgument> extends MixinNormalizationOp<T> {
+ ListLikeNormalizationOp(final T identifier) {
+ super(identifier);
+ }
+
+ @Override
+ protected final DataNormalizationOperation<?> enterChild(final QName child, final SchemaInferenceStack stack)
+ throws DataNormalizationException {
+ // Stack is already pointing to the corresponding statement, now we are just working with the child
+ return getChild(child);
+ }
+
+ @Override
+ protected final DataNormalizationOperation<?> enterChild(final PathArgument child,
+ final SchemaInferenceStack stack) throws DataNormalizationException {
+ return getChild(child);
+ }
+ }
+
+ private static final class LeafListMixinNormalization extends ListLikeNormalizationOp<NodeIdentifier> {
+ private final DataNormalizationOperation<?> innerOp;
+
+ LeafListMixinNormalization(final LeafListSchemaNode potential) {
+ super(new NodeIdentifier(potential.getQName()));
+ innerOp = new LeafListEntryNormalization(potential);
+ }
+
+ @Override
+ DataNormalizationOperation<?> getChild(final PathArgument child) {
+ if (child instanceof NodeWithValue) {
+ return innerOp;
+ }
+ return null;
+ }
+
+ @Override
+ DataNormalizationOperation<?> getChild(final QName child) {
+ if (getIdentifier().getNodeType().equals(child)) {
+ return innerOp;
+ }
+ return null;
+ }
+ }
+
+ private static final class AugmentationNormalization
+ extends DataContainerNormalizationOperation<AugmentationIdentifier> {
+
+ AugmentationNormalization(final AugmentationSchemaNode augmentation, final DataNodeContainer schema) {
+ super(DataSchemaContextNode.augmentationIdentifierFrom(augmentation),
+ new EffectiveAugmentationSchema(augmentation, schema));
+ }
+
+ @Override
+ boolean isMixin() {
+ return true;
+ }
+
+ @Override
+ DataNormalizationOperation<?> fromLocalSchemaAndQName(final DataNodeContainer schema, final QName child) {
+ final DataSchemaNode result = findChildSchemaNode(schema, child);
+ if (result == null) {
+ return null;
+ }
+
+ // We try to look up if this node was added by augmentation
+ if (schema instanceof DataSchemaNode && result.isAugmenting()) {
+ return fromAugmentation(schema, (AugmentationTarget) schema, result);
+ }
+ return fromDataSchemaNode(result);
+ }
+
+ @Override
+ Set<QName> getQNameIdentifiers() {
+ return getIdentifier().getPossibleChildNames();
+ }
+
+ @Override
+ void pushToStack(final SchemaInferenceStack stack) {
+ // No-op
+ }
+ }
+
+ private static final class MapMixinNormalization extends ListLikeNormalizationOp<NodeIdentifier> {
+ private final ListItemNormalization innerNode;
+
+ MapMixinNormalization(final ListSchemaNode list) {
+ super(new NodeIdentifier(list.getQName()));
+ innerNode = new ListItemNormalization(NodeIdentifierWithPredicates.of(list.getQName()), list);
+ }
+
+ @Override
+ DataNormalizationOperation<?> getChild(final PathArgument child) {
+ if (child.getNodeType().equals(getIdentifier().getNodeType())) {
+ return innerNode;
+ }
+ return null;
+ }
+
+ @Override
+ DataNormalizationOperation<?> getChild(final QName child) {
+ if (getIdentifier().getNodeType().equals(child)) {
+ return innerNode;
+ }
+ return null;
+ }
+ }
+
+ private static final class UnkeyedListMixinNormalization extends ListLikeNormalizationOp<NodeIdentifier> {
+ private final UnkeyedListItemNormalization innerNode;
+
+ UnkeyedListMixinNormalization(final ListSchemaNode list) {
+ super(new NodeIdentifier(list.getQName()));
+ innerNode = new UnkeyedListItemNormalization(list);
+ }
+
+ @Override
+ DataNormalizationOperation<?> getChild(final PathArgument child) {
+ if (child.getNodeType().equals(getIdentifier().getNodeType())) {
+ return innerNode;
+ }
+ return null;
+ }
+
+ @Override
+ DataNormalizationOperation<?> getChild(final QName child) {
+ if (getIdentifier().getNodeType().equals(child)) {
+ return innerNode;
+ }
+ return null;
+ }
+ }
+
+ private static final class ChoiceNodeNormalization extends MixinNormalizationOp<NodeIdentifier> {
+ private final ImmutableMap<QName, DataNormalizationOperation<?>> byQName;
+ private final ImmutableMap<PathArgument, DataNormalizationOperation<?>> byArg;
+ private final ImmutableMap<DataNormalizationOperation<?>, QName> childToCase;
+
+ ChoiceNodeNormalization(final ChoiceSchemaNode schema) {
+ super(new NodeIdentifier(schema.getQName()));
+ ImmutableMap.Builder<DataNormalizationOperation<?>, QName> childToCaseBuilder = ImmutableMap.builder();
+ final ImmutableMap.Builder<QName, DataNormalizationOperation<?>> byQNameBuilder = ImmutableMap.builder();
+ final ImmutableMap.Builder<PathArgument, DataNormalizationOperation<?>> byArgBuilder =
+ ImmutableMap.builder();
+
+ for (final CaseSchemaNode caze : schema.getCases()) {
+ for (final DataSchemaNode cazeChild : caze.getChildNodes()) {
+ final DataNormalizationOperation<?> childOp = fromDataSchemaNode(cazeChild);
+ byArgBuilder.put(childOp.getIdentifier(), childOp);
+ childToCaseBuilder.put(childOp, caze.getQName());
+ for (final QName qname : childOp.getQNameIdentifiers()) {
+ byQNameBuilder.put(qname, childOp);
+ }
+ }
+ }
+ childToCase = childToCaseBuilder.build();
+ byQName = byQNameBuilder.build();
+ byArg = byArgBuilder.build();
+ }
+
+ @Override
+ DataNormalizationOperation<?> getChild(final PathArgument child) {
+ return byArg.get(child);
+ }
+
+ @Override
+ DataNormalizationOperation<?> getChild(final QName child) {
+ return byQName.get(child);
+ }
+
+ @Override
+ Set<QName> getQNameIdentifiers() {
+ return byQName.keySet();
+ }
+
+ @Override
+ DataNormalizationOperation<?> enterChild(final QName child, final SchemaInferenceStack stack) {
+ return pushToStack(getChild(child), stack);
+ }
+
+ @Override
+ DataNormalizationOperation<?> enterChild(final PathArgument child, final SchemaInferenceStack stack) {
+ return pushToStack(getChild(child), stack);
+ }
+
+ @Override
+ void pushToStack(final SchemaInferenceStack stack) {
+ stack.enterChoice(getIdentifier().getNodeType());
+ }
+
+ private DataNormalizationOperation<?> pushToStack(final DataNormalizationOperation<?> child,
+ final SchemaInferenceStack stack) {
+ if (child != null) {
+ final var caseName = verifyNotNull(childToCase.get(child), "No case statement for %s in %s", child,
+ this);
+ stack.enterSchemaTree(caseName);
+ child.pushToStack(stack);
+ }
+ return child;
+ }
+ }
+
+ private static final class AnyxmlNormalization extends SimpleTypeNormalization<NodeIdentifier> {
+ AnyxmlNormalization(final AnyxmlSchemaNode schema) {
+ super(new NodeIdentifier(schema.getQName()));
+ }
+ }
+
+ private static @Nullable DataSchemaNode findChildSchemaNode(final DataNodeContainer parent, final QName child) {
+ final DataSchemaNode potential = parent.dataChildByName(child);
+ return potential != null ? potential : findChoice(parent, child);
+ }
+
+ private static @Nullable ChoiceSchemaNode findChoice(final DataNodeContainer parent, final QName child) {
+ for (final ChoiceSchemaNode choice : Iterables.filter(parent.getChildNodes(), ChoiceSchemaNode.class)) {
+ for (final CaseSchemaNode caze : choice.getCases()) {
+ if (findChildSchemaNode(caze, child) != null) {
+ return choice;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a DataNormalizationOperation for provided child node.
+ *
+ * <p>
+ * If supplied child is added by Augmentation this operation returns
+ * a DataNormalizationOperation for augmentation,
+ * otherwise returns a DataNormalizationOperation for child as
+ * call for {@link #fromDataSchemaNode(DataSchemaNode)}.
+ */
+ private static DataNormalizationOperation<?> fromAugmentation(final DataNodeContainer parent,
+ final AugmentationTarget parentAug, final DataSchemaNode child) {
+ for (final AugmentationSchemaNode aug : parentAug.getAvailableAugmentations()) {
+ if (aug.dataChildByName(child.getQName()) != null) {
+ return new AugmentationNormalization(aug, parent);
+ }
+ }
+ return fromDataSchemaNode(child);
+ }
+
+ static DataNormalizationOperation<?> fromDataSchemaNode(final DataSchemaNode potential) {
+ if (potential instanceof ContainerSchemaNode) {
+ return new ContainerNormalization((ContainerSchemaNode) potential);
+ } else if (potential instanceof ListSchemaNode) {
+ return fromListSchemaNode((ListSchemaNode) potential);
+ } else if (potential instanceof LeafSchemaNode) {
+ return new LeafNormalization((LeafSchemaNode) potential);
+ } else if (potential instanceof ChoiceSchemaNode) {
+ return new ChoiceNodeNormalization((ChoiceSchemaNode) potential);
+ } else if (potential instanceof LeafListSchemaNode) {
+ return new LeafListMixinNormalization((LeafListSchemaNode) potential);
+ } else if (potential instanceof AnyxmlSchemaNode) {
+ return new AnyxmlNormalization((AnyxmlSchemaNode) potential);
+ }
+ return null;
+ }
+
+ private static DataNormalizationOperation<?> fromListSchemaNode(final ListSchemaNode potential) {
+ if (potential.getKeyDefinition().isEmpty()) {
+ return new UnkeyedListMixinNormalization(potential);
+ }
+ return new MapMixinNormalization(potential);
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/DataNormalizer.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/DataNormalizer.java
new file mode 100644
index 0000000..334a6c7
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/DataNormalizer.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+
+class DataNormalizer {
+ private final DataNormalizationOperation<?> operation;
+ private final EffectiveModelContext context;
+
+ DataNormalizer(final EffectiveModelContext ctx) {
+ context = requireNonNull(ctx);
+ operation = DataNormalizationOperation.from(ctx);
+ }
+
+ Entry<YangInstanceIdentifier, SchemaInferenceStack> toNormalized(final YangInstanceIdentifier legacy) {
+ List<PathArgument> normalizedArgs = new ArrayList<>();
+
+ DataNormalizationOperation<?> currentOp = operation;
+ Iterator<PathArgument> arguments = legacy.getPathArguments().iterator();
+ SchemaInferenceStack stack = SchemaInferenceStack.of(context);
+
+ try {
+ while (arguments.hasNext()) {
+ PathArgument legacyArg = arguments.next();
+ currentOp = currentOp.enterChild(legacyArg, stack);
+ checkArgument(currentOp != null,
+ "Legacy Instance Identifier %s is not correct. Normalized Instance Identifier so far %s",
+ legacy, normalizedArgs);
+ while (currentOp.isMixin()) {
+ normalizedArgs.add(currentOp.getIdentifier());
+ currentOp = currentOp.enterChild(legacyArg.getNodeType(), stack);
+ }
+ normalizedArgs.add(legacyArg);
+ }
+ } catch (DataNormalizationException e) {
+ throw new IllegalArgumentException("Failed to normalize path " + legacy, e);
+ }
+
+ return Map.entry(YangInstanceIdentifier.create(normalizedArgs), stack);
+ }
+
+ DataNormalizationOperation<?> getOperation(final YangInstanceIdentifier legacy)
+ throws DataNormalizationException {
+ DataNormalizationOperation<?> currentOp = operation;
+
+ for (PathArgument pathArgument : legacy.getPathArguments()) {
+ currentOp = currentOp.getChild(pathArgument);
+ }
+ return currentOp;
+ }
+
+ YangInstanceIdentifier toLegacy(final YangInstanceIdentifier normalized) throws DataNormalizationException {
+ ImmutableList.Builder<PathArgument> legacyArgs = ImmutableList.builder();
+ DataNormalizationOperation<?> currentOp = operation;
+ for (PathArgument normalizedArg : normalized.getPathArguments()) {
+ currentOp = currentOp.getChild(normalizedArg);
+ if (!currentOp.isMixin()) {
+ legacyArgs.add(normalizedArg);
+ }
+ }
+ return YangInstanceIdentifier.create(legacyArgs.build());
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/JSONRestconfServiceImpl.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/JSONRestconfServiceImpl.java
new file mode 100644
index 0000000..296fd91
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/JSONRestconfServiceImpl.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Optional;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.UriInfo;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.netconf.sal.rest.impl.JsonNormalizedNodeBodyReader;
+import org.opendaylight.netconf.sal.rest.impl.JsonToPatchBodyReader;
+import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext;
+import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeJsonBodyWriter;
+import org.opendaylight.netconf.sal.rest.impl.PatchJsonBodyWriter;
+import org.opendaylight.netconf.sal.restconf.api.JSONRestconfService;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+import org.opendaylight.restconf.common.util.SimpleUriInfo;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.OperationFailedException;
+import org.opendaylight.yangtools.yang.common.RpcError;
+import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of the JSONRestconfService interface using the restconf Draft02 implementation.
+ *
+ * @author Thomas Pantelis
+ * @deprecated Replaced by {JSONRestconfServiceRfc8040Impl from restconf-nb-rfc8040
+ */
+@Singleton
+@Deprecated
+public class JSONRestconfServiceImpl implements JSONRestconfService {
+ private static final Logger LOG = LoggerFactory.getLogger(JSONRestconfServiceImpl.class);
+
+ private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];
+
+ private final ControllerContext controllerContext;
+ private final RestconfService restconfService;
+
+ @Inject
+ public JSONRestconfServiceImpl(final ControllerContext controllerContext, final RestconfImpl restconfService) {
+ this.controllerContext = controllerContext;
+ this.restconfService = restconfService;
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public void put(final String uriPath, final String payload) throws OperationFailedException {
+ requireNonNull(payload, "payload can't be null");
+
+ LOG.debug("put: uriPath: {}, payload: {}", uriPath, payload);
+
+ final InputStream entityStream = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8));
+ final NormalizedNodeContext context = JsonNormalizedNodeBodyReader.readFrom(uriPath, entityStream, false,
+ controllerContext);
+
+ LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
+ LOG.debug("Parsed NormalizedNode: {}", context.getData());
+
+ try {
+ restconfService.updateConfigurationData(uriPath, context, new SimpleUriInfo(uriPath));
+ } catch (final Exception e) {
+ propagateExceptionAs(uriPath, e, "PUT");
+ }
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public void post(final String uriPath, final String payload) throws OperationFailedException {
+ requireNonNull(payload, "payload can't be null");
+
+ LOG.debug("post: uriPath: {}, payload: {}", uriPath, payload);
+
+ final InputStream entityStream = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8));
+ final NormalizedNodeContext context = JsonNormalizedNodeBodyReader.readFrom(uriPath, entityStream, true,
+ controllerContext);
+
+ LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
+ LOG.debug("Parsed NormalizedNode: {}", context.getData());
+
+ try {
+ restconfService.createConfigurationData(uriPath, context, new SimpleUriInfo(uriPath));
+ } catch (final Exception e) {
+ propagateExceptionAs(uriPath, e, "POST");
+ }
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public void delete(final String uriPath) throws OperationFailedException {
+ LOG.debug("delete: uriPath: {}", uriPath);
+
+ try {
+ restconfService.deleteConfigurationData(uriPath);
+ } catch (final Exception e) {
+ propagateExceptionAs(uriPath, e, "DELETE");
+ }
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public Optional<String> get(final String uriPath, final LogicalDatastoreType datastoreType)
+ throws OperationFailedException {
+ LOG.debug("get: uriPath: {}", uriPath);
+
+ try {
+ NormalizedNodeContext readData;
+ final SimpleUriInfo uriInfo = new SimpleUriInfo(uriPath);
+ if (datastoreType == LogicalDatastoreType.CONFIGURATION) {
+ readData = restconfService.readConfigurationData(uriPath, uriInfo);
+ } else {
+ readData = restconfService.readOperationalData(uriPath, uriInfo);
+ }
+
+ final Optional<String> result = Optional.of(toJson(readData));
+
+ LOG.debug("get returning: {}", result.get());
+
+ return result;
+ } catch (final Exception e) {
+ if (!isDataMissing(e)) {
+ propagateExceptionAs(uriPath, e, "GET");
+ }
+
+ LOG.debug("Data missing - returning absent");
+ return Optional.empty();
+ }
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @SuppressFBWarnings(value = "NP_NULL_PARAM_DEREF", justification = "Unrecognised NullableDecl")
+ @Override
+ public Optional<String> invokeRpc(final String uriPath, final Optional<String> input)
+ throws OperationFailedException {
+ requireNonNull(uriPath, "uriPath can't be null");
+
+ final String actualInput = input.isPresent() ? input.get() : null;
+
+ LOG.debug("invokeRpc: uriPath: {}, input: {}", uriPath, actualInput);
+
+ String output = null;
+ try {
+ NormalizedNodeContext outputContext;
+ if (actualInput != null) {
+ final InputStream entityStream = new ByteArrayInputStream(actualInput.getBytes(StandardCharsets.UTF_8));
+ final NormalizedNodeContext inputContext =
+ JsonNormalizedNodeBodyReader.readFrom(uriPath, entityStream, true, controllerContext);
+
+ LOG.debug("Parsed YangInstanceIdentifier: {}", inputContext.getInstanceIdentifierContext()
+ .getInstanceIdentifier());
+ LOG.debug("Parsed NormalizedNode: {}", inputContext.getData());
+
+ outputContext = restconfService.invokeRpc(uriPath, inputContext, null);
+ } else {
+ outputContext = restconfService.invokeRpc(uriPath, null, null);
+ }
+
+ if (outputContext.getData() != null) {
+ output = toJson(outputContext);
+ }
+ } catch (final RuntimeException | IOException e) {
+ propagateExceptionAs(uriPath, e, "RPC");
+ }
+
+ return Optional.ofNullable(output);
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public Optional<String> patch(final String uriPath, final String payload)
+ throws OperationFailedException {
+
+ String output = null;
+ requireNonNull(payload, "payload can't be null");
+
+ LOG.debug("patch: uriPath: {}, payload: {}", uriPath, payload);
+
+ final InputStream entityStream = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8));
+
+ JsonToPatchBodyReader jsonToPatchBodyReader = new JsonToPatchBodyReader(controllerContext);
+ final PatchContext context = jsonToPatchBodyReader.readFrom(uriPath, entityStream);
+
+ LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
+ LOG.debug("Parsed NormalizedNode: {}", context.getData());
+
+ try {
+ PatchStatusContext patchStatusContext = restconfService
+ .patchConfigurationData(context, new SimpleUriInfo(uriPath));
+ output = toJson(patchStatusContext);
+ } catch (final Exception e) {
+ propagateExceptionAs(uriPath, e, "PATCH");
+ }
+ return Optional.ofNullable(output);
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public Optional<String> subscribeToStream(final String identifier, final MultivaluedMap<String, String> params)
+ throws OperationFailedException {
+ //Note: We use http://127.0.0.1 because the Uri parser requires something there though it does nothing
+ String uri = new StringBuilder("http://127.0.0.1:8081/restconf/streams/stream/").append(identifier).toString();
+ MultivaluedMap queryParams = params != null ? params : new MultivaluedHashMap<String, String>();
+ UriInfo uriInfo = new SimpleUriInfo(uri, queryParams);
+
+ String jsonRes = null;
+ try {
+ NormalizedNodeContext res = restconfService.subscribeToStream(identifier, uriInfo);
+ jsonRes = toJson(res);
+ } catch (final Exception e) {
+ propagateExceptionAs(identifier, e, "RPC");
+ }
+
+ return Optional.ofNullable(jsonRes);
+ }
+
+ private static String toJson(final PatchStatusContext patchStatusContext) throws IOException {
+ final PatchJsonBodyWriter writer = new PatchJsonBodyWriter();
+ final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ writer.writeTo(patchStatusContext, PatchStatusContext.class, null, EMPTY_ANNOTATIONS,
+ MediaType.APPLICATION_JSON_TYPE, null, outputStream);
+ return outputStream.toString(StandardCharsets.UTF_8);
+ }
+
+ private static String toJson(final NormalizedNodeContext readData) throws IOException {
+ final NormalizedNodeJsonBodyWriter writer = new NormalizedNodeJsonBodyWriter();
+ final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ writer.writeTo(readData, NormalizedNodeContext.class, null, EMPTY_ANNOTATIONS,
+ MediaType.APPLICATION_JSON_TYPE, null, outputStream);
+ return outputStream.toString(StandardCharsets.UTF_8);
+ }
+
+ private static boolean isDataMissing(final Exception exception) {
+ boolean dataMissing = false;
+ if (exception instanceof RestconfDocumentedException) {
+ final RestconfDocumentedException rde = (RestconfDocumentedException)exception;
+ if (!rde.getErrors().isEmpty()) {
+ if (rde.getErrors().get(0).getErrorTag() == ErrorTag.DATA_MISSING) {
+ dataMissing = true;
+ }
+ }
+ }
+
+ return dataMissing;
+ }
+
+ private static void propagateExceptionAs(final String uriPath, final Exception exception, final String operation)
+ throws OperationFailedException {
+ LOG.debug("Error for uriPath: {}", uriPath, exception);
+
+ if (exception instanceof RestconfDocumentedException) {
+ throw new OperationFailedException(String.format(
+ "%s failed for URI %s", operation, uriPath), exception.getCause(),
+ toRpcErrors(((RestconfDocumentedException)exception).getErrors()));
+ }
+
+ throw new OperationFailedException(String.format("%s failed for URI %s", operation, uriPath), exception);
+ }
+
+ private static RpcError[] toRpcErrors(final List<RestconfError> from) {
+ final RpcError[] to = new RpcError[from.size()];
+ int index = 0;
+ for (final RestconfError e: from) {
+ to[index++] = RpcResultBuilder.newError(e.getErrorType(), e.getErrorTag(), e.getErrorMessage());
+ }
+
+ return to;
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/NormalizedDataPrunner.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/NormalizedDataPrunner.java
new file mode 100644
index 0000000..975da60
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/NormalizedDataPrunner.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.schema.AnyxmlNode;
+import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MixinNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.SystemMapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UserMapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.CollectionNodeBuilder;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+
+class NormalizedDataPrunner {
+
+ public DataContainerChild pruneDataAtDepth(final DataContainerChild node, final Integer depth) {
+ if (depth == null) {
+ return node;
+ }
+
+ if (node instanceof LeafNode || node instanceof LeafSetNode || node instanceof AnyxmlNode) {
+ return node;
+ } else if (node instanceof MixinNode) {
+ return processMixinNode(node, depth);
+ } else if (node instanceof DataContainerNode) {
+ return processContainerNode(node, depth);
+ }
+ throw new IllegalStateException("Unexpected Mixin node occured why pruning data to requested depth");
+ }
+
+ private DataContainerChild processMixinNode(final NormalizedNode node, final Integer depth) {
+ if (node instanceof AugmentationNode) {
+ return processAugmentationNode(node, depth);
+ } else if (node instanceof ChoiceNode) {
+ return processChoiceNode(node, depth);
+ } else if (node instanceof UserMapNode) {
+ return processOrderedMapNode(node, depth);
+ } else if (node instanceof MapNode) {
+ return processMapNode(node, depth);
+ } else if (node instanceof UnkeyedListNode) {
+ return processUnkeyedListNode(node, depth);
+ }
+ throw new IllegalStateException("Unexpected Mixin node occured why pruning data to requested depth");
+ }
+
+ private DataContainerChild processContainerNode(final NormalizedNode node, final Integer depth) {
+ final ContainerNode containerNode = (ContainerNode) node;
+ DataContainerNodeBuilder<NodeIdentifier, ContainerNode> newContainerBuilder = Builders.containerBuilder()
+ .withNodeIdentifier(containerNode.getIdentifier());
+ if (depth > 1) {
+ processDataContainerChild((DataContainerNode) node, depth, newContainerBuilder);
+ }
+ return newContainerBuilder.build();
+ }
+
+ private DataContainerChild processChoiceNode(final NormalizedNode node, final Integer depth) {
+ final ChoiceNode choiceNode = (ChoiceNode) node;
+ DataContainerNodeBuilder<NodeIdentifier, ChoiceNode> newChoiceBuilder = Builders.choiceBuilder()
+ .withNodeIdentifier(choiceNode.getIdentifier());
+
+ processDataContainerChild((DataContainerNode) node, depth, newChoiceBuilder);
+
+ return newChoiceBuilder.build();
+ }
+
+ private DataContainerChild processAugmentationNode(final NormalizedNode node, final Integer depth) {
+ final AugmentationNode augmentationNode = (AugmentationNode) node;
+ DataContainerNodeBuilder<AugmentationIdentifier, ? extends DataContainerChild> newAugmentationBuilder =
+ Builders.augmentationBuilder().withNodeIdentifier(augmentationNode.getIdentifier());
+
+ processDataContainerChild((DataContainerNode) node, depth, newAugmentationBuilder);
+
+ return newAugmentationBuilder.build();
+ }
+
+ private void processDataContainerChild(final DataContainerNode node, final Integer depth,
+ final DataContainerNodeBuilder<?, ?> newBuilder) {
+ for (DataContainerChild nodeValue : node.body()) {
+ newBuilder.withChild(pruneDataAtDepth(nodeValue, depth - 1));
+ }
+ }
+
+ private DataContainerChild processUnkeyedListNode(final NormalizedNode node, final Integer depth) {
+ CollectionNodeBuilder<UnkeyedListEntryNode, UnkeyedListNode> newUnkeyedListBuilder = Builders
+ .unkeyedListBuilder();
+ if (depth > 1) {
+ for (UnkeyedListEntryNode oldUnkeyedListEntry : ((UnkeyedListNode) node).body()) {
+ DataContainerNodeBuilder<NodeIdentifier, UnkeyedListEntryNode> newUnkeyedListEntry = Builders
+ .unkeyedListEntryBuilder().withNodeIdentifier(oldUnkeyedListEntry.getIdentifier());
+ for (DataContainerChild oldUnkeyedListEntryValue : oldUnkeyedListEntry.body()) {
+ newUnkeyedListEntry.withChild(pruneDataAtDepth(oldUnkeyedListEntryValue, depth - 1));
+ }
+ newUnkeyedListBuilder.addChild(newUnkeyedListEntry.build());
+ }
+ }
+ return newUnkeyedListBuilder.build();
+ }
+
+ private DataContainerChild processOrderedMapNode(final NormalizedNode node, final Integer depth) {
+ CollectionNodeBuilder<MapEntryNode, UserMapNode> newOrderedMapNodeBuilder = Builders.orderedMapBuilder();
+ processMapEntries(node, depth, newOrderedMapNodeBuilder);
+ return newOrderedMapNodeBuilder.build();
+ }
+
+ private DataContainerChild processMapNode(final NormalizedNode node, final Integer depth) {
+ CollectionNodeBuilder<MapEntryNode, SystemMapNode> newMapNodeBuilder = Builders.mapBuilder();
+ processMapEntries(node, depth, newMapNodeBuilder);
+ return newMapNodeBuilder.build();
+ }
+
+ private void processMapEntries(final NormalizedNode node, final Integer depth,
+ final CollectionNodeBuilder<MapEntryNode, ? extends MapNode> newOrderedMapNodeBuilder) {
+ if (depth > 1) {
+ for (MapEntryNode oldMapEntryNode : ((MapNode) node).body()) {
+ DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> newMapEntryNodeBuilder =
+ Builders.mapEntryBuilder().withNodeIdentifier(oldMapEntryNode.getIdentifier());
+ for (DataContainerChild mapEntryNodeValue : oldMapEntryNode.body()) {
+ newMapEntryNodeBuilder.withChild(pruneDataAtDepth(mapEntryNodeValue, depth - 1));
+ }
+ newOrderedMapNodeBuilder.withChild(newMapEntryNodeBuilder.build());
+ }
+ }
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PutResult.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PutResult.java
new file mode 100644
index 0000000..87e33b9
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PutResult.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import com.google.common.util.concurrent.FluentFuture;
+import javax.ws.rs.core.Response.Status;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+
+/**
+ * Wrapper for status and future of PUT operation.
+ */
+public class PutResult {
+ private final Status status;
+ private final FluentFuture<? extends CommitInfo> future;
+
+ /**
+ * Wrap status and future by constructor - make this immutable.
+ *
+ * @param status
+ * status of operations
+ * @param future
+ * result of submit of PUT operation
+ */
+ public PutResult(final Status status, final FluentFuture<? extends CommitInfo> future) {
+ this.status = status;
+ this.future = future;
+ }
+
+ /**
+ * Get status.
+ *
+ * @return {@link Status} result
+ */
+ public Status getStatus() {
+ return this.status;
+ }
+
+ /**
+ * Get future.
+ *
+ * @return {@link FluentFuture} result
+ */
+ public FluentFuture<? extends CommitInfo> getFutureOfPutData() {
+ return this.future;
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/QueryParametersParser.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/QueryParametersParser.java
new file mode 100644
index 0000000..098c53b
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/QueryParametersParser.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import com.google.common.base.Strings;
+import javax.ws.rs.core.UriInfo;
+import org.opendaylight.netconf.sal.rest.impl.WriterParameters;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+
+public final class QueryParametersParser {
+
+ private enum UriParameters {
+ PRETTY_PRINT("prettyPrint"),
+ DEPTH("depth");
+
+ private final String uriParameterName;
+
+ UriParameters(final String uriParameterName) {
+ this.uriParameterName = uriParameterName;
+ }
+
+ @Override
+ public String toString() {
+ return uriParameterName;
+ }
+ }
+
+ private QueryParametersParser() {
+
+ }
+
+ public static WriterParameters parseWriterParameters(final UriInfo info) {
+ final WriterParameters.WriterParametersBuilder wpBuilder = new WriterParameters.WriterParametersBuilder();
+ if (info == null) {
+ return wpBuilder.build();
+ }
+
+ String param = info.getQueryParameters(false).getFirst(UriParameters.DEPTH.toString());
+ if (!Strings.isNullOrEmpty(param) && !"unbounded".equals(param)) {
+ try {
+ final int depth = Integer.parseInt(param);
+ if (depth < 1) {
+ throw new RestconfDocumentedException(
+ new RestconfError(ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
+ "Invalid depth parameter: " + depth, null,
+ "The depth parameter must be an integer > 1 or \"unbounded\""));
+ }
+ wpBuilder.setDepth(depth);
+ } catch (final NumberFormatException e) {
+ throw new RestconfDocumentedException(e, new RestconfError(
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
+ "Invalid depth parameter: " + e.getMessage(), null,
+ "The depth parameter must be an integer > 1 or \"unbounded\""));
+ }
+ }
+ param = info.getQueryParameters(false).getFirst(UriParameters.PRETTY_PRINT.toString());
+ wpBuilder.setPrettyPrint("true".equals(param));
+ return wpBuilder.build();
+ }
+
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestCodec.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestCodec.java
new file mode 100644
index 0000000..5d7a840
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestCodec.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import org.opendaylight.mdsal.dom.api.DOMMountPoint;
+import org.opendaylight.netconf.sal.rest.impl.StringModuleInstanceIdentifierCodec;
+import org.opendaylight.restconf.common.util.IdentityValuesDTO;
+import org.opendaylight.restconf.common.util.IdentityValuesDTO.IdentityValue;
+import org.opendaylight.restconf.common.util.IdentityValuesDTO.Predicate;
+import org.opendaylight.restconf.common.util.RestUtil;
+import org.opendaylight.yangtools.concepts.IllegalArgumentCodec;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.XMLNamespace;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.codec.IdentityrefCodec;
+import org.opendaylight.yangtools.yang.data.api.codec.InstanceIdentifierCodec;
+import org.opendaylight.yangtools.yang.data.api.codec.LeafrefCodec;
+import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class RestCodec {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RestCodec.class);
+
+ private RestCodec() {
+ }
+
+ // FIXME: IllegalArgumentCodec is not quite accurate
+ public static IllegalArgumentCodec<Object, Object> from(final TypeDefinition<?> typeDefinition,
+ final DOMMountPoint mountPoint, final ControllerContext controllerContext) {
+ return new ObjectCodec(typeDefinition, mountPoint, controllerContext);
+ }
+
+ @SuppressWarnings("rawtypes")
+ public static final class ObjectCodec implements IllegalArgumentCodec<Object, Object> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ObjectCodec.class);
+
+ public static final IllegalArgumentCodec LEAFREF_DEFAULT_CODEC = new LeafrefCodecImpl();
+
+ private final ControllerContext controllerContext;
+ private final IllegalArgumentCodec instanceIdentifier;
+ private final IllegalArgumentCodec identityrefCodec;
+
+ private final TypeDefinition<?> type;
+
+ private ObjectCodec(final TypeDefinition<?> typeDefinition, final DOMMountPoint mountPoint,
+ final ControllerContext controllerContext) {
+ this.controllerContext = controllerContext;
+ type = RestUtil.resolveBaseTypeFrom(typeDefinition);
+ if (type instanceof IdentityrefTypeDefinition) {
+ identityrefCodec = new IdentityrefCodecImpl(mountPoint, controllerContext);
+ } else {
+ identityrefCodec = null;
+ }
+ if (type instanceof InstanceIdentifierTypeDefinition) {
+ instanceIdentifier = new InstanceIdentifierCodecImpl(mountPoint, controllerContext);
+ } else {
+ instanceIdentifier = null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ @SuppressFBWarnings(value = "NP_NONNULL_RETURN_VIOLATION", justification = "Legacy code")
+ public Object deserialize(final Object input) {
+ try {
+ if (type instanceof IdentityrefTypeDefinition) {
+ if (input instanceof IdentityValuesDTO) {
+ return identityrefCodec.deserialize(input);
+ }
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(
+ "Value is not instance of IdentityrefTypeDefinition but is {}. "
+ + "Therefore NULL is used as translation of - {}",
+ input == null ? "null" : input.getClass(), String.valueOf(input));
+ }
+ // FIXME: this should be a hard error
+ return null;
+ } else if (type instanceof InstanceIdentifierTypeDefinition) {
+ if (input instanceof IdentityValuesDTO) {
+ return instanceIdentifier.deserialize(input);
+ } else {
+ final StringModuleInstanceIdentifierCodec codec = new StringModuleInstanceIdentifierCodec(
+ controllerContext.getGlobalSchema());
+ return codec.deserialize((String) input);
+ }
+ } else {
+ final TypeDefinitionAwareCodec<Object, ? extends TypeDefinition<?>> typeAwarecodec =
+ TypeDefinitionAwareCodec.from(type);
+ if (typeAwarecodec != null) {
+ if (input instanceof IdentityValuesDTO) {
+ return typeAwarecodec.deserialize(((IdentityValuesDTO) input).getOriginValue());
+ }
+ return typeAwarecodec.deserialize(String.valueOf(input));
+ } else {
+ LOG.debug("Codec for type \"{}\" is not implemented yet.", type.getQName().getLocalName());
+ // FIXME: this should be a hard error
+ return null;
+ }
+ }
+ } catch (final ClassCastException e) { // TODO remove this catch when everyone use codecs
+ LOG.error("ClassCastException was thrown when codec is invoked with parameter {}", input, e);
+ // FIXME: this should be a hard error
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ @SuppressFBWarnings(value = "NP_NONNULL_RETURN_VIOLATION", justification = "legacy code")
+ public Object serialize(final Object input) {
+ try {
+ if (type instanceof IdentityrefTypeDefinition) {
+ return identityrefCodec.serialize(input);
+ } else if (type instanceof LeafrefTypeDefinition) {
+ return LEAFREF_DEFAULT_CODEC.serialize(input);
+ } else if (type instanceof InstanceIdentifierTypeDefinition) {
+ return instanceIdentifier.serialize(input);
+ } else {
+ final TypeDefinitionAwareCodec<Object, ? extends TypeDefinition<?>> typeAwarecodec =
+ TypeDefinitionAwareCodec.from(type);
+ if (typeAwarecodec != null) {
+ return typeAwarecodec.serialize(input);
+ } else {
+ LOG.debug("Codec for type \"{}\" is not implemented yet.", type.getQName().getLocalName());
+ return null;
+ }
+ }
+ } catch (final ClassCastException e) {
+ // FIXME: remove this catch when everyone use codecs
+ LOG.error("ClassCastException was thrown when codec is invoked with parameter {}", input, e);
+ // FIXME: this should be a hard error
+ return input;
+ }
+ }
+
+ }
+
+ public static class IdentityrefCodecImpl implements IdentityrefCodec<IdentityValuesDTO> {
+ private static final Logger LOG = LoggerFactory.getLogger(IdentityrefCodecImpl.class);
+
+ private final DOMMountPoint mountPoint;
+ private final ControllerContext controllerContext;
+
+ public IdentityrefCodecImpl(final DOMMountPoint mountPoint, final ControllerContext controllerContext) {
+ this.mountPoint = mountPoint;
+ this.controllerContext = controllerContext;
+ }
+
+ @Override
+ public IdentityValuesDTO serialize(final QName data) {
+ return new IdentityValuesDTO(data.getNamespace().toString(), data.getLocalName(), null, null);
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "NP_NONNULL_RETURN_VIOLATION", justification = "See FIXME below")
+ public QName deserialize(final IdentityValuesDTO data) {
+ final IdentityValue valueWithNamespace = data.getValuesWithNamespaces().get(0);
+ final Module module = getModuleByNamespace(valueWithNamespace.getNamespace(), mountPoint,
+ controllerContext);
+ if (module == null) {
+ // FIXME: this should be a hard error
+ LOG.info("Module was not found for namespace {}", valueWithNamespace.getNamespace());
+ LOG.info("Idenetityref will be translated as NULL for data - {}", String.valueOf(valueWithNamespace));
+ return null;
+ }
+
+ return QName.create(module.getNamespace(), module.getRevision(), valueWithNamespace.getValue());
+ }
+ }
+
+ public static class LeafrefCodecImpl implements LeafrefCodec<String> {
+
+ @Override
+ public String serialize(final Object data) {
+ return String.valueOf(data);
+ }
+
+ @Override
+ public Object deserialize(final String data) {
+ return data;
+ }
+
+ }
+
+ public static class InstanceIdentifierCodecImpl implements InstanceIdentifierCodec<IdentityValuesDTO> {
+ private static final Logger LOG = LoggerFactory.getLogger(InstanceIdentifierCodecImpl.class);
+
+ private final DOMMountPoint mountPoint;
+ private final ControllerContext controllerContext;
+
+ public InstanceIdentifierCodecImpl(final DOMMountPoint mountPoint,
+ final ControllerContext controllerContext) {
+ this.mountPoint = mountPoint;
+ this.controllerContext = controllerContext;
+ }
+
+ @Override
+ public IdentityValuesDTO serialize(final YangInstanceIdentifier data) {
+ final IdentityValuesDTO identityValuesDTO = new IdentityValuesDTO();
+ for (final PathArgument pathArgument : data.getPathArguments()) {
+ final IdentityValue identityValue = qNameToIdentityValue(pathArgument.getNodeType());
+ if (pathArgument instanceof NodeIdentifierWithPredicates && identityValue != null) {
+ final List<Predicate> predicates =
+ keyValuesToPredicateList(((NodeIdentifierWithPredicates) pathArgument).entrySet());
+ identityValue.setPredicates(predicates);
+ } else if (pathArgument instanceof NodeWithValue && identityValue != null) {
+ final List<Predicate> predicates = new ArrayList<>();
+ final String value = String.valueOf(((NodeWithValue<?>) pathArgument).getValue());
+ predicates.add(new Predicate(null, value));
+ identityValue.setPredicates(predicates);
+ }
+ identityValuesDTO.add(identityValue);
+ }
+ return identityValuesDTO;
+ }
+
+ @SuppressFBWarnings(value = { "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", "NP_NONNULL_RETURN_VIOLATION" },
+ justification = "Unrecognised NullableDecl")
+ @Override
+ public YangInstanceIdentifier deserialize(final IdentityValuesDTO data) {
+ final List<PathArgument> result = new ArrayList<>();
+ final IdentityValue valueWithNamespace = data.getValuesWithNamespaces().get(0);
+ final Module module = getModuleByNamespace(valueWithNamespace.getNamespace(), mountPoint,
+ controllerContext);
+ if (module == null) {
+ LOG.info("Module by namespace '{}' of first node in instance-identifier was not found.",
+ valueWithNamespace.getNamespace());
+ LOG.info("Instance-identifier will be translated as NULL for data - {}",
+ String.valueOf(valueWithNamespace.getValue()));
+ // FIXME: this should be a hard error
+ return null;
+ }
+
+ DataNodeContainer parentContainer = module;
+ final List<IdentityValue> identities = data.getValuesWithNamespaces();
+ for (int i = 0; i < identities.size(); i++) {
+ final IdentityValue identityValue = identities.get(i);
+ XMLNamespace validNamespace = resolveValidNamespace(identityValue.getNamespace(), mountPoint,
+ controllerContext);
+ final var found = ControllerContext.findInstanceDataChildByNameAndNamespace(
+ parentContainer, identityValue.getValue(), validNamespace);
+ if (found == null) {
+ LOG.info("'{}' node was not found in {}", identityValue, parentContainer.getChildNodes());
+ LOG.info("Instance-identifier will be translated as NULL for data - {}",
+ String.valueOf(identityValue.getValue()));
+ // FIXME: this should be a hard error
+ return null;
+ }
+ final DataSchemaNode node = found.child;
+ final QName qName = node.getQName();
+ PathArgument pathArgument = null;
+ if (identityValue.getPredicates().isEmpty()) {
+ pathArgument = new NodeIdentifier(qName);
+ } else {
+ if (node instanceof LeafListSchemaNode) { // predicate is value of leaf-list entry
+ final Predicate leafListPredicate = identityValue.getPredicates().get(0);
+ if (!leafListPredicate.isLeafList()) {
+ LOG.info("Predicate's data is not type of leaf-list. It should be in format \".='value'\"");
+ LOG.info("Instance-identifier will be translated as NULL for data - {}",
+ String.valueOf(identityValue.getValue()));
+ // FIXME: this should be a hard error
+ return null;
+ }
+ pathArgument = new NodeWithValue<>(qName, leafListPredicate.getValue());
+ } else if (node instanceof ListSchemaNode) { // predicates are keys of list
+ final DataNodeContainer listNode = (DataNodeContainer) node;
+ final Map<QName, Object> predicatesMap = new HashMap<>();
+ for (final Predicate predicate : identityValue.getPredicates()) {
+ validNamespace = resolveValidNamespace(predicate.getName().getNamespace(), mountPoint,
+ controllerContext);
+ final var listKey = ControllerContext
+ .findInstanceDataChildByNameAndNamespace(listNode, predicate.getName().getValue(),
+ validNamespace);
+ predicatesMap.put(listKey.child.getQName(), predicate.getValue());
+ }
+ pathArgument = NodeIdentifierWithPredicates.of(qName, predicatesMap);
+ } else {
+ LOG.info("Node {} is not List or Leaf-list.", node);
+ LOG.info("Instance-identifier will be translated as NULL for data - {}",
+ String.valueOf(identityValue.getValue()));
+ // FIXME: this should be a hard error
+ return null;
+ }
+ }
+ result.add(pathArgument);
+ if (i < identities.size() - 1) { // last element in instance-identifier can be other than
+ // DataNodeContainer
+ if (node instanceof DataNodeContainer) {
+ parentContainer = (DataNodeContainer) node;
+ } else {
+ LOG.info("Node {} isn't instance of DataNodeContainer", node);
+ LOG.info("Instance-identifier will be translated as NULL for data - {}",
+ String.valueOf(identityValue.getValue()));
+ // FIXME: this should be a hard error
+ return null;
+ }
+ }
+ }
+
+ return result.isEmpty() ? null : YangInstanceIdentifier.create(result);
+ }
+
+ private static List<Predicate> keyValuesToPredicateList(final Set<Entry<QName, Object>> keyValues) {
+ final List<Predicate> result = new ArrayList<>();
+ for (final Entry<QName, Object> entry : keyValues) {
+ final QName qualifiedName = entry.getKey();
+ final Object value = entry.getValue();
+ result.add(new Predicate(qNameToIdentityValue(qualifiedName), String.valueOf(value)));
+ }
+ return result;
+ }
+
+ private static IdentityValue qNameToIdentityValue(final QName qualifiedName) {
+ if (qualifiedName != null) {
+ return new IdentityValue(qualifiedName.getNamespace().toString(), qualifiedName.getLocalName());
+ }
+ return null;
+ }
+ }
+
+ private static Module getModuleByNamespace(final String namespace, final DOMMountPoint mountPoint,
+ final ControllerContext controllerContext) {
+ final XMLNamespace validNamespace = resolveValidNamespace(namespace, mountPoint, controllerContext);
+
+ Module module = null;
+ if (mountPoint != null) {
+ module = ControllerContext.findModuleByNamespace(mountPoint, validNamespace);
+ } else {
+ module = controllerContext.findModuleByNamespace(validNamespace);
+ }
+ if (module == null) {
+ LOG.info("Module for namespace {} was not found.", validNamespace);
+ return null;
+ }
+ return module;
+ }
+
+ private static XMLNamespace resolveValidNamespace(final String namespace, final DOMMountPoint mountPoint,
+ final ControllerContext controllerContext) {
+ XMLNamespace validNamespace;
+ if (mountPoint != null) {
+ validNamespace = ControllerContext.findNamespaceByModuleName(mountPoint, namespace);
+ } else {
+ validNamespace = controllerContext.findNamespaceByModuleName(namespace);
+ }
+ if (validNamespace == null) {
+ validNamespace = XMLNamespace.of(namespace);
+ }
+
+ return validNamespace;
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfImpl.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfImpl.java
new file mode 100644
index 0000000..b3a1a9c
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfImpl.java
@@ -0,0 +1,1546 @@
+/*
+ * Copyright (c) 2014 - 2016 Brocade Communication Systems, Inc., Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Predicates;
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.util.concurrent.FluentFuture;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.net.URI;
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.ChronoField;
+import java.time.temporal.TemporalAccessor;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.common.api.OptimisticLockFailedException;
+import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
+import org.opendaylight.mdsal.dom.api.DOMMountPoint;
+import org.opendaylight.mdsal.dom.api.DOMRpcImplementationNotAvailableException;
+import org.opendaylight.mdsal.dom.api.DOMRpcResult;
+import org.opendaylight.mdsal.dom.api.DOMRpcService;
+import org.opendaylight.mdsal.dom.api.DOMSchemaService;
+import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult;
+import org.opendaylight.netconf.sal.rest.api.Draft02;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext.FoundChild;
+import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter;
+import org.opendaylight.netconf.sal.streams.listeners.NotificationListenerAdapter;
+import org.opendaylight.netconf.sal.streams.listeners.Notificator;
+import org.opendaylight.netconf.sal.streams.websockets.WebSocketServer;
+import org.opendaylight.restconf.common.OperationsContent;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+import org.opendaylight.restconf.common.util.OperationsResourceUtils;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.DateAndTime;
+import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.CreateDataChangeEventSubscriptionInput1.Scope;
+import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
+import org.opendaylight.yangtools.yang.common.Empty;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.Revision;
+import org.opendaylight.yangtools.yang.common.XMLNamespace;
+import org.opendaylight.yangtools.yang.common.YangConstants;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.SystemLeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.SystemMapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.CollectionNodeBuilder;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.ListNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.SchemaAwareBuilders;
+import org.opendaylight.yangtools.yang.data.tree.api.ModifiedNodeDoesNotExistException;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.FeatureDefinition;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Singleton
+public final class RestconfImpl implements RestconfService {
+ /**
+ * Notifications are served on port 8181.
+ */
+ private static final int NOTIFICATION_PORT = 8181;
+
+ private static final int CHAR_NOT_FOUND = -1;
+
+ private static final String SAL_REMOTE_NAMESPACE = "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote";
+
+ private static final Logger LOG = LoggerFactory.getLogger(RestconfImpl.class);
+
+ private static final LogicalDatastoreType DEFAULT_DATASTORE = LogicalDatastoreType.CONFIGURATION;
+
+ private static final XMLNamespace NAMESPACE_EVENT_SUBSCRIPTION_AUGMENT =
+ XMLNamespace.of("urn:sal:restconf:event:subscription");
+
+ private static final String DATASTORE_PARAM_NAME = "datastore";
+
+ private static final String SCOPE_PARAM_NAME = "scope";
+
+ private static final String OUTPUT_TYPE_PARAM_NAME = "notification-output-type";
+
+ private static final String NETCONF_BASE = "urn:ietf:params:xml:ns:netconf:base:1.0";
+
+ private static final String NETCONF_BASE_PAYLOAD_NAME = "data";
+
+ private static final QName NETCONF_BASE_QNAME = QName.create(QNameModule.create(XMLNamespace.of(NETCONF_BASE)),
+ NETCONF_BASE_PAYLOAD_NAME).intern();
+
+ private static final QNameModule SAL_REMOTE_AUGMENT = QNameModule.create(NAMESPACE_EVENT_SUBSCRIPTION_AUGMENT,
+ Revision.of("2014-07-08"));
+
+ private static final AugmentationIdentifier SAL_REMOTE_AUG_IDENTIFIER =
+ new AugmentationIdentifier(ImmutableSet.of(
+ QName.create(SAL_REMOTE_AUGMENT, "scope"), QName.create(SAL_REMOTE_AUGMENT, "datastore"),
+ QName.create(SAL_REMOTE_AUGMENT, "notification-output-type")));
+
+ public static final String DATA_SUBSCR = "data-change-event-subscription";
+ private static final String CREATE_DATA_SUBSCR = "create-" + DATA_SUBSCR;
+
+ public static final String NOTIFICATION_STREAM = "notification-stream";
+ private static final String CREATE_NOTIFICATION_STREAM = "create-" + NOTIFICATION_STREAM;
+
+ private static final DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder()
+ .appendValue(ChronoField.YEAR, 4).appendLiteral('-')
+ .appendValue(ChronoField.MONTH_OF_YEAR, 2).appendLiteral('-')
+ .appendValue(ChronoField.DAY_OF_MONTH, 2).appendLiteral('T')
+ .appendValue(ChronoField.HOUR_OF_DAY, 2).appendLiteral(':')
+ .appendValue(ChronoField.MINUTE_OF_HOUR, 2).appendLiteral(':')
+ .appendValue(ChronoField.SECOND_OF_MINUTE, 2)
+ .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
+ .appendOffset("+HH:MM", "Z").toFormatter();
+
+ private final BrokerFacade broker;
+
+ private final ControllerContext controllerContext;
+
+ @Inject
+ public RestconfImpl(final BrokerFacade broker, final ControllerContext controllerContext) {
+ this.broker = broker;
+ this.controllerContext = controllerContext;
+ }
+
+ /**
+ * Factory method.
+ *
+ * @deprecated Just use {@link #RestconfImpl(BrokerFacade, ControllerContext)} constructor instead.
+ */
+ @Deprecated
+ public static RestconfImpl newInstance(final BrokerFacade broker, final ControllerContext controllerContext) {
+ return new RestconfImpl(broker, controllerContext);
+ }
+
+ @Override
+ @Deprecated
+ public NormalizedNodeContext getModules(final UriInfo uriInfo) {
+ final Module restconfModule = getRestconfModule();
+ final var stack = SchemaInferenceStack.of(controllerContext.getGlobalSchema());
+ final var restconf = QName.create(restconfModule.getQNameModule(),
+ Draft02.RestConfModule.RESTCONF_GROUPING_SCHEMA_NODE);
+ stack.enterGrouping(restconf);
+ stack.enterSchemaTree(restconf);
+ final var modules = QName.create(restconf, Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
+ final var modulesSchemaNode = stack.enterSchemaTree(modules);
+ checkState(modulesSchemaNode instanceof ContainerSchemaNode);
+
+ final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> moduleContainerBuilder =
+ SchemaAwareBuilders.containerBuilder((ContainerSchemaNode) modulesSchemaNode);
+ moduleContainerBuilder.withChild(makeModuleMapNode(controllerContext.getAllModules()));
+
+ return new NormalizedNodeContext(InstanceIdentifierContext.ofStack(stack, null),
+ moduleContainerBuilder.build(), QueryParametersParser.parseWriterParameters(uriInfo));
+ }
+
+ /**
+ * Valid only for mount point.
+ */
+ @Override
+ @Deprecated
+ public NormalizedNodeContext getModules(final String identifier, final UriInfo uriInfo) {
+ if (!identifier.contains(ControllerContext.MOUNT)) {
+ final String errMsg = "URI has bad format. If modules behind mount point should be showed,"
+ + " URI has to end with " + ControllerContext.MOUNT;
+ LOG.debug("{} for {}", errMsg, identifier);
+ throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ final InstanceIdentifierContext mountPointIdentifier =
+ controllerContext.toMountPointIdentifier(identifier);
+ final DOMMountPoint mountPoint = mountPointIdentifier.getMountPoint();
+ final MapNode mountPointModulesMap = makeModuleMapNode(controllerContext.getAllModules(mountPoint));
+
+ final Module restconfModule = getRestconfModule();
+ final var stack = SchemaInferenceStack.of(controllerContext.getGlobalSchema());
+ final var restconf = QName.create(restconfModule.getQNameModule(),
+ Draft02.RestConfModule.RESTCONF_GROUPING_SCHEMA_NODE);
+ stack.enterGrouping(restconf);
+ stack.enterSchemaTree(restconf);
+ final var modules = QName.create(restconf, Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
+ final var modulesSchemaNode = stack.enterSchemaTree(modules);
+ checkState(modulesSchemaNode instanceof ContainerSchemaNode);
+
+ final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> moduleContainerBuilder =
+ SchemaAwareBuilders.containerBuilder((ContainerSchemaNode) modulesSchemaNode);
+ moduleContainerBuilder.withChild(mountPointModulesMap);
+
+ return new NormalizedNodeContext(InstanceIdentifierContext.ofStack(stack, null),
+ moduleContainerBuilder.build(), QueryParametersParser.parseWriterParameters(uriInfo));
+ }
+
+ @Override
+ @Deprecated
+ public NormalizedNodeContext getModule(final String identifier, final UriInfo uriInfo) {
+ final Entry<String, Revision> nameRev = getModuleNameAndRevision(requireNonNull(identifier));
+ final Module module;
+ final DOMMountPoint mountPoint;
+ if (identifier.contains(ControllerContext.MOUNT)) {
+ final InstanceIdentifierContext mountPointIdentifier =
+ controllerContext.toMountPointIdentifier(identifier);
+ mountPoint = mountPointIdentifier.getMountPoint();
+ module = controllerContext.findModuleByNameAndRevision(mountPoint, nameRev.getKey(),
+ nameRev.getValue());
+ } else {
+ mountPoint = null;
+ module = controllerContext.findModuleByNameAndRevision(nameRev.getKey(), nameRev.getValue());
+ }
+
+ if (module == null) {
+ LOG.debug("Module with name '{}' and revision '{}' was not found.", nameRev.getKey(), nameRev.getValue());
+ throw new RestconfDocumentedException("Module with name '" + nameRev.getKey() + "' and revision '"
+ + nameRev.getValue() + "' was not found.", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
+ }
+
+ final Module restconfModule = getRestconfModule();
+ final var stack = SchemaInferenceStack.of(controllerContext.getGlobalSchema());
+ final var restconf = QName.create(restconfModule.getQNameModule(),
+ Draft02.RestConfModule.RESTCONF_GROUPING_SCHEMA_NODE);
+ stack.enterGrouping(restconf);
+ stack.enterSchemaTree(restconf);
+ stack.enterSchemaTree(QName.create(restconf, Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE));
+ stack.enterSchemaTree(QName.create(restconf, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE));
+
+ return new NormalizedNodeContext(InstanceIdentifierContext.ofStack(stack, mountPoint),
+ makeModuleMapNode(Set.of(module)), QueryParametersParser.parseWriterParameters(uriInfo));
+ }
+
+ @Override
+ @Deprecated
+ public NormalizedNodeContext getAvailableStreams(final UriInfo uriInfo) {
+ final Set<String> availableStreams = Notificator.getStreamNames();
+ final Module restconfModule = getRestconfModule();
+ final DataSchemaNode streamSchemaNode = controllerContext
+ .getRestconfModuleRestConfSchemaNode(restconfModule, Draft02.RestConfModule.STREAM_LIST_SCHEMA_NODE);
+ checkState(streamSchemaNode instanceof ListSchemaNode);
+
+ final CollectionNodeBuilder<MapEntryNode, SystemMapNode> listStreamsBuilder =
+ SchemaAwareBuilders.mapBuilder((ListSchemaNode) streamSchemaNode);
+
+ for (final String streamName : availableStreams) {
+ listStreamsBuilder.withChild(toStreamEntryNode(streamName, streamSchemaNode));
+ }
+
+ final var stack = SchemaInferenceStack.of(controllerContext.getGlobalSchema());
+ final var restconf = QName.create(restconfModule.getQNameModule(),
+ Draft02.RestConfModule.RESTCONF_GROUPING_SCHEMA_NODE);
+ stack.enterGrouping(restconf);
+ stack.enterSchemaTree(restconf);
+ final var streams = QName.create(restconf, Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
+ final var streamsContainerSchemaNode = stack.enterSchemaTree(streams);
+ checkState(streamsContainerSchemaNode instanceof ContainerSchemaNode);
+
+ final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> streamsContainerBuilder =
+ SchemaAwareBuilders.containerBuilder((ContainerSchemaNode) streamsContainerSchemaNode);
+ streamsContainerBuilder.withChild(listStreamsBuilder.build());
+
+ return new NormalizedNodeContext(InstanceIdentifierContext.ofStack(stack),
+ streamsContainerBuilder.build(), QueryParametersParser.parseWriterParameters(uriInfo));
+ }
+
+ @Override
+ @Deprecated
+ public String getOperationsJSON() {
+ return OperationsContent.JSON.bodyFor(controllerContext.getGlobalSchema());
+ }
+
+ @Override
+ @Deprecated
+ public String getOperationsXML() {
+ return OperationsContent.XML.bodyFor(controllerContext.getGlobalSchema());
+ }
+
+ @Override
+ @Deprecated
+ public NormalizedNodeContext getOperations(final String identifier, final UriInfo uriInfo) {
+ if (!identifier.contains(ControllerContext.MOUNT)) {
+ final String errMsg = "URI has bad format. If operations behind mount point should be showed, URI has to "
+ + " end with " + ControllerContext.MOUNT;
+ LOG.debug("{} for {}", errMsg, identifier);
+ throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ final InstanceIdentifierContext mountPointIdentifier = controllerContext.toMountPointIdentifier(identifier);
+ final DOMMountPoint mountPoint = mountPointIdentifier.getMountPoint();
+ final var entry = OperationsResourceUtils.contextForModelContext(modelContext(mountPoint), mountPoint);
+ return new NormalizedNodeContext(entry.getKey(), entry.getValue());
+ }
+
+ private Module getRestconfModule() {
+ final Module restconfModule = controllerContext.getRestconfModule();
+ if (restconfModule == null) {
+ LOG.debug("ietf-restconf module was not found.");
+ throw new RestconfDocumentedException("ietf-restconf module was not found.", ErrorType.APPLICATION,
+ ErrorTag.OPERATION_NOT_SUPPORTED);
+ }
+
+ return restconfModule;
+ }
+
+ private static Entry<String, Revision> getModuleNameAndRevision(final String identifier) {
+ final int mountIndex = identifier.indexOf(ControllerContext.MOUNT);
+ String moduleNameAndRevision = "";
+ if (mountIndex >= 0) {
+ moduleNameAndRevision = identifier.substring(mountIndex + ControllerContext.MOUNT.length());
+ } else {
+ moduleNameAndRevision = identifier;
+ }
+
+ final Splitter splitter = Splitter.on('/').omitEmptyStrings();
+ final List<String> pathArgs = splitter.splitToList(moduleNameAndRevision);
+ if (pathArgs.size() < 2) {
+ LOG.debug("URI has bad format. It should be \'moduleName/yyyy-MM-dd\' {}", identifier);
+ throw new RestconfDocumentedException(
+ "URI has bad format. End of URI should be in format \'moduleName/yyyy-MM-dd\'", ErrorType.PROTOCOL,
+ ErrorTag.INVALID_VALUE);
+ }
+
+ try {
+ return Map.entry(pathArgs.get(0), Revision.of(pathArgs.get(1)));
+ } catch (final DateTimeParseException e) {
+ LOG.debug("URI has bad format. It should be \'moduleName/yyyy-MM-dd\' {}", identifier);
+ throw new RestconfDocumentedException("URI has bad format. It should be \'moduleName/yyyy-MM-dd\'",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e);
+ }
+ }
+
+ @Override
+ public Object getRoot() {
+ return null;
+ }
+
+ @Override
+ public NormalizedNodeContext invokeRpc(final String identifier, final NormalizedNodeContext payload,
+ final UriInfo uriInfo) {
+ if (payload == null) {
+ // no payload specified, reroute this to no payload invokeRpc implementation
+ return invokeRpc(identifier, uriInfo);
+ }
+
+ final SchemaNode schema = payload.getInstanceIdentifierContext().getSchemaNode();
+ final ListenableFuture<? extends DOMRpcResult> response;
+ final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
+ final NormalizedNode input = nonnullInput(schema, payload.getData());
+ final EffectiveModelContext schemaContext;
+
+ if (mountPoint != null) {
+ final Optional<DOMRpcService> mountRpcServices = mountPoint.getService(DOMRpcService.class);
+ if (mountRpcServices.isEmpty()) {
+ LOG.debug("Error: Rpc service is missing.");
+ throw new RestconfDocumentedException("Rpc service is missing.");
+ }
+ schemaContext = modelContext(mountPoint);
+ response = mountRpcServices.get().invokeRpc(schema.getQName(), input);
+ } else {
+ final XMLNamespace namespace = schema.getQName().getNamespace();
+ if (namespace.toString().equals(SAL_REMOTE_NAMESPACE)) {
+ if (identifier.contains(CREATE_DATA_SUBSCR)) {
+ response = invokeSalRemoteRpcSubscribeRPC(payload);
+ } else if (identifier.contains(CREATE_NOTIFICATION_STREAM)) {
+ response = invokeSalRemoteRpcNotifiStrRPC(payload);
+ } else {
+ final String msg = "Not supported operation";
+ LOG.warn(msg);
+ throw new RestconfDocumentedException(msg, ErrorType.RPC, ErrorTag.OPERATION_NOT_SUPPORTED);
+ }
+ } else {
+ response = broker.invokeRpc(schema.getQName(), input);
+ }
+ schemaContext = controllerContext.getGlobalSchema();
+ }
+
+ final DOMRpcResult result = checkRpcResponse(response);
+
+ final NormalizedNode resultData;
+ if (result != null && result.getResult() != null) {
+ resultData = result.getResult();
+ } else {
+ resultData = null;
+ }
+
+ if (resultData != null && ((ContainerNode) resultData).isEmpty()) {
+ throw new WebApplicationException(Response.Status.NO_CONTENT);
+ }
+
+ final var resultNodeSchema = (RpcDefinition) payload.getInstanceIdentifierContext().getSchemaNode();
+ return new NormalizedNodeContext(
+ InstanceIdentifierContext.ofRpcOutput(schemaContext, resultNodeSchema, mountPoint), resultData,
+ QueryParametersParser.parseWriterParameters(uriInfo));
+ }
+
+ @SuppressFBWarnings(value = "NP_LOAD_OF_KNOWN_NULL_VALUE",
+ justification = "Looks like a false positive, see below FIXME")
+ private NormalizedNodeContext invokeRpc(final String identifier, final UriInfo uriInfo) {
+ final DOMMountPoint mountPoint;
+ final String identifierEncoded;
+ final EffectiveModelContext schemaContext;
+ if (identifier.contains(ControllerContext.MOUNT)) {
+ // mounted RPC call - look up mount instance.
+ final InstanceIdentifierContext mountPointId = controllerContext.toMountPointIdentifier(identifier);
+ mountPoint = mountPointId.getMountPoint();
+ schemaContext = modelContext(mountPoint);
+ final int startOfRemoteRpcName =
+ identifier.lastIndexOf(ControllerContext.MOUNT) + ControllerContext.MOUNT.length() + 1;
+ final String remoteRpcName = identifier.substring(startOfRemoteRpcName);
+ identifierEncoded = remoteRpcName;
+
+ } else if (identifier.indexOf('/') == CHAR_NOT_FOUND) {
+ identifierEncoded = identifier;
+ mountPoint = null;
+ schemaContext = controllerContext.getGlobalSchema();
+ } else {
+ LOG.debug("Identifier {} cannot contain slash character (/).", identifier);
+ throw new RestconfDocumentedException(String.format("Identifier %n%s%ncan\'t contain slash character (/).%n"
+ + "If slash is part of identifier name then use %%2F placeholder.", identifier), ErrorType.PROTOCOL,
+ ErrorTag.INVALID_VALUE);
+ }
+
+ final String identifierDecoded = ControllerContext.urlPathArgDecode(identifierEncoded);
+ final RpcDefinition rpc;
+ if (mountPoint == null) {
+ rpc = controllerContext.getRpcDefinition(identifierDecoded);
+ } else {
+ rpc = findRpc(modelContext(mountPoint), identifierDecoded);
+ }
+
+ if (rpc == null) {
+ LOG.debug("RPC {} does not exist.", identifierDecoded);
+ throw new RestconfDocumentedException("RPC does not exist.", ErrorType.RPC, ErrorTag.UNKNOWN_ELEMENT);
+ }
+
+ if (!rpc.getInput().getChildNodes().isEmpty()) {
+ LOG.debug("No input specified for RPC {} with an input section", rpc);
+ throw new RestconfDocumentedException("No input specified for RPC " + rpc
+ + " with an input section defined", ErrorType.RPC, ErrorTag.MISSING_ELEMENT);
+ }
+
+ final ContainerNode input = defaultInput(rpc.getQName());
+ final ListenableFuture<? extends DOMRpcResult> response;
+ if (mountPoint != null) {
+ final Optional<DOMRpcService> mountRpcServices = mountPoint.getService(DOMRpcService.class);
+ if (mountRpcServices.isEmpty()) {
+ throw new RestconfDocumentedException("Rpc service is missing.");
+ }
+ response = mountRpcServices.get().invokeRpc(rpc.getQName(), input);
+ } else {
+ response = broker.invokeRpc(rpc.getQName(), input);
+ }
+
+ final NormalizedNode result = checkRpcResponse(response).getResult();
+ if (result != null && ((ContainerNode) result).isEmpty()) {
+ throw new WebApplicationException(Response.Status.NO_CONTENT);
+ }
+
+ // FIXME: in reference to the above @SupressFBWarnings: "mountPoint" reference here trips up SpotBugs, as it
+ // thinks it can only ever be null. Except it can very much be non-null. The core problem is the horrible
+ // structure of this code where we have a sh*tload of checks for mountpoint above and all over the
+ // codebase where all that difference should have been properly encapsulated.
+ //
+ // This is legacy code, so if anybody cares to do that refactor, feel free to contribute, but I am not
+ // doing that work.
+ final var iic = mountPoint == null ? InstanceIdentifierContext.ofLocalRpcOutput(schemaContext, rpc)
+ : InstanceIdentifierContext.ofMountPointRpcOutput(mountPoint, schemaContext, rpc);
+ return new NormalizedNodeContext(iic, result, QueryParametersParser.parseWriterParameters(uriInfo));
+ }
+
+ private static @NonNull NormalizedNode nonnullInput(final SchemaNode rpc, final NormalizedNode input) {
+ return input != null ? input : defaultInput(rpc.getQName());
+ }
+
+ private static @NonNull ContainerNode defaultInput(final QName rpcName) {
+ return ImmutableNodes.containerNode(YangConstants.operationInputQName(rpcName.getModule()));
+ }
+
+ @SuppressWarnings("checkstyle:avoidHidingCauseException")
+ private static DOMRpcResult checkRpcResponse(final ListenableFuture<? extends DOMRpcResult> response) {
+ if (response == null) {
+ return null;
+ }
+ try {
+ final DOMRpcResult retValue = response.get();
+ if (retValue.getErrors().isEmpty()) {
+ return retValue;
+ }
+ LOG.debug("RpcError message {}", retValue.getErrors());
+ throw new RestconfDocumentedException("RpcError message", null, retValue.getErrors());
+ } catch (final InterruptedException e) {
+ final String errMsg = "The operation was interrupted while executing and did not complete.";
+ LOG.debug("Rpc Interrupt - {}", errMsg, e);
+ throw new RestconfDocumentedException(errMsg, ErrorType.RPC, ErrorTag.PARTIAL_OPERATION, e);
+ } catch (final ExecutionException e) {
+ LOG.debug("Execution RpcError: ", e);
+ Throwable cause = e.getCause();
+ if (cause == null) {
+ throw new RestconfDocumentedException("The operation encountered an unexpected error while executing.",
+ e);
+ }
+ while (cause.getCause() != null) {
+ cause = cause.getCause();
+ }
+
+ if (cause instanceof IllegalArgumentException) {
+ throw new RestconfDocumentedException(cause.getMessage(), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ } else if (cause instanceof DOMRpcImplementationNotAvailableException) {
+ throw new RestconfDocumentedException(cause.getMessage(), ErrorType.APPLICATION,
+ ErrorTag.OPERATION_NOT_SUPPORTED);
+ }
+ throw new RestconfDocumentedException("The operation encountered an unexpected error while executing.",
+ cause);
+ } catch (final CancellationException e) {
+ final String errMsg = "The operation was cancelled while executing.";
+ LOG.debug("Cancel RpcExecution: {}", errMsg, e);
+ throw new RestconfDocumentedException(errMsg, ErrorType.RPC, ErrorTag.PARTIAL_OPERATION);
+ }
+ }
+
+ private static void validateInput(final SchemaNode inputSchema, final NormalizedNodeContext payload) {
+ if (inputSchema != null && payload.getData() == null) {
+ // expected a non null payload
+ throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+ } else if (inputSchema == null && payload.getData() != null) {
+ // did not expect any input
+ throw new RestconfDocumentedException("No input expected.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+ }
+ }
+
+ private ListenableFuture<DOMRpcResult> invokeSalRemoteRpcSubscribeRPC(final NormalizedNodeContext payload) {
+ final ContainerNode value = (ContainerNode) payload.getData();
+ final QName rpcQName = payload.getInstanceIdentifierContext().getSchemaNode().getQName();
+ final Optional<DataContainerChild> path =
+ value.findChildByArg(new NodeIdentifier(QName.create(rpcQName, "path")));
+ final Object pathValue = path.isPresent() ? path.get().body() : null;
+
+ if (!(pathValue instanceof YangInstanceIdentifier)) {
+ LOG.debug("Instance identifier {} was not normalized correctly", rpcQName);
+ throw new RestconfDocumentedException("Instance identifier was not normalized correctly",
+ ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED);
+ }
+
+ final YangInstanceIdentifier pathIdentifier = (YangInstanceIdentifier) pathValue;
+ final String streamName;
+ NotificationOutputType outputType = null;
+ if (!pathIdentifier.isEmpty()) {
+ final String fullRestconfIdentifier =
+ DATA_SUBSCR + controllerContext.toFullRestconfIdentifier(pathIdentifier, null);
+
+ LogicalDatastoreType datastore =
+ parseEnumTypeParameter(value, LogicalDatastoreType.class, DATASTORE_PARAM_NAME);
+ datastore = datastore == null ? DEFAULT_DATASTORE : datastore;
+
+ Scope scope = parseEnumTypeParameter(value, Scope.class, SCOPE_PARAM_NAME);
+ scope = scope == null ? Scope.BASE : scope;
+
+ outputType = parseEnumTypeParameter(value, NotificationOutputType.class, OUTPUT_TYPE_PARAM_NAME);
+ outputType = outputType == null ? NotificationOutputType.XML : outputType;
+
+ streamName = Notificator
+ .createStreamNameFromUri(fullRestconfIdentifier + "/datastore=" + datastore + "/scope=" + scope);
+ } else {
+ streamName = CREATE_DATA_SUBSCR;
+ }
+
+ if (Strings.isNullOrEmpty(streamName)) {
+ LOG.debug("Path is empty or contains value node which is not Container or List built-in type at {}",
+ pathIdentifier);
+ throw new RestconfDocumentedException("Path is empty or contains value node which is not Container or List "
+ + "built-in type.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ if (!Notificator.existListenerFor(streamName)) {
+ Notificator.createListener(pathIdentifier, streamName, outputType, controllerContext);
+ }
+
+ return Futures.immediateFuture(new DefaultDOMRpcResult(Builders.containerBuilder()
+ .withNodeIdentifier(new NodeIdentifier(QName.create(rpcQName, "output")))
+ .withChild(ImmutableNodes.leafNode(QName.create(rpcQName, "stream-name"), streamName))
+ .build()));
+ }
+
+ private static RpcDefinition findRpc(final SchemaContext schemaContext, final String identifierDecoded) {
+ final String[] splittedIdentifier = identifierDecoded.split(":");
+ if (splittedIdentifier.length != 2) {
+ LOG.debug("{} could not be split to 2 parts (module:rpc name)", identifierDecoded);
+ throw new RestconfDocumentedException(identifierDecoded + " could not be split to 2 parts "
+ + "(module:rpc name)", ErrorType.APPLICATION, ErrorTag.INVALID_VALUE);
+ }
+ for (final Module module : schemaContext.getModules()) {
+ if (module.getName().equals(splittedIdentifier[0])) {
+ for (final RpcDefinition rpcDefinition : module.getRpcs()) {
+ if (rpcDefinition.getQName().getLocalName().equals(splittedIdentifier[1])) {
+ return rpcDefinition;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public NormalizedNodeContext readConfigurationData(final String identifier, final UriInfo uriInfo) {
+ boolean withDefaUsed = false;
+ String withDefa = null;
+
+ for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
+ switch (entry.getKey()) {
+ case "with-defaults":
+ if (!withDefaUsed) {
+ withDefaUsed = true;
+ withDefa = entry.getValue().iterator().next();
+ } else {
+ throw new RestconfDocumentedException("With-defaults parameter can be used only once.");
+ }
+ break;
+ default:
+ LOG.info("Unknown key : {}.", entry.getKey());
+ break;
+ }
+ }
+
+ // TODO: this flag is always ignored
+ boolean tagged = false;
+ if (withDefaUsed) {
+ if ("report-all-tagged".equals(withDefa)) {
+ tagged = true;
+ withDefa = null;
+ }
+ if ("report-all".equals(withDefa)) {
+ withDefa = null;
+ }
+ }
+
+ final InstanceIdentifierContext iiWithData = controllerContext.toInstanceIdentifier(identifier);
+ final DOMMountPoint mountPoint = iiWithData.getMountPoint();
+ NormalizedNode data = null;
+ final YangInstanceIdentifier normalizedII = iiWithData.getInstanceIdentifier();
+ if (mountPoint != null) {
+ data = broker.readConfigurationData(mountPoint, normalizedII, withDefa);
+ } else {
+ data = broker.readConfigurationData(normalizedII, withDefa);
+ }
+ if (data == null) {
+ throw dataMissing(identifier);
+ }
+ return new NormalizedNodeContext(iiWithData, data, QueryParametersParser.parseWriterParameters(uriInfo));
+ }
+
+ @Override
+ public NormalizedNodeContext readOperationalData(final String identifier, final UriInfo uriInfo) {
+ final InstanceIdentifierContext iiWithData = controllerContext.toInstanceIdentifier(identifier);
+ final DOMMountPoint mountPoint = iiWithData.getMountPoint();
+ NormalizedNode data = null;
+ final YangInstanceIdentifier normalizedII = iiWithData.getInstanceIdentifier();
+ if (mountPoint != null) {
+ data = broker.readOperationalData(mountPoint, normalizedII);
+ } else {
+ data = broker.readOperationalData(normalizedII);
+ }
+ if (data == null) {
+ throw dataMissing(identifier);
+ }
+ return new NormalizedNodeContext(iiWithData, data, QueryParametersParser.parseWriterParameters(uriInfo));
+ }
+
+ private static RestconfDocumentedException dataMissing(final String identifier) {
+ LOG.debug("Request could not be completed because the relevant data model content does not exist {}",
+ identifier);
+ return new RestconfDocumentedException("Request could not be completed because the relevant data model content "
+ + "does not exist", ErrorType.APPLICATION, ErrorTag.DATA_MISSING);
+ }
+
+ @Override
+ public Response updateConfigurationData(final String identifier, final NormalizedNodeContext payload,
+ final UriInfo uriInfo) {
+ boolean insertUsed = false;
+ boolean pointUsed = false;
+ String insert = null;
+ String point = null;
+
+ for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
+ switch (entry.getKey()) {
+ case "insert":
+ if (!insertUsed) {
+ insertUsed = true;
+ insert = entry.getValue().iterator().next();
+ } else {
+ throw new RestconfDocumentedException("Insert parameter can be used only once.");
+ }
+ break;
+ case "point":
+ if (!pointUsed) {
+ pointUsed = true;
+ point = entry.getValue().iterator().next();
+ } else {
+ throw new RestconfDocumentedException("Point parameter can be used only once.");
+ }
+ break;
+ default:
+ throw new RestconfDocumentedException("Bad parameter for post: " + entry.getKey());
+ }
+ }
+
+ if (pointUsed && !insertUsed) {
+ throw new RestconfDocumentedException("Point parameter can't be used without Insert parameter.");
+ }
+ if (pointUsed && (insert.equals("first") || insert.equals("last"))) {
+ throw new RestconfDocumentedException(
+ "Point parameter can be used only with 'after' or 'before' values of Insert parameter.");
+ }
+
+ requireNonNull(identifier);
+
+ final InstanceIdentifierContext iiWithData = payload.getInstanceIdentifierContext();
+
+ validateInput(iiWithData.getSchemaNode(), payload);
+ validateTopLevelNodeName(payload, iiWithData.getInstanceIdentifier());
+ validateListKeysEqualityInPayloadAndUri(payload);
+
+ final DOMMountPoint mountPoint = iiWithData.getMountPoint();
+ final YangInstanceIdentifier normalizedII = iiWithData.getInstanceIdentifier();
+
+ /*
+ * There is a small window where another write transaction could be
+ * updating the same data simultaneously and we get an
+ * OptimisticLockFailedException. This error is likely transient and The
+ * WriteTransaction#submit API docs state that a retry will likely
+ * succeed. So we'll try again if that scenario occurs. If it fails a
+ * third time then it probably will never succeed so we'll fail in that
+ * case.
+ *
+ * By retrying we're attempting to hide the internal implementation of
+ * the data store and how it handles concurrent updates from the
+ * restconf client. The client has instructed us to put the data and we
+ * should make every effort to do so without pushing optimistic lock
+ * failures back to the client and forcing them to handle it via retry
+ * (and having to document the behavior).
+ */
+ PutResult result = null;
+ int tries = 2;
+ while (true) {
+ if (mountPoint != null) {
+
+ result = broker.commitMountPointDataPut(mountPoint, normalizedII, payload.getData(), insert,
+ point);
+ } else {
+ result = broker.commitConfigurationDataPut(controllerContext.getGlobalSchema(), normalizedII,
+ payload.getData(), insert, point);
+ }
+
+ try {
+ result.getFutureOfPutData().get();
+ } catch (final InterruptedException e) {
+ LOG.debug("Update failed for {}", identifier, e);
+ throw new RestconfDocumentedException(e.getMessage(), e);
+ } catch (final ExecutionException e) {
+ final TransactionCommitFailedException failure = Throwables.getCauseAs(e,
+ TransactionCommitFailedException.class);
+ if (failure instanceof OptimisticLockFailedException) {
+ if (--tries <= 0) {
+ LOG.debug("Got OptimisticLockFailedException on last try - failing {}", identifier);
+ throw new RestconfDocumentedException(e.getMessage(), e, failure.getErrorList());
+ }
+
+ LOG.debug("Got OptimisticLockFailedException - trying again {}", identifier);
+ continue;
+ }
+
+ LOG.debug("Update failed for {}", identifier, e);
+ throw RestconfDocumentedException.decodeAndThrow(e.getMessage(), failure);
+ }
+
+ return Response.status(result.getStatus()).build();
+ }
+ }
+
+ private static void validateTopLevelNodeName(final NormalizedNodeContext node,
+ final YangInstanceIdentifier identifier) {
+
+ final String payloadName = node.getData().getIdentifier().getNodeType().getLocalName();
+
+ // no arguments
+ if (identifier.isEmpty()) {
+ // no "data" payload
+ if (!node.getData().getIdentifier().getNodeType().equals(NETCONF_BASE_QNAME)) {
+ throw new RestconfDocumentedException("Instance identifier has to contain at least one path argument",
+ ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+ }
+ // any arguments
+ } else {
+ final String identifierName = identifier.getLastPathArgument().getNodeType().getLocalName();
+ if (!payloadName.equals(identifierName)) {
+ throw new RestconfDocumentedException(
+ "Payload name (" + payloadName + ") is different from identifier name (" + identifierName + ")",
+ ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+ }
+ }
+ }
+
+ /**
+ * Validates whether keys in {@code payload} are equal to values of keys in
+ * {@code iiWithData} for list schema node.
+ *
+ * @throws RestconfDocumentedException
+ * if key values or key count in payload and URI isn't equal
+ *
+ */
+ private static void validateListKeysEqualityInPayloadAndUri(final NormalizedNodeContext payload) {
+ checkArgument(payload != null);
+ final InstanceIdentifierContext iiWithData = payload.getInstanceIdentifierContext();
+ final PathArgument lastPathArgument = iiWithData.getInstanceIdentifier().getLastPathArgument();
+ final SchemaNode schemaNode = iiWithData.getSchemaNode();
+ final NormalizedNode data = payload.getData();
+ if (schemaNode instanceof ListSchemaNode) {
+ final List<QName> keyDefinitions = ((ListSchemaNode) schemaNode).getKeyDefinition();
+ if (lastPathArgument instanceof NodeIdentifierWithPredicates && data instanceof MapEntryNode) {
+ final Map<QName, Object> uriKeyValues = ((NodeIdentifierWithPredicates) lastPathArgument).asMap();
+ isEqualUriAndPayloadKeyValues(uriKeyValues, (MapEntryNode) data, keyDefinitions);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public static void isEqualUriAndPayloadKeyValues(final Map<QName, Object> uriKeyValues, final MapEntryNode payload,
+ final List<QName> keyDefinitions) {
+
+ final Map<QName, Object> mutableCopyUriKeyValues = new HashMap<>(uriKeyValues);
+ for (final QName keyDefinition : keyDefinitions) {
+ final Object uriKeyValue = RestconfDocumentedException.throwIfNull(
+ // should be caught during parsing URI to InstanceIdentifier
+ mutableCopyUriKeyValues.remove(keyDefinition), ErrorType.PROTOCOL, ErrorTag.DATA_MISSING,
+ "Missing key %s in URI.", keyDefinition);
+
+ final Object dataKeyValue = payload.getIdentifier().getValue(keyDefinition);
+
+ if (!Objects.deepEquals(uriKeyValue, dataKeyValue)) {
+ final String errMsg = "The value '" + uriKeyValue + "' for key '" + keyDefinition.getLocalName()
+ + "' specified in the URI doesn't match the value '" + dataKeyValue
+ + "' specified in the message body. ";
+ throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+ }
+ }
+
+ @Override
+ public Response createConfigurationData(final String identifier, final NormalizedNodeContext payload,
+ final UriInfo uriInfo) {
+ return createConfigurationData(payload, uriInfo);
+ }
+
+ @Override
+ public Response createConfigurationData(final NormalizedNodeContext payload, final UriInfo uriInfo) {
+ if (payload == null) {
+ throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+ }
+ final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
+ final InstanceIdentifierContext iiWithData = payload.getInstanceIdentifierContext();
+ final YangInstanceIdentifier normalizedII = iiWithData.getInstanceIdentifier();
+
+ boolean insertUsed = false;
+ boolean pointUsed = false;
+ String insert = null;
+ String point = null;
+
+ if (uriInfo != null) {
+ for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
+ switch (entry.getKey()) {
+ case "insert":
+ if (!insertUsed) {
+ insertUsed = true;
+ insert = entry.getValue().iterator().next();
+ } else {
+ throw new RestconfDocumentedException("Insert parameter can be used only once.");
+ }
+ break;
+ case "point":
+ if (!pointUsed) {
+ pointUsed = true;
+ point = entry.getValue().iterator().next();
+ } else {
+ throw new RestconfDocumentedException("Point parameter can be used only once.");
+ }
+ break;
+ default:
+ throw new RestconfDocumentedException("Bad parameter for post: " + entry.getKey());
+ }
+ }
+ }
+
+ if (pointUsed && !insertUsed) {
+ throw new RestconfDocumentedException("Point parameter can't be used without Insert parameter.");
+ }
+ if (pointUsed && (insert.equals("first") || insert.equals("last"))) {
+ throw new RestconfDocumentedException(
+ "Point parameter can be used only with 'after' or 'before' values of Insert parameter.");
+ }
+
+ FluentFuture<? extends CommitInfo> future;
+ if (mountPoint != null) {
+ future = broker.commitConfigurationDataPost(mountPoint, normalizedII, payload.getData(), insert,
+ point);
+ } else {
+ future = broker.commitConfigurationDataPost(controllerContext.getGlobalSchema(), normalizedII,
+ payload.getData(), insert, point);
+ }
+
+ try {
+ future.get();
+ } catch (final InterruptedException e) {
+ LOG.info("Error creating data {}", uriInfo != null ? uriInfo.getPath() : "", e);
+ throw new RestconfDocumentedException(e.getMessage(), e);
+ } catch (final ExecutionException e) {
+ LOG.info("Error creating data {}", uriInfo != null ? uriInfo.getPath() : "", e);
+ throw RestconfDocumentedException.decodeAndThrow(e.getMessage(), Throwables.getCauseAs(e,
+ TransactionCommitFailedException.class));
+ }
+
+ LOG.trace("Successfuly created data.");
+
+ final ResponseBuilder responseBuilder = Response.status(Status.NO_CONTENT);
+ // FIXME: Provide path to result.
+ final URI location = resolveLocation(uriInfo, "", mountPoint, normalizedII);
+ if (location != null) {
+ responseBuilder.location(location);
+ }
+ return responseBuilder.build();
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ private URI resolveLocation(final UriInfo uriInfo, final String uriBehindBase, final DOMMountPoint mountPoint,
+ final YangInstanceIdentifier normalizedII) {
+ if (uriInfo == null) {
+ // This is null if invoked internally
+ return null;
+ }
+
+ final UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
+ uriBuilder.path("config");
+ try {
+ uriBuilder.path(controllerContext.toFullRestconfIdentifier(normalizedII, mountPoint));
+ } catch (final Exception e) {
+ LOG.info("Location for instance identifier {} was not created", normalizedII, e);
+ return null;
+ }
+ return uriBuilder.build();
+ }
+
+ @Override
+ public Response deleteConfigurationData(final String identifier) {
+ final InstanceIdentifierContext iiWithData = controllerContext.toInstanceIdentifier(identifier);
+ final DOMMountPoint mountPoint = iiWithData.getMountPoint();
+ final YangInstanceIdentifier normalizedII = iiWithData.getInstanceIdentifier();
+
+ final FluentFuture<? extends CommitInfo> future;
+ if (mountPoint != null) {
+ future = broker.commitConfigurationDataDelete(mountPoint, normalizedII);
+ } else {
+ future = broker.commitConfigurationDataDelete(normalizedII);
+ }
+
+ try {
+ future.get();
+ } catch (final InterruptedException e) {
+ throw new RestconfDocumentedException(e.getMessage(), e);
+ } catch (final ExecutionException e) {
+ final Optional<Throwable> searchedException = Iterables.tryFind(Throwables.getCausalChain(e),
+ Predicates.instanceOf(ModifiedNodeDoesNotExistException.class)).toJavaUtil();
+ if (searchedException.isPresent()) {
+ throw new RestconfDocumentedException("Data specified for delete doesn't exist.", ErrorType.APPLICATION,
+ ErrorTag.DATA_MISSING, e);
+ }
+
+ throw RestconfDocumentedException.decodeAndThrow(e.getMessage(), Throwables.getCauseAs(e,
+ TransactionCommitFailedException.class));
+ }
+
+ return Response.status(Status.OK).build();
+ }
+
+ /**
+ * Subscribes to some path in schema context (stream) to listen on changes
+ * on this stream.
+ *
+ * <p>
+ * Additional parameters for subscribing to stream are loaded via rpc input
+ * parameters:
+ * <ul>
+ * <li>datastore - default CONFIGURATION (other values of
+ * {@link LogicalDatastoreType} enum type)</li>
+ * <li>scope - default BASE (other values of {@link Scope})</li>
+ * </ul>
+ */
+ @Override
+ public NormalizedNodeContext subscribeToStream(final String identifier, final UriInfo uriInfo) {
+ boolean startTimeUsed = false;
+ boolean stopTimeUsed = false;
+ Instant start = Instant.now();
+ Instant stop = null;
+ boolean filterUsed = false;
+ String filter = null;
+ boolean leafNodesOnlyUsed = false;
+ boolean leafNodesOnly = false;
+ boolean skipNotificationDataUsed = false;
+ boolean skipNotificationData = false;
+
+ for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
+ switch (entry.getKey()) {
+ case "start-time":
+ if (!startTimeUsed) {
+ startTimeUsed = true;
+ start = parseDateFromQueryParam(entry);
+ } else {
+ throw new RestconfDocumentedException("Start-time parameter can be used only once.");
+ }
+ break;
+ case "stop-time":
+ if (!stopTimeUsed) {
+ stopTimeUsed = true;
+ stop = parseDateFromQueryParam(entry);
+ } else {
+ throw new RestconfDocumentedException("Stop-time parameter can be used only once.");
+ }
+ break;
+ case "filter":
+ if (!filterUsed) {
+ filterUsed = true;
+ filter = entry.getValue().iterator().next();
+ } else {
+ throw new RestconfDocumentedException("Filter parameter can be used only once.");
+ }
+ break;
+ case "odl-leaf-nodes-only":
+ if (!leafNodesOnlyUsed) {
+ leafNodesOnlyUsed = true;
+ leafNodesOnly = Boolean.parseBoolean(entry.getValue().iterator().next());
+ } else {
+ throw new RestconfDocumentedException("Odl-leaf-nodes-only parameter can be used only once.");
+ }
+ break;
+ case "odl-skip-notification-data":
+ if (!skipNotificationDataUsed) {
+ skipNotificationDataUsed = true;
+ skipNotificationData = Boolean.parseBoolean(entry.getValue().iterator().next());
+ } else {
+ throw new RestconfDocumentedException(
+ "Odl-skip-notification-data parameter can be used only once.");
+ }
+ break;
+ default:
+ throw new RestconfDocumentedException("Bad parameter used with notifications: " + entry.getKey());
+ }
+ }
+ if (!startTimeUsed && stopTimeUsed) {
+ throw new RestconfDocumentedException("Stop-time parameter has to be used with start-time parameter.");
+ }
+ URI response = null;
+ if (identifier.contains(DATA_SUBSCR)) {
+ response = dataSubs(identifier, uriInfo, start, stop, filter, leafNodesOnly, skipNotificationData);
+ } else if (identifier.contains(NOTIFICATION_STREAM)) {
+ response = notifStream(identifier, uriInfo, start, stop, filter);
+ }
+
+ if (response != null) {
+ // prepare node with value of location
+
+ final QName qnameBase = QName.create("subscribe:to:notification", "2016-10-28", "notifi");
+ final QName locationQName = QName.create(qnameBase, "location");
+
+ final var stack = SchemaInferenceStack.of(controllerContext.getGlobalSchema());
+ stack.enterSchemaTree(qnameBase);
+ stack.enterSchemaTree(locationQName);
+
+ // prepare new header with location
+ return new NormalizedNodeContext(InstanceIdentifierContext.ofStack(stack),
+ ImmutableNodes.leafNode(locationQName, response.toString()), ImmutableMap.of("Location", response));
+ }
+
+ final String msg = "Bad type of notification of sal-remote";
+ LOG.warn(msg);
+ throw new RestconfDocumentedException(msg);
+ }
+
+ private static Instant parseDateFromQueryParam(final Entry<String, List<String>> entry) {
+ final DateAndTime event = new DateAndTime(entry.getValue().iterator().next());
+ final String value = event.getValue();
+ final TemporalAccessor p;
+ try {
+ p = FORMATTER.parse(value);
+ } catch (final DateTimeParseException e) {
+ throw new RestconfDocumentedException("Cannot parse of value in date: " + value, e);
+ }
+ return Instant.from(p);
+ }
+
+ /**
+ * Register notification listener by stream name.
+ *
+ * @param identifier
+ * stream name
+ * @param uriInfo
+ * uriInfo
+ * @param stop
+ * stop-time of getting notification
+ * @param start
+ * start-time of getting notification
+ * @param filter
+ * indicate which subset of all possible events are of interest
+ * @return {@link URI} of location
+ */
+ private URI notifStream(final String identifier, final UriInfo uriInfo, final Instant start,
+ final Instant stop, final String filter) {
+ final String streamName = Notificator.createStreamNameFromUri(identifier);
+ if (Strings.isNullOrEmpty(streamName)) {
+ throw new RestconfDocumentedException("Stream name is empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+ final List<NotificationListenerAdapter> listeners = Notificator.getNotificationListenerFor(streamName);
+ if (listeners == null || listeners.isEmpty()) {
+ throw new RestconfDocumentedException("Stream was not found.", ErrorType.PROTOCOL,
+ ErrorTag.UNKNOWN_ELEMENT);
+ }
+
+ for (final NotificationListenerAdapter listener : listeners) {
+ broker.registerToListenNotification(listener);
+ listener.setQueryParams(start, Optional.ofNullable(stop), Optional.ofNullable(filter), false, false);
+ }
+
+ final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
+
+ final WebSocketServer webSocketServerInstance = WebSocketServer.getInstance(NOTIFICATION_PORT);
+ final int notificationPort = webSocketServerInstance.getPort();
+
+
+ final UriBuilder uriToWebsocketServerBuilder = uriBuilder.port(notificationPort).scheme(getWsScheme(uriInfo));
+
+ return uriToWebsocketServerBuilder.replacePath(streamName).build();
+ }
+
+ private static String getWsScheme(final UriInfo uriInfo) {
+ URI uri = uriInfo.getAbsolutePath();
+ if (uri == null) {
+ return "ws";
+ }
+ String subscriptionScheme = uri.getScheme().toLowerCase(Locale.ROOT);
+ return subscriptionScheme.equals("https") ? "wss" : "ws";
+ }
+
+ /**
+ * Register data change listener by stream name.
+ *
+ * @param identifier
+ * stream name
+ * @param uriInfo
+ * uri info
+ * @param stop
+ * start-time of getting notification
+ * @param start
+ * stop-time of getting notification
+ * @param filter
+ * indicate which subset of all possible events are of interest
+ * @return {@link URI} of location
+ */
+ private URI dataSubs(final String identifier, final UriInfo uriInfo, final Instant start, final Instant stop,
+ final String filter, final boolean leafNodesOnly, final boolean skipNotificationData) {
+ final String streamName = Notificator.createStreamNameFromUri(identifier);
+ if (Strings.isNullOrEmpty(streamName)) {
+ throw new RestconfDocumentedException("Stream name is empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ final ListenerAdapter listener = Notificator.getListenerFor(streamName);
+ if (listener == null) {
+ throw new RestconfDocumentedException("Stream was not found.", ErrorType.PROTOCOL,
+ ErrorTag.UNKNOWN_ELEMENT);
+ }
+ listener.setQueryParams(start, Optional.ofNullable(stop), Optional.ofNullable(filter), leafNodesOnly,
+ skipNotificationData);
+
+ final Map<String, String> paramToValues = resolveValuesFromUri(identifier);
+ final LogicalDatastoreType datastore =
+ parserURIEnumParameter(LogicalDatastoreType.class, paramToValues.get(DATASTORE_PARAM_NAME));
+ if (datastore == null) {
+ throw new RestconfDocumentedException("Stream name doesn't contains datastore value (pattern /datastore=)",
+ ErrorType.APPLICATION, ErrorTag.MISSING_ATTRIBUTE);
+ }
+ final Scope scope = parserURIEnumParameter(Scope.class, paramToValues.get(SCOPE_PARAM_NAME));
+ if (scope == null) {
+ throw new RestconfDocumentedException("Stream name doesn't contains datastore value (pattern /scope=)",
+ ErrorType.APPLICATION, ErrorTag.MISSING_ATTRIBUTE);
+ }
+
+ broker.registerToListenDataChanges(datastore, scope, listener);
+
+ final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
+
+ final WebSocketServer webSocketServerInstance = WebSocketServer.getInstance(NOTIFICATION_PORT);
+ final int notificationPort = webSocketServerInstance.getPort();
+
+ final UriBuilder uriToWebsocketServerBuilder = uriBuilder.port(notificationPort).scheme(getWsScheme(uriInfo));
+
+ return uriToWebsocketServerBuilder.replacePath(streamName).build();
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public PatchStatusContext patchConfigurationData(final String identifier, final PatchContext context,
+ final UriInfo uriInfo) {
+ if (context == null) {
+ throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+ }
+
+ try {
+ return broker.patchConfigurationDataWithinTransaction(context);
+ } catch (final Exception e) {
+ LOG.debug("Patch transaction failed", e);
+ throw new RestconfDocumentedException(e.getMessage(), e);
+ }
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public PatchStatusContext patchConfigurationData(final PatchContext context, @Context final UriInfo uriInfo) {
+ if (context == null) {
+ throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+ }
+
+ try {
+ return broker.patchConfigurationDataWithinTransaction(context);
+ } catch (final Exception e) {
+ LOG.debug("Patch transaction failed", e);
+ throw new RestconfDocumentedException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Load parameter for subscribing to stream from input composite node.
+ *
+ * @param value
+ * contains value
+ * @return enum object if its string value is equal to {@code paramName}. In
+ * other cases null.
+ */
+ private static <T> T parseEnumTypeParameter(final ContainerNode value, final Class<T> classDescriptor,
+ final String paramName) {
+ final Optional<DataContainerChild> optAugNode = value.findChildByArg(SAL_REMOTE_AUG_IDENTIFIER);
+ if (optAugNode.isEmpty()) {
+ return null;
+ }
+ final DataContainerChild augNode = optAugNode.get();
+ if (!(augNode instanceof AugmentationNode)) {
+ return null;
+ }
+ final Optional<DataContainerChild> enumNode = ((AugmentationNode) augNode).findChildByArg(
+ new NodeIdentifier(QName.create(SAL_REMOTE_AUGMENT, paramName)));
+ if (enumNode.isEmpty()) {
+ return null;
+ }
+ final Object rawValue = enumNode.get().body();
+ if (!(rawValue instanceof String)) {
+ return null;
+ }
+
+ return resolveAsEnum(classDescriptor, (String) rawValue);
+ }
+
+ /**
+ * Checks whether {@code value} is one of the string representation of
+ * enumeration {@code classDescriptor}.
+ *
+ * @return enum object if string value of {@code classDescriptor}
+ * enumeration is equal to {@code value}. Other cases null.
+ */
+ private static <T> T parserURIEnumParameter(final Class<T> classDescriptor, final String value) {
+ if (Strings.isNullOrEmpty(value)) {
+ return null;
+ }
+ return resolveAsEnum(classDescriptor, value);
+ }
+
+ private static <T> T resolveAsEnum(final Class<T> classDescriptor, final String value) {
+ final T[] enumConstants = classDescriptor.getEnumConstants();
+ if (enumConstants != null) {
+ for (final T enm : classDescriptor.getEnumConstants()) {
+ if (((Enum<?>) enm).name().equals(value)) {
+ return enm;
+ }
+ }
+ }
+ return null;
+ }
+
+ private static Map<String, String> resolveValuesFromUri(final String uri) {
+ final Map<String, String> result = new HashMap<>();
+ final String[] tokens = uri.split("/");
+ for (int i = 1; i < tokens.length; i++) {
+ final String[] parameterTokens = tokens[i].split("=");
+ if (parameterTokens.length == 2) {
+ result.put(parameterTokens[0], parameterTokens[1]);
+ }
+ }
+ return result;
+ }
+
+ private MapNode makeModuleMapNode(final Collection<? extends Module> modules) {
+ requireNonNull(modules);
+ final Module restconfModule = getRestconfModule();
+ final DataSchemaNode moduleSchemaNode = controllerContext
+ .getRestconfModuleRestConfSchemaNode(restconfModule, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE);
+ checkState(moduleSchemaNode instanceof ListSchemaNode);
+
+ final CollectionNodeBuilder<MapEntryNode, SystemMapNode> listModuleBuilder =
+ SchemaAwareBuilders.mapBuilder((ListSchemaNode) moduleSchemaNode);
+
+ for (final Module module : modules) {
+ listModuleBuilder.withChild(toModuleEntryNode(module, moduleSchemaNode));
+ }
+ return listModuleBuilder.build();
+ }
+
+ private static MapEntryNode toModuleEntryNode(final Module module, final DataSchemaNode moduleSchemaNode) {
+ checkArgument(moduleSchemaNode instanceof ListSchemaNode,
+ "moduleSchemaNode has to be of type ListSchemaNode");
+ final ListSchemaNode listModuleSchemaNode = (ListSchemaNode) moduleSchemaNode;
+ final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> moduleNodeValues =
+ SchemaAwareBuilders.mapEntryBuilder(listModuleSchemaNode);
+
+ var instanceDataChildrenByName =
+ ControllerContext.findInstanceDataChildrenByName(listModuleSchemaNode, "name");
+ final LeafSchemaNode nameSchemaNode = getFirstLeaf(instanceDataChildrenByName);
+ moduleNodeValues.withChild(
+ SchemaAwareBuilders.leafBuilder(nameSchemaNode).withValue(module.getName()).build());
+
+ final QNameModule qNameModule = module.getQNameModule();
+
+ instanceDataChildrenByName =
+ ControllerContext.findInstanceDataChildrenByName(listModuleSchemaNode, "revision");
+ final LeafSchemaNode revisionSchemaNode = getFirstLeaf(instanceDataChildrenByName);
+ final Optional<Revision> revision = qNameModule.getRevision();
+ moduleNodeValues.withChild(SchemaAwareBuilders.leafBuilder(revisionSchemaNode)
+ .withValue(revision.map(Revision::toString).orElse("")).build());
+
+ instanceDataChildrenByName =
+ ControllerContext.findInstanceDataChildrenByName(listModuleSchemaNode, "namespace");
+ final LeafSchemaNode namespaceSchemaNode = getFirstLeaf(instanceDataChildrenByName);
+ moduleNodeValues.withChild(SchemaAwareBuilders.leafBuilder(namespaceSchemaNode)
+ .withValue(qNameModule.getNamespace().toString()).build());
+
+ instanceDataChildrenByName =
+ ControllerContext.findInstanceDataChildrenByName(listModuleSchemaNode, "feature");
+ final LeafListSchemaNode featureSchemaNode = getFirst(instanceDataChildrenByName, LeafListSchemaNode.class);
+ final ListNodeBuilder<Object, SystemLeafSetNode<Object>> featuresBuilder =
+ SchemaAwareBuilders.leafSetBuilder(featureSchemaNode);
+ for (final FeatureDefinition feature : module.getFeatures()) {
+ featuresBuilder.withChild(SchemaAwareBuilders.leafSetEntryBuilder(featureSchemaNode)
+ .withValue(feature.getQName().getLocalName()).build());
+ }
+ moduleNodeValues.withChild(featuresBuilder.build());
+
+ return moduleNodeValues.build();
+ }
+
+ protected MapEntryNode toStreamEntryNode(final String streamName, final DataSchemaNode streamSchemaNode) {
+ checkArgument(streamSchemaNode instanceof ListSchemaNode,
+ "streamSchemaNode has to be of type ListSchemaNode");
+ final ListSchemaNode listStreamSchemaNode = (ListSchemaNode) streamSchemaNode;
+ final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> streamNodeValues =
+ SchemaAwareBuilders.mapEntryBuilder(listStreamSchemaNode);
+
+ var instanceDataChildrenByName =
+ ControllerContext.findInstanceDataChildrenByName(listStreamSchemaNode, "name");
+ final LeafSchemaNode nameSchemaNode = getFirstLeaf(instanceDataChildrenByName);
+ streamNodeValues.withChild(
+ SchemaAwareBuilders.leafBuilder(nameSchemaNode).withValue(streamName).build());
+
+ instanceDataChildrenByName =
+ ControllerContext.findInstanceDataChildrenByName(listStreamSchemaNode, "description");
+ final LeafSchemaNode descriptionSchemaNode = getFirstLeaf(instanceDataChildrenByName);
+ streamNodeValues.withChild(SchemaAwareBuilders.leafBuilder(descriptionSchemaNode)
+ .withValue("DESCRIPTION_PLACEHOLDER")
+ .build());
+
+ instanceDataChildrenByName =
+ ControllerContext.findInstanceDataChildrenByName(listStreamSchemaNode, "replay-support");
+ final LeafSchemaNode replaySupportSchemaNode = getFirstLeaf(instanceDataChildrenByName);
+ streamNodeValues.withChild(SchemaAwareBuilders.leafBuilder(replaySupportSchemaNode)
+ .withValue(Boolean.TRUE).build());
+
+ instanceDataChildrenByName =
+ ControllerContext.findInstanceDataChildrenByName(listStreamSchemaNode, "replay-log-creation-time");
+ final LeafSchemaNode replayLogCreationTimeSchemaNode = getFirstLeaf(instanceDataChildrenByName);
+ streamNodeValues.withChild(
+ SchemaAwareBuilders.leafBuilder(replayLogCreationTimeSchemaNode).withValue("").build());
+
+ instanceDataChildrenByName = ControllerContext.findInstanceDataChildrenByName(listStreamSchemaNode, "events");
+ final LeafSchemaNode eventsSchemaNode = getFirstLeaf(instanceDataChildrenByName);
+ streamNodeValues.withChild(
+ SchemaAwareBuilders.leafBuilder(eventsSchemaNode).withValue(Empty.value()).build());
+
+ return streamNodeValues.build();
+ }
+
+ /**
+ * Prepare stream for notification.
+ *
+ * @param payload
+ * contains list of qnames of notifications
+ * @return - checked future object
+ */
+ private ListenableFuture<DOMRpcResult> invokeSalRemoteRpcNotifiStrRPC(final NormalizedNodeContext payload) {
+ final ContainerNode data = (ContainerNode) payload.getData();
+ LeafSetNode leafSet = null;
+ String outputType = "XML";
+ for (final DataContainerChild dataChild : data.body()) {
+ if (dataChild instanceof LeafSetNode) {
+ leafSet = (LeafSetNode) dataChild;
+ } else if (dataChild instanceof AugmentationNode) {
+ outputType = (String) ((AugmentationNode) dataChild).body().iterator().next().body();
+ }
+ }
+
+ final Collection<LeafSetEntryNode<?>> entryNodes = leafSet.body();
+ final List<Absolute> paths = new ArrayList<>();
+
+ StringBuilder streamNameBuilder = new StringBuilder(CREATE_NOTIFICATION_STREAM).append('/');
+ final Iterator<LeafSetEntryNode<?>> iterator = entryNodes.iterator();
+ while (iterator.hasNext()) {
+ final QName valueQName = QName.create((String) iterator.next().body());
+ final XMLNamespace namespace = valueQName.getModule().getNamespace();
+ final Module module = controllerContext.findModuleByNamespace(namespace);
+ checkNotNull(module, "Module for namespace %s does not exist", namespace);
+ NotificationDefinition notifiDef = null;
+ for (final NotificationDefinition notification : module.getNotifications()) {
+ if (notification.getQName().equals(valueQName)) {
+ notifiDef = notification;
+ break;
+ }
+ }
+ final String moduleName = module.getName();
+ if (notifiDef == null) {
+ throw new IllegalArgumentException("Notification " + valueQName + " does not exist in module "
+ + moduleName);
+ }
+
+ paths.add(Absolute.of(notifiDef.getQName()));
+ streamNameBuilder.append(moduleName).append(':').append(valueQName.getLocalName());
+ if (iterator.hasNext()) {
+ streamNameBuilder.append(',');
+ }
+ }
+
+ final String streamName = streamNameBuilder.toString();
+ final QName rpcQName = payload.getInstanceIdentifierContext().getSchemaNode().getQName();
+
+ if (!Notificator.existNotificationListenerFor(streamName)) {
+ Notificator.createNotificationListener(paths, streamName, outputType, controllerContext);
+ }
+
+ return Futures.immediateFuture(new DefaultDOMRpcResult(Builders.containerBuilder()
+ .withNodeIdentifier(new NodeIdentifier(QName.create(rpcQName, "output")))
+ .withChild(ImmutableNodes.leafNode(QName.create(rpcQName, "notification-stream-identifier"), streamName))
+ .build()));
+ }
+
+ private static LeafSchemaNode getFirstLeaf(final List<FoundChild> children) {
+ return getFirst(children, LeafSchemaNode.class);
+ }
+
+ private static <T extends DataSchemaNode> T getFirst(final List<FoundChild> children, final Class<T> expected) {
+ checkState(!children.isEmpty());
+ final var first = children.get(0);
+ checkState(expected.isInstance(first.child));
+ return expected.cast(first.child);
+ }
+
+ private static EffectiveModelContext modelContext(final DOMMountPoint mountPoint) {
+ return mountPoint.getService(DOMSchemaService.class)
+ .flatMap(svc -> Optional.ofNullable(svc.getGlobalContext()))
+ .orElse(null);
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfProviderImpl.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfProviderImpl.java
new file mode 100644
index 0000000..5b6608e
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfProviderImpl.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import java.math.BigInteger;
+import org.opendaylight.controller.md.sal.common.util.jmx.AbstractMXBean;
+import org.opendaylight.netconf.sal.rest.api.RestConnector;
+import org.opendaylight.netconf.sal.restconf.impl.jmx.Config;
+import org.opendaylight.netconf.sal.restconf.impl.jmx.Delete;
+import org.opendaylight.netconf.sal.restconf.impl.jmx.Get;
+import org.opendaylight.netconf.sal.restconf.impl.jmx.Operational;
+import org.opendaylight.netconf.sal.restconf.impl.jmx.Post;
+import org.opendaylight.netconf.sal.restconf.impl.jmx.Put;
+import org.opendaylight.netconf.sal.restconf.impl.jmx.RestConnectorRuntimeMXBean;
+import org.opendaylight.netconf.sal.restconf.impl.jmx.Rpcs;
+import org.opendaylight.netconf.sal.streams.websockets.WebSocketServer;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.PortNumber;
+
+public class RestconfProviderImpl extends AbstractMXBean
+ implements AutoCloseable, RestConnector, RestConnectorRuntimeMXBean {
+ private final IpAddress websocketAddress;
+ private final PortNumber websocketPort;
+ private final StatisticsRestconfServiceWrapper stats;
+ private Thread webSocketServerThread;
+
+ public RestconfProviderImpl(final StatisticsRestconfServiceWrapper stats, final IpAddress websocketAddress,
+ final PortNumber websocketPort) {
+ super("Draft02ProviderStatistics", "restconf-connector", null);
+ this.stats = requireNonNull(stats);
+ this.websocketAddress = requireNonNull(websocketAddress);
+ this.websocketPort = requireNonNull(websocketPort);
+ }
+
+ public void start() {
+ this.webSocketServerThread = new Thread(WebSocketServer.createInstance(
+ websocketAddress.stringValue(), websocketPort.getValue().toJava()));
+ this.webSocketServerThread.setName("Web socket server on port " + websocketPort);
+ this.webSocketServerThread.start();
+
+ registerMBean();
+ }
+
+ @Override
+ public void close() {
+ WebSocketServer.destroyInstance();
+ if (this.webSocketServerThread != null) {
+ this.webSocketServerThread.interrupt();
+ }
+
+ unregisterMBean();
+ }
+
+ @Override
+ public Config getConfig() {
+ final Config config = new Config();
+
+ final Get get = new Get();
+ get.setReceivedRequests(this.stats.getConfigGet());
+ get.setSuccessfulResponses(this.stats.getSuccessGetConfig());
+ get.setFailedResponses(this.stats.getFailureGetConfig());
+ config.setGet(get);
+
+ final Post post = new Post();
+ post.setReceivedRequests(this.stats.getConfigPost());
+ post.setSuccessfulResponses(this.stats.getSuccessPost());
+ post.setFailedResponses(this.stats.getFailurePost());
+ config.setPost(post);
+
+ final Put put = new Put();
+ put.setReceivedRequests(this.stats.getConfigPut());
+ put.setSuccessfulResponses(this.stats.getSuccessPut());
+ put.setFailedResponses(this.stats.getFailurePut());
+ config.setPut(put);
+
+ final Delete delete = new Delete();
+ delete.setReceivedRequests(this.stats.getConfigDelete());
+ delete.setSuccessfulResponses(this.stats.getSuccessDelete());
+ delete.setFailedResponses(this.stats.getFailureDelete());
+ config.setDelete(delete);
+
+ return config;
+ }
+
+ @Override
+ public Operational getOperational() {
+ final BigInteger opGet = this.stats.getOperationalGet();
+ final Operational operational = new Operational();
+ final Get get = new Get();
+ get.setReceivedRequests(opGet);
+ get.setSuccessfulResponses(this.stats.getSuccessGetOperational());
+ get.setFailedResponses(this.stats.getFailureGetOperational());
+ operational.setGet(get);
+ return operational;
+ }
+
+ @Override
+ public Rpcs getRpcs() {
+ final BigInteger rpcInvoke = this.stats.getRpc();
+ final Rpcs rpcs = new Rpcs();
+ rpcs.setReceivedRequests(rpcInvoke);
+ return rpcs;
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/StatisticsRestconfServiceWrapper.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/StatisticsRestconfServiceWrapper.java
new file mode 100644
index 0000000..afe4be0
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/StatisticsRestconfServiceWrapper.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import java.math.BigInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriInfo;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+
+@Singleton
+public final class StatisticsRestconfServiceWrapper implements RestconfService {
+
+ AtomicLong operationalGet = new AtomicLong();
+ AtomicLong configGet = new AtomicLong();
+ AtomicLong rpc = new AtomicLong();
+ AtomicLong configPost = new AtomicLong();
+ AtomicLong configPut = new AtomicLong();
+ AtomicLong configDelete = new AtomicLong();
+ AtomicLong successGetConfig = new AtomicLong();
+ AtomicLong successGetOperational = new AtomicLong();
+ AtomicLong successPost = new AtomicLong();
+ AtomicLong successPut = new AtomicLong();
+ AtomicLong successDelete = new AtomicLong();
+ AtomicLong failureGetConfig = new AtomicLong();
+ AtomicLong failureGetOperational = new AtomicLong();
+ AtomicLong failurePost = new AtomicLong();
+ AtomicLong failurePut = new AtomicLong();
+ AtomicLong failureDelete = new AtomicLong();
+
+ private final RestconfService delegate;
+
+ @Inject
+ public StatisticsRestconfServiceWrapper(final RestconfImpl delegate) {
+ this.delegate = delegate;
+ }
+
+ /**
+ * Factory method.
+ *
+ * @deprecated Just use {@link #StatisticsRestconfServiceWrapper(RestconfImpl)} constructor instead.
+ */
+ @Deprecated
+ public static StatisticsRestconfServiceWrapper newInstance(RestconfImpl delegate) {
+ return new StatisticsRestconfServiceWrapper(delegate);
+ }
+
+ @Override
+ public Object getRoot() {
+ return this.delegate.getRoot();
+ }
+
+ @Override
+ public NormalizedNodeContext getModules(final UriInfo uriInfo) {
+ return this.delegate.getModules(uriInfo);
+ }
+
+ @Override
+ public NormalizedNodeContext getModules(final String identifier, final UriInfo uriInfo) {
+ return this.delegate.getModules(identifier, uriInfo);
+ }
+
+ @Override
+ public NormalizedNodeContext getModule(final String identifier, final UriInfo uriInfo) {
+ return this.delegate.getModule(identifier, uriInfo);
+ }
+
+ @Override
+ public String getOperationsJSON() {
+ return this.delegate.getOperationsJSON();
+ }
+
+ @Override
+ public String getOperationsXML() {
+ return this.delegate.getOperationsXML();
+ }
+
+ @Override
+ public NormalizedNodeContext getOperations(final String identifier, final UriInfo uriInfo) {
+ return this.delegate.getOperations(identifier, uriInfo);
+ }
+
+ @Override
+ public NormalizedNodeContext invokeRpc(final String identifier, final NormalizedNodeContext payload,
+ final UriInfo uriInfo) {
+ this.rpc.incrementAndGet();
+ return this.delegate.invokeRpc(identifier, payload, uriInfo);
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public NormalizedNodeContext readConfigurationData(final String identifier, final UriInfo uriInfo) {
+ this.configGet.incrementAndGet();
+ NormalizedNodeContext normalizedNodeContext = null;
+ try {
+ normalizedNodeContext = this.delegate.readConfigurationData(identifier, uriInfo);
+ if (normalizedNodeContext.getData() != null) {
+ this.successGetConfig.incrementAndGet();
+ } else {
+ this.failureGetConfig.incrementAndGet();
+ }
+ } catch (final Exception e) {
+ this.failureGetConfig.incrementAndGet();
+ throw e;
+ }
+ return normalizedNodeContext;
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public NormalizedNodeContext readOperationalData(final String identifier, final UriInfo uriInfo) {
+ this.operationalGet.incrementAndGet();
+ NormalizedNodeContext normalizedNodeContext = null;
+ try {
+ normalizedNodeContext = this.delegate.readOperationalData(identifier, uriInfo);
+ if (normalizedNodeContext.getData() != null) {
+ this.successGetOperational.incrementAndGet();
+ } else {
+ this.failureGetOperational.incrementAndGet();
+ }
+ } catch (final Exception e) {
+ this.failureGetOperational.incrementAndGet();
+ throw e;
+ }
+ return normalizedNodeContext;
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public Response updateConfigurationData(final String identifier, final NormalizedNodeContext payload,
+ final UriInfo uriInfo) {
+ this.configPut.incrementAndGet();
+ Response response = null;
+ try {
+ response = this.delegate.updateConfigurationData(identifier, payload, uriInfo);
+ if (response.getStatus() == Status.OK.getStatusCode()) {
+ this.successPut.incrementAndGet();
+ } else {
+ this.failurePut.incrementAndGet();
+ }
+ } catch (final Exception e) {
+ this.failurePut.incrementAndGet();
+ throw e;
+ }
+ return response;
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public Response createConfigurationData(final String identifier, final NormalizedNodeContext payload,
+ final UriInfo uriInfo) {
+ this.configPost.incrementAndGet();
+ Response response = null;
+ try {
+ response = this.delegate.createConfigurationData(identifier, payload, uriInfo);
+ if (response.getStatus() == Status.OK.getStatusCode()) {
+ this.successPost.incrementAndGet();
+ } else {
+ this.failurePost.incrementAndGet();
+ }
+ } catch (final Exception e) {
+ this.failurePost.incrementAndGet();
+ throw e;
+ }
+ return response;
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public Response createConfigurationData(final NormalizedNodeContext payload, final UriInfo uriInfo) {
+ this.configPost.incrementAndGet();
+ Response response = null;
+ try {
+ response = this.delegate.createConfigurationData(payload, uriInfo);
+ if (response.getStatus() == Status.OK.getStatusCode()) {
+ this.successPost.incrementAndGet();
+ } else {
+ this.failurePost.incrementAndGet();
+ }
+ } catch (final Exception e) {
+ this.failurePost.incrementAndGet();
+ throw e;
+ }
+ return response;
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public Response deleteConfigurationData(final String identifier) {
+ this.configDelete.incrementAndGet();
+ Response response = null;
+ try {
+ response = this.delegate.deleteConfigurationData(identifier);
+ if (response.getStatus() == Status.OK.getStatusCode()) {
+ this.successDelete.incrementAndGet();
+ } else {
+ this.failureDelete.incrementAndGet();
+ }
+ } catch (final Exception e) {
+ this.failureDelete.incrementAndGet();
+ throw e;
+ }
+ return response;
+ }
+
+ @Override
+ public NormalizedNodeContext subscribeToStream(final String identifier, final UriInfo uriInfo) {
+ return this.delegate.subscribeToStream(identifier, uriInfo);
+ }
+
+ @Override
+ public NormalizedNodeContext getAvailableStreams(final UriInfo uriInfo) {
+ return this.delegate.getAvailableStreams(uriInfo);
+ }
+
+ @Override
+ public PatchStatusContext patchConfigurationData(final String identifier, final PatchContext payload,
+ final UriInfo uriInfo) {
+ return this.delegate.patchConfigurationData(identifier, payload, uriInfo);
+ }
+
+ @Override
+ public PatchStatusContext patchConfigurationData(final PatchContext payload, final UriInfo uriInfo) {
+ return this.delegate.patchConfigurationData(payload, uriInfo);
+ }
+
+ public BigInteger getConfigDelete() {
+ return BigInteger.valueOf(this.configDelete.get());
+ }
+
+ public BigInteger getConfigGet() {
+ return BigInteger.valueOf(this.configGet.get());
+ }
+
+ public BigInteger getConfigPost() {
+ return BigInteger.valueOf(this.configPost.get());
+ }
+
+ public BigInteger getConfigPut() {
+ return BigInteger.valueOf(this.configPut.get());
+ }
+
+ public BigInteger getOperationalGet() {
+ return BigInteger.valueOf(this.operationalGet.get());
+ }
+
+ public BigInteger getRpc() {
+ return BigInteger.valueOf(this.rpc.get());
+ }
+
+ public BigInteger getSuccessGetConfig() {
+ return BigInteger.valueOf(this.successGetConfig.get());
+ }
+
+ public BigInteger getSuccessGetOperational() {
+ return BigInteger.valueOf(this.successGetOperational.get());
+ }
+
+ public BigInteger getSuccessPost() {
+ return BigInteger.valueOf(this.successPost.get());
+ }
+
+ public BigInteger getSuccessPut() {
+ return BigInteger.valueOf(this.successPut.get());
+ }
+
+ public BigInteger getSuccessDelete() {
+ return BigInteger.valueOf(this.successDelete.get());
+ }
+
+ public BigInteger getFailureGetConfig() {
+ return BigInteger.valueOf(this.failureGetConfig.get());
+ }
+
+ public BigInteger getFailureGetOperational() {
+ return BigInteger.valueOf(this.failureGetOperational.get());
+ }
+
+ public BigInteger getFailurePost() {
+ return BigInteger.valueOf(this.failurePost.get());
+ }
+
+ public BigInteger getFailurePut() {
+ return BigInteger.valueOf(this.failurePut.get());
+ }
+
+ public BigInteger getFailureDelete() {
+ return BigInteger.valueOf(this.failureDelete.get());
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Config.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Config.java
new file mode 100644
index 0000000..a115254
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Config.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2017 Inocybe Technologies and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl.jmx;
+
+public class Config {
+ private Delete delete;
+
+ private Post post;
+
+ private Get get;
+
+ private Put put;
+
+ public Delete getDelete() {
+ return delete;
+ }
+
+ public void setDelete(Delete delete) {
+ this.delete = delete;
+ }
+
+ public Post getPost() {
+ return post;
+ }
+
+ public void setPost(Post post) {
+ this.post = post;
+ }
+
+ public Get getGet() {
+ return get;
+ }
+
+ public void setGet(Get get) {
+ this.get = get;
+ }
+
+ public Put getPut() {
+ return put;
+ }
+
+ public void setPut(Put put) {
+ this.put = put;
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(delete, post, get, put);
+
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final Config that = (Config) obj;
+ if (!java.util.Objects.equals(delete, that.delete)) {
+ return false;
+ }
+
+ if (!java.util.Objects.equals(post, that.post)) {
+ return false;
+ }
+
+ if (!java.util.Objects.equals(get, that.get)) {
+ return false;
+ }
+
+ return java.util.Objects.equals(put, that.put);
+
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Delete.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Delete.java
new file mode 100644
index 0000000..16a815b
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Delete.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2017 Inocybe Technologies and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl.jmx;
+
+import java.math.BigInteger;
+
+public class Delete {
+ private BigInteger successfulResponses;
+
+ private BigInteger receivedRequests;
+
+ private BigInteger failedResponses;
+
+ public BigInteger getSuccessfulResponses() {
+ return successfulResponses;
+ }
+
+ public void setSuccessfulResponses(BigInteger successfulResponses) {
+ this.successfulResponses = successfulResponses;
+ }
+
+ public BigInteger getReceivedRequests() {
+ return receivedRequests;
+ }
+
+ public void setReceivedRequests(BigInteger receivedRequests) {
+ this.receivedRequests = receivedRequests;
+ }
+
+ public BigInteger getFailedResponses() {
+ return failedResponses;
+ }
+
+ public void setFailedResponses(BigInteger failedResponses) {
+ this.failedResponses = failedResponses;
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(successfulResponses, receivedRequests, failedResponses);
+
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final Delete that = (Delete) obj;
+ if (!java.util.Objects.equals(successfulResponses, that.successfulResponses)) {
+ return false;
+ }
+
+ if (!java.util.Objects.equals(receivedRequests, that.receivedRequests)) {
+ return false;
+ }
+
+ return java.util.Objects.equals(failedResponses, that.failedResponses);
+
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Get.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Get.java
new file mode 100644
index 0000000..45603a8
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Get.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2017 Inocybe Technologies and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl.jmx;
+
+import java.math.BigInteger;
+
+public class Get {
+ private BigInteger successfulResponses;
+
+ private BigInteger receivedRequests;
+
+ private BigInteger failedResponses;
+
+ public BigInteger getSuccessfulResponses() {
+ return successfulResponses;
+ }
+
+ public void setSuccessfulResponses(BigInteger successfulResponses) {
+ this.successfulResponses = successfulResponses;
+ }
+
+ public BigInteger getReceivedRequests() {
+ return receivedRequests;
+ }
+
+ public void setReceivedRequests(BigInteger receivedRequests) {
+ this.receivedRequests = receivedRequests;
+ }
+
+ public BigInteger getFailedResponses() {
+ return failedResponses;
+ }
+
+ public void setFailedResponses(BigInteger failedResponses) {
+ this.failedResponses = failedResponses;
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(successfulResponses, receivedRequests, failedResponses);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final Get that = (Get) obj;
+ if (!java.util.Objects.equals(successfulResponses, that.successfulResponses)) {
+ return false;
+ }
+
+ if (!java.util.Objects.equals(receivedRequests, that.receivedRequests)) {
+ return false;
+ }
+
+ return java.util.Objects.equals(failedResponses, that.failedResponses);
+
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Operational.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Operational.java
new file mode 100644
index 0000000..5d9989b
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Operational.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2017 Inocybe Technologies and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl.jmx;
+
+public class Operational {
+ private Get get;
+
+ public Get getGet() {
+ return get;
+ }
+
+ public void setGet(Get get) {
+ this.get = get;
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(get);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final Operational that = (Operational) obj;
+ return java.util.Objects.equals(get, that.get);
+
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Post.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Post.java
new file mode 100644
index 0000000..a466c01
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Post.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2017 Inocybe Technologies and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl.jmx;
+
+import java.math.BigInteger;
+
+public class Post {
+ private BigInteger successfulResponses;
+
+ private BigInteger receivedRequests;
+
+ private BigInteger failedResponses;
+
+ public BigInteger getSuccessfulResponses() {
+ return successfulResponses;
+ }
+
+ public void setSuccessfulResponses(BigInteger successfulResponses) {
+ this.successfulResponses = successfulResponses;
+ }
+
+ public BigInteger getReceivedRequests() {
+ return receivedRequests;
+ }
+
+ public void setReceivedRequests(BigInteger receivedRequests) {
+ this.receivedRequests = receivedRequests;
+ }
+
+ public BigInteger getFailedResponses() {
+ return failedResponses;
+ }
+
+ public void setFailedResponses(BigInteger failedResponses) {
+ this.failedResponses = failedResponses;
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(successfulResponses, receivedRequests, failedResponses);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final Post that = (Post) obj;
+ if (!java.util.Objects.equals(successfulResponses, that.successfulResponses)) {
+ return false;
+ }
+
+ if (!java.util.Objects.equals(receivedRequests, that.receivedRequests)) {
+ return false;
+ }
+
+ return java.util.Objects.equals(failedResponses, that.failedResponses);
+
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Put.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Put.java
new file mode 100644
index 0000000..589f02c
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Put.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2017 Inocybe Technologies and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl.jmx;
+
+import java.math.BigInteger;
+
+public class Put {
+ private BigInteger successfulResponses;
+
+ private BigInteger receivedRequests;
+
+ private BigInteger failedResponses;
+
+ public BigInteger getSuccessfulResponses() {
+ return successfulResponses;
+ }
+
+ public void setSuccessfulResponses(BigInteger successfulResponses) {
+ this.successfulResponses = successfulResponses;
+ }
+
+ public BigInteger getReceivedRequests() {
+ return receivedRequests;
+ }
+
+ public void setReceivedRequests(BigInteger receivedRequests) {
+ this.receivedRequests = receivedRequests;
+ }
+
+ public BigInteger getFailedResponses() {
+ return failedResponses;
+ }
+
+ public void setFailedResponses(BigInteger failedResponses) {
+ this.failedResponses = failedResponses;
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(successfulResponses, receivedRequests, failedResponses);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final Put that = (Put) obj;
+ if (!java.util.Objects.equals(successfulResponses, that.successfulResponses)) {
+ return false;
+ }
+
+ if (!java.util.Objects.equals(receivedRequests, that.receivedRequests)) {
+ return false;
+ }
+
+ return java.util.Objects.equals(failedResponses, that.failedResponses);
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/RestConnectorRuntimeMXBean.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/RestConnectorRuntimeMXBean.java
new file mode 100644
index 0000000..cafcb8f
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/RestConnectorRuntimeMXBean.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2017 Inocybe Technologies and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl.jmx;
+
+public interface RestConnectorRuntimeMXBean {
+ Operational getOperational();
+
+ Rpcs getRpcs();
+
+ Config getConfig();
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Rpcs.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Rpcs.java
new file mode 100644
index 0000000..d849a4d
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Rpcs.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2017 Inocybe Technologies and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl.jmx;
+
+import java.math.BigInteger;
+
+public class Rpcs {
+ private BigInteger successfulResponses;
+
+ private BigInteger receivedRequests;
+
+ private BigInteger failedResponses;
+
+ public BigInteger getSuccessfulResponses() {
+ return successfulResponses;
+ }
+
+ public void setSuccessfulResponses(BigInteger successfulResponses) {
+ this.successfulResponses = successfulResponses;
+ }
+
+ public BigInteger getReceivedRequests() {
+ return receivedRequests;
+ }
+
+ public void setReceivedRequests(BigInteger receivedRequests) {
+ this.receivedRequests = receivedRequests;
+ }
+
+ public BigInteger getFailedResponses() {
+ return failedResponses;
+ }
+
+ public void setFailedResponses(BigInteger failedResponses) {
+ this.failedResponses = failedResponses;
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(successfulResponses, receivedRequests, failedResponses);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final Rpcs that = (Rpcs) obj;
+ if (!java.util.Objects.equals(successfulResponses, that.successfulResponses)) {
+ return false;
+ }
+
+ if (!java.util.Objects.equals(receivedRequests, that.receivedRequests)) {
+ return false;
+ }
+
+ return java.util.Objects.equals(failedResponses, that.failedResponses);
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/package-info.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/package-info.java
new file mode 100644
index 0000000..d67ee37
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/package-info.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2017 Inocybe Technologies and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+/**
+ * This package contains the statistical JMX classes for the Draft02 restconf implementation. Originally these classes
+ * were generated by the CSS code generator and were moved to this package on conversion to blueprint.
+ */
+package org.opendaylight.netconf.sal.restconf.impl.jmx;
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/package-info.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/package-info.java
new file mode 100644
index 0000000..e78fddd
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/package-info.java
@@ -0,0 +1,8 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl; \ No newline at end of file