summaryrefslogtreecommitdiffstats
path: root/netconf/restconf/restconf-common/src
diff options
context:
space:
mode:
Diffstat (limited to 'netconf/restconf/restconf-common/src')
-rw-r--r--netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/ErrorTags.java99
-rw-r--r--netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/OperationsContent.java156
-rw-r--r--netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/context/InstanceIdentifierContext.java242
-rw-r--r--netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/errors/RestconfDocumentedException.java276
-rw-r--r--netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/errors/RestconfError.java195
-rw-r--r--netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/schema/SchemaExportContext.java38
-rw-r--r--netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/AbstractOperationDataSchemaNode.java50
-rw-r--r--netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/AbstractOperationsModule.java176
-rw-r--r--netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/IdentityValuesDTO.java139
-rw-r--r--netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/OperationsContainerSchemaNode.java100
-rw-r--r--netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/OperationsEffectiveModuleContext.java39
-rw-r--r--netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/OperationsImportedModule.java56
-rw-r--r--netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/OperationsLeafSchemaNode.java53
-rw-r--r--netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/OperationsResourceUtils.java70
-rw-r--r--netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/OperationsRestconfModule.java62
-rw-r--r--netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/RestUtil.java184
-rw-r--r--netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/SimpleUriInfo.java130
-rw-r--r--netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/validation/RestconfValidationUtils.java46
18 files changed, 2111 insertions, 0 deletions
diff --git a/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/ErrorTags.java b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/ErrorTags.java
new file mode 100644
index 0000000..952218a
--- /dev/null
+++ b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/ErrorTags.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2021 PANTHEON.tech, 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.restconf.common;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableMap;
+import javax.ws.rs.core.Response.Status;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link ErrorTag} mapping to HTTP errors. Aside from the mappings defined by
+ * <a href="https://datatracker.ietf.org/doc/html/rfc8040#section-7">RFC8040 section 7</a>, we also define tags which
+ * map to useful {@link Status} codes.
+ */
+@Beta
+@NonNullByDefault
+public final class ErrorTags {
+ /**
+ * Error reported when the request is valid, but the resource cannot be accessed. This tag typically maps to
+ * {@link Status#SERVICE_UNAVAILABLE}.
+ */
+ // FIXME: redefine as SERVICE_UNAVAILABLE? It would be more obvious
+ public static final ErrorTag RESOURCE_DENIED_TRANSPORT = new ErrorTag("resource-denied-transport");
+
+ private static final Logger LOG = LoggerFactory.getLogger(ErrorTags.class);
+ private static final ImmutableMap<ErrorTag, Status> WELL_KNOWN_ERROR_TAGS = ImmutableMap.<ErrorTag, Status>builder()
+ .put(ErrorTag.IN_USE, Status.CONFLICT)
+ .put(ErrorTag.INVALID_VALUE, Status.BAD_REQUEST)
+ .put(ErrorTag.TOO_BIG, Status.REQUEST_ENTITY_TOO_LARGE)
+ .put(ErrorTag.MISSING_ATTRIBUTE, Status.BAD_REQUEST)
+ .put(ErrorTag.BAD_ATTRIBUTE, Status.BAD_REQUEST)
+ .put(ErrorTag.UNKNOWN_ATTRIBUTE, Status.BAD_REQUEST)
+ .put(ErrorTag.MISSING_ELEMENT, Status.BAD_REQUEST)
+ .put(ErrorTag.BAD_ELEMENT, Status.BAD_REQUEST)
+ .put(ErrorTag.UNKNOWN_ELEMENT, Status.BAD_REQUEST)
+ .put(ErrorTag.UNKNOWN_NAMESPACE, Status.BAD_REQUEST)
+
+ .put(ErrorTag.ACCESS_DENIED, Status.FORBIDDEN)
+ .put(ErrorTag.LOCK_DENIED, Status.CONFLICT)
+ .put(ErrorTag.RESOURCE_DENIED, Status.CONFLICT)
+ .put(ErrorTag.ROLLBACK_FAILED, Status.INTERNAL_SERVER_ERROR)
+ .put(ErrorTag.DATA_EXISTS, Status.CONFLICT)
+ .put(ErrorTag.DATA_MISSING, dataMissingHttpStatus())
+
+ .put(ErrorTag.OPERATION_NOT_SUPPORTED, Status.NOT_IMPLEMENTED)
+ .put(ErrorTag.OPERATION_FAILED, Status.INTERNAL_SERVER_ERROR)
+ .put(ErrorTag.PARTIAL_OPERATION, Status.INTERNAL_SERVER_ERROR)
+ .put(ErrorTag.MALFORMED_MESSAGE, Status.BAD_REQUEST)
+ .put(ErrorTags.RESOURCE_DENIED_TRANSPORT, Status.SERVICE_UNAVAILABLE)
+ .build();
+
+ private ErrorTags() {
+ // Hidden on purpose
+ }
+
+ /**
+ * Return the HTTP {@link Status} corresponding to specified {@link ErrorTag}.
+ *
+ * @param tag Error tag to map
+ * @return HTTP Status
+ * @throws NullPointerException if {@code tag} is null
+ */
+ public static Status statusOf(final ErrorTag tag) {
+ final Status known = WELL_KNOWN_ERROR_TAGS.get(requireNonNull(tag));
+ return known != null ? known : Status.INTERNAL_SERVER_ERROR;
+ }
+
+ private static Status dataMissingHttpStatus() {
+ // Control over the HTTP status reported on "data-missing" conditions. This defaults to disabled,
+ // HTTP status 409 as specified by RFC8040 (and all previous drafts). See the discussion in:
+ // https://www.rfc-editor.org/errata/eid5565
+ // https://mailarchive.ietf.org/arch/msg/netconf/hkVDdHK4xA74NgvXzWP0zObMiyY/
+ final String propName = "org.opendaylight.restconf.eid5565";
+ final String propValue = System.getProperty(propName, "disabled");
+ switch (propValue) {
+ case "enabled":
+ // RFC7231 interpretation: 404 Not Found
+ LOG.info("RESTCONF data-missing condition is reported as HTTP status 404 (Errata 5565)");
+ return Status.NOT_FOUND;
+ case "disabled":
+ break;
+ default:
+ LOG.warn("Unhandled {} value \"{}\", assuming disabled", propName, propValue);
+ }
+
+ // RFC8040 specification: 409 Conflict
+ return Status.CONFLICT;
+ }
+}
diff --git a/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/OperationsContent.java b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/OperationsContent.java
new file mode 100644
index 0000000..199728f
--- /dev/null
+++ b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/OperationsContent.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2021 PANTHEON.tech, 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.restconf.common;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.HashBasedTable;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.yang.common.Revision;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement;
+
+/**
+ * RESTCONF {@code /operations} content for a {@code GET} operation as per
+ * <a href="https://datatracker.ietf.org/doc/html/rfc8040#section-3.3.2">RFC8040</a>.
+ */
+// FIXME: when bierman02 is gone, this should be folded to nb-rfc8040, as it is a server-side thing.
+public enum OperationsContent {
+ JSON("{ \"ietf-restconf:operations\" : { } }") {
+ @Override
+ String createBody(final List<Entry<String, List<String>>> rpcsByPrefix) {
+ final var sb = new StringBuilder("{\n"
+ + " \"ietf-restconf:operations\" : {\n");
+ var entryIt = rpcsByPrefix.iterator();
+ var entry = entryIt.next();
+ var nameIt = entry.getValue().iterator();
+ while (true) {
+ sb.append(" \"").append(entry.getKey()).append(':').append(nameIt.next()).append("\": [null]");
+ if (nameIt.hasNext()) {
+ sb.append(",\n");
+ continue;
+ }
+
+ if (entryIt.hasNext()) {
+ sb.append(",\n");
+ entry = entryIt.next();
+ nameIt = entry.getValue().iterator();
+ continue;
+ }
+
+ break;
+ }
+
+ return sb.append("\n }\n}").toString();
+ }
+
+ @Override
+ String prefix(final ModuleEffectiveStatement module) {
+ return module.argument().getLocalName();
+ }
+ },
+
+ XML("<operations xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"/>") {
+ @Override
+ String createBody(final List<Entry<String, List<String>>> rpcsByPrefix) {
+ // Header with namespace declarations for each module
+ final var sb = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<operations xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"");
+ for (int i = 0; i < rpcsByPrefix.size(); ++i) {
+ final var prefix = "ns" + i;
+ sb.append("\n xmlns:").append(prefix).append("=\"").append(rpcsByPrefix.get(i).getKey())
+ .append("\"");
+ }
+ sb.append(" >");
+
+ // Second pass: emit all leaves
+ for (int i = 0; i < rpcsByPrefix.size(); ++i) {
+ final var prefix = "ns" + i;
+ for (var localName : rpcsByPrefix.get(i).getValue()) {
+ sb.append("\n <").append(prefix).append(':').append(localName).append("/>");
+ }
+ }
+
+ return sb.append("\n</operations>").toString();
+ }
+
+ @Override
+ String prefix(final ModuleEffectiveStatement module) {
+ return module.localQNameModule().getNamespace().toString();
+ }
+ };
+
+ private final @NonNull String emptyBody;
+
+ OperationsContent(final String emptyBody) {
+ this.emptyBody = requireNonNull(emptyBody);
+ }
+
+ /**
+ * Return the content for a particular {@link EffectiveModelContext}.
+ *
+ * @param context Context to use
+ * @return Content of HTTP GET operation as a String
+ */
+ public final @NonNull String bodyFor(final @Nullable EffectiveModelContext context) {
+ if (context == null) {
+ return emptyBody;
+ }
+ final var modules = context.getModuleStatements();
+ if (modules.isEmpty()) {
+ return emptyBody;
+ }
+
+ // Index into prefix -> revision -> module table
+ final var prefixRevModule = HashBasedTable.<String, Optional<Revision>, ModuleEffectiveStatement>create();
+ for (var module : modules.values()) {
+ prefixRevModule.put(prefix(module), module.localQNameModule().getRevision(), module);
+ }
+
+ // Now extract RPC names for each module with highest revision. This needed so we expose the right set of RPCs,
+ // as we always pick the latest revision to resolve prefix (or module name)
+ // TODO: Simplify this once we have yangtools-7.0.9+
+ final var moduleRpcs = new ArrayList<Entry<String, List<String>>>();
+ for (var moduleEntry : prefixRevModule.rowMap().entrySet()) {
+ final var revisions = new ArrayList<>(moduleEntry.getValue().keySet());
+ revisions.sort(Revision::compare);
+ final var selectedRevision = revisions.get(revisions.size() - 1);
+
+ final var rpcNames = moduleEntry.getValue().get(selectedRevision)
+ .streamEffectiveSubstatements(RpcEffectiveStatement.class)
+ .map(rpc -> rpc.argument().getLocalName())
+ .collect(Collectors.toUnmodifiableList());
+ if (!rpcNames.isEmpty()) {
+ moduleRpcs.add(Map.entry(moduleEntry.getKey(), rpcNames));
+ }
+ }
+
+ if (moduleRpcs.isEmpty()) {
+ // No RPCs, return empty content
+ return emptyBody;
+ }
+
+ // Ensure stability: sort by prefix
+ moduleRpcs.sort(Comparator.comparing(Entry::getKey));
+
+ return modules.isEmpty() ? emptyBody : createBody(moduleRpcs);
+ }
+
+ abstract @NonNull String createBody(List<Entry<String, List<String>>> rpcsByPrefix);
+
+ abstract @NonNull String prefix(ModuleEffectiveStatement module);
+}
diff --git a/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/context/InstanceIdentifierContext.java b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/context/InstanceIdentifierContext.java
new file mode 100644
index 0000000..81577a0
--- /dev/null
+++ b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/context/InstanceIdentifierContext.java
@@ -0,0 +1,242 @@
+/*
+ * 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.restconf.common.context;
+
+import static com.google.common.base.Verify.verify;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Iterables;
+import java.util.List;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.mdsal.dom.api.DOMMountPoint;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
+
+public abstract class InstanceIdentifierContext {
+ private static final class Root extends InstanceIdentifierContext {
+ private final @NonNull EffectiveModelContext context;
+
+ Root(final EffectiveModelContext context, final DOMMountPoint mountPoint) {
+ super(context, mountPoint);
+ this.context = requireNonNull(context);
+ }
+
+ @Override
+ public EffectiveModelContext getSchemaContext() {
+ return context;
+ }
+
+ @Override
+ public YangInstanceIdentifier getInstanceIdentifier() {
+ return YangInstanceIdentifier.empty();
+ }
+
+ @Override
+ public Inference inference() {
+ return SchemaInferenceStack.of(context).toInference();
+ }
+
+ @Override
+ InstanceIdentifierContext createWithConcapt(final List<PathArgument> concatArgs) {
+ return new DataPath(context, getMountPoint(), SchemaInferenceStack.of(context),
+ YangInstanceIdentifier.create(concatArgs));
+ }
+ }
+
+ private static final class DataPath extends InstanceIdentifierContext {
+ private final @NonNull YangInstanceIdentifier path;
+ private final @NonNull SchemaInferenceStack stack;
+
+ private DataPath(final SchemaNode schemaNode, final DOMMountPoint mountPoint,
+ final SchemaInferenceStack stack, final YangInstanceIdentifier path) {
+ super(schemaNode, mountPoint);
+ this.stack = requireNonNull(stack);
+ this.path = requireNonNull(path);
+ }
+
+ static @NonNull DataPath of(final EffectiveModelContext context, final YangInstanceIdentifier path,
+ final DOMMountPoint mountPoint) {
+ final var nodeAndStack = DataSchemaContextTree.from(context).enterPath(path).orElseThrow();
+ return new DataPath(nodeAndStack.node().getDataSchemaNode(), mountPoint, nodeAndStack.stack(), path);
+ }
+
+ @Override
+ public YangInstanceIdentifier getInstanceIdentifier() {
+ return path;
+ }
+
+ @Override
+ public Inference inference() {
+ return stack.toInference();
+ }
+
+ @Override
+ @NonNull
+ InstanceIdentifierContext createWithConcapt(final List<PathArgument> concatArgs) {
+ final var newInstanceIdentifier = YangInstanceIdentifier.create(
+ Iterables.concat(path.getPathArguments(), concatArgs));
+ return new DataPath(getSchemaNode(), getMountPoint(), stack, newInstanceIdentifier);
+ }
+ }
+
+ private static final class WithoutDataPath extends InstanceIdentifierContext {
+ private final @NonNull SchemaInferenceStack stack;
+
+ private WithoutDataPath(final SchemaNode schemaNode, final DOMMountPoint mountPoint,
+ final SchemaInferenceStack stack) {
+ super(schemaNode, mountPoint);
+ this.stack = requireNonNull(stack);
+ }
+
+ @Override
+ public Inference inference() {
+ return stack.toInference();
+ }
+
+ @Override
+ public @Nullable YangInstanceIdentifier getInstanceIdentifier() {
+ return null;
+ }
+
+ @Override
+ InstanceIdentifierContext createWithConcapt(final List<PathArgument> concatArgs) {
+ return this;
+ }
+ }
+
+ private final @NonNull SchemaNode schemaNode;
+ private final @Nullable DOMMountPoint mountPoint;
+
+ InstanceIdentifierContext(final SchemaNode schemaNode, final DOMMountPoint mountPoint) {
+ this.schemaNode = requireNonNull(schemaNode);
+ this.mountPoint = mountPoint;
+ }
+
+ public static @NonNull InstanceIdentifierContext ofLocalRoot(final EffectiveModelContext context) {
+ return new Root(context, null);
+ }
+
+ @VisibleForTesting
+ public static @NonNull InstanceIdentifierContext ofLocalPath(final EffectiveModelContext context,
+ final YangInstanceIdentifier path) {
+ return DataPath.of(context, path, null);
+ }
+
+ // Legacy bierman02 invokeRpc()
+ public static @NonNull InstanceIdentifierContext ofRpcInput(final EffectiveModelContext context,
+ // FIXME: this this method really needed?
+ final RpcDefinition rpc, final @Nullable DOMMountPoint mountPoint) {
+ final var stack = SchemaInferenceStack.of(context);
+ stack.enterSchemaTree(rpc.getQName());
+ stack.enterSchemaTree(rpc.getInput().getQName());
+ return new WithoutDataPath(rpc, mountPoint, stack);
+ }
+
+ public static @NonNull InstanceIdentifierContext ofRpcOutput(final EffectiveModelContext context,
+ // FIXME: this this method really needed?
+ final RpcDefinition rpc, final @Nullable DOMMountPoint mountPoint) {
+ final var stack = SchemaInferenceStack.of(context);
+ stack.enterSchemaTree(rpc.getQName());
+ stack.enterSchemaTree(rpc.getOutput().getQName());
+ return new WithoutDataPath(rpc, mountPoint, stack);
+ }
+
+ // Invocations of various identifier-less details
+ public static @NonNull InstanceIdentifierContext ofStack(final SchemaInferenceStack stack) {
+ return ofStack(stack, null);
+ }
+
+ // Invocations of various identifier-less details, potentially having a mount point
+ public static @NonNull InstanceIdentifierContext ofStack(final SchemaInferenceStack stack,
+ final @Nullable DOMMountPoint mountPoint) {
+ final SchemaNode schemaNode;
+ if (!stack.isEmpty()) {
+ final var stmt = stack.currentStatement();
+ verify(stmt instanceof SchemaNode, "Unexpected statement %s", stmt);
+ schemaNode = (SchemaNode) stmt;
+ } else {
+ schemaNode = stack.getEffectiveModelContext();
+ }
+
+ return new WithoutDataPath(schemaNode, mountPoint, stack);
+ }
+
+ public static @NonNull InstanceIdentifierContext ofLocalRpcInput(final EffectiveModelContext context,
+ // FIXME: this this method really needed?
+ final RpcDefinition rpc) {
+ final var stack = SchemaInferenceStack.of(context);
+ stack.enterSchemaTree(rpc.getQName());
+ stack.enterSchemaTree(rpc.getInput().getQName());
+ return new WithoutDataPath(rpc.getInput(), null, stack);
+ }
+
+ public static @NonNull InstanceIdentifierContext ofLocalRpcOutput(final EffectiveModelContext context,
+ // FIXME: we want to re-validate this, so might as well take a QName
+ final RpcDefinition rpc) {
+ final var stack = SchemaInferenceStack.of(context);
+ stack.enterSchemaTree(rpc.getQName());
+ stack.enterSchemaTree(rpc.getOutput().getQName());
+ return new WithoutDataPath(rpc, null, stack);
+ }
+
+ public static @NonNull InstanceIdentifierContext ofPath(final SchemaInferenceStack stack,
+ final SchemaNode schemaNode, final YangInstanceIdentifier path,
+ final @Nullable DOMMountPoint mountPoint) {
+ return new DataPath(schemaNode, mountPoint, stack, path);
+ }
+
+ public static @NonNull InstanceIdentifierContext ofMountPointRoot(final DOMMountPoint mountPoint,
+ final EffectiveModelContext mountContext) {
+ return new Root(mountContext, requireNonNull(mountPoint));
+ }
+
+ @VisibleForTesting
+ public static @NonNull InstanceIdentifierContext ofMountPointPath(final DOMMountPoint mountPoint,
+ final EffectiveModelContext context, final YangInstanceIdentifier path) {
+ return DataPath.of(context, path, requireNonNull(mountPoint));
+ }
+
+ public static @NonNull InstanceIdentifierContext ofMountPointRpcOutput(final DOMMountPoint mountPoint,
+ final EffectiveModelContext mountContext, final RpcDefinition rpc) {
+ final var stack = SchemaInferenceStack.of(mountContext);
+ stack.enterSchemaTree(rpc.getQName());
+ stack.enterSchemaTree(rpc.getOutput().getQName());
+ return new WithoutDataPath(rpc, requireNonNull(mountPoint), stack);
+ }
+
+ // FIXME: what the heck are the callers of this doing?!
+ public final @NonNull InstanceIdentifierContext withConcatenatedArgs(final List<PathArgument> concatArgs) {
+ return concatArgs.isEmpty() ? this : createWithConcapt(concatArgs);
+ }
+
+ abstract @NonNull InstanceIdentifierContext createWithConcapt(List<PathArgument> concatArgs);
+
+ public final @NonNull SchemaNode getSchemaNode() {
+ return schemaNode;
+ }
+
+ public final @Nullable DOMMountPoint getMountPoint() {
+ return mountPoint;
+ }
+
+ public @NonNull EffectiveModelContext getSchemaContext() {
+ return inference().getEffectiveModelContext();
+ }
+
+ public abstract @NonNull Inference inference();
+
+ public abstract @Nullable YangInstanceIdentifier getInstanceIdentifier();
+}
diff --git a/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/errors/RestconfDocumentedException.java b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/errors/RestconfDocumentedException.java
new file mode 100644
index 0000000..e0431f1
--- /dev/null
+++ b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/errors/RestconfDocumentedException.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (c) 2014 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.restconf.common.errors;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response.Status;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.restconf.common.ErrorTags;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+import org.opendaylight.yangtools.yang.common.OperationFailedException;
+import org.opendaylight.yangtools.yang.common.RpcError;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangNetconfError;
+import org.opendaylight.yangtools.yang.data.api.YangNetconfErrorAware;
+
+/**
+ * Unchecked exception to communicate error information, as defined in the ietf restcong draft, to be sent to the
+ * client.
+ *
+ * <p>
+ * See also <a href="https://tools.ietf.org/html/draft-bierman-netconf-restconf-02">RESTCONF</a>
+ *
+ * @author Devin Avery
+ * @author Thomas Pantelis
+ */
+public class RestconfDocumentedException extends WebApplicationException {
+ private static final long serialVersionUID = 1L;
+
+ private final ImmutableList<RestconfError> errors;
+ private final Status status;
+
+ /**
+ * Constructs an instance with an error message. The error type defaults to APPLICATION and the error tag defaults
+ * to OPERATION_FAILED.
+ *
+ * @param message
+ * A string which provides a plain text string describing the error.
+ */
+ public RestconfDocumentedException(final String message) {
+ this(message, ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED);
+ }
+
+ /**
+ * Constructs an instance with an error message, error type, error tag and exception cause.
+ *
+ * @param message
+ * A string which provides a plain text string describing the error.
+ * @param errorType
+ * The enumerated type indicating the layer where the error occurred.
+ * @param errorTag
+ * The enumerated tag representing a more specific error cause.
+ * @param cause
+ * The underlying exception cause.
+ */
+ public RestconfDocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
+ final Throwable cause) {
+ this(cause, new RestconfError(errorType, errorTag, message, null, cause.getMessage(), null));
+ }
+
+ /**
+ * Constructs an instance with an error message, error type, and error tag.
+ *
+ * @param message
+ * A string which provides a plain text string describing the error.
+ * @param errorType
+ * The enumerated type indicating the layer where the error occurred.
+ * @param errorTag
+ * The enumerated tag representing a more specific error cause.
+ */
+ public RestconfDocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag) {
+ this(null, new RestconfError(errorType, errorTag, message));
+ }
+
+ /**
+ * Constructs an instance with an error message, error type, error tag and error path.
+ *
+ * @param message
+ * A string which provides a plain text string describing the error.
+ * @param errorType
+ * The enumerated type indicating the layer where the error occurred.
+ * @param errorTag
+ * The enumerated tag representing a more specific error cause.
+ * @param errorPath
+ * The instance identifier representing error path
+ */
+ public RestconfDocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
+ final YangInstanceIdentifier errorPath) {
+ this(null, new RestconfError(errorType, errorTag, message, errorPath));
+ }
+
+ /**
+ * Constructs an instance with an error message and exception cause.
+ * The underlying exception is included in the error-info.
+ *
+ * @param message
+ * A string which provides a plain text string describing the error.
+ * @param cause
+ * The underlying exception cause.
+ */
+ public RestconfDocumentedException(final String message, final Throwable cause) {
+ this(cause, new RestconfError(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, message, null,
+ cause.getMessage(), null));
+ }
+
+ /**
+ * Constructs an instance with the given error.
+ */
+ public RestconfDocumentedException(final RestconfError error) {
+ this(null, error);
+ }
+
+ /**
+ * Constructs an instance with the given errors.
+ */
+ public RestconfDocumentedException(final String message, final Throwable cause, final List<RestconfError> errors) {
+ // FIXME: We override getMessage so supplied message is lost for any public access
+ // this was lost also in original code.
+ super(cause);
+ if (!errors.isEmpty()) {
+ this.errors = ImmutableList.copyOf(errors);
+ } else {
+ this.errors = ImmutableList.of(
+ new RestconfError(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, message));
+ }
+
+ status = null;
+ }
+
+ /**
+ * Constructs an instance with the given RpcErrors.
+ */
+ public RestconfDocumentedException(final String message, final Throwable cause,
+ final Collection<? extends RpcError> rpcErrors) {
+ this(message, cause, convertToRestconfErrors(rpcErrors));
+ }
+
+ /**
+ * Constructs an instance with an HTTP status and no error information.
+ *
+ * @param status
+ * the HTTP status.
+ */
+ public RestconfDocumentedException(final Status status) {
+ errors = ImmutableList.of();
+ this.status = requireNonNull(status, "Status can't be null");
+ }
+
+ public RestconfDocumentedException(final Throwable cause, final RestconfError error) {
+ super(cause, ErrorTags.statusOf(error.getErrorTag()));
+ errors = ImmutableList.of(error);
+ status = null;
+ }
+
+ public RestconfDocumentedException(final Throwable cause, final List<RestconfError> errors) {
+ super(cause, ErrorTags.statusOf(errors.get(0).getErrorTag()));
+ this.errors = ImmutableList.copyOf(errors);
+ status = null;
+ }
+
+ public static RestconfDocumentedException decodeAndThrow(final String message,
+ final OperationFailedException cause) {
+ for (final RpcError error : cause.getErrorList()) {
+ if (error.getErrorType() == ErrorType.TRANSPORT && error.getTag().equals(ErrorTag.RESOURCE_DENIED)) {
+ throw new RestconfDocumentedException(error.getMessage(), ErrorType.TRANSPORT,
+ ErrorTags.RESOURCE_DENIED_TRANSPORT, cause);
+ }
+ }
+ throw new RestconfDocumentedException(message, cause, cause.getErrorList());
+ }
+
+ /**
+ * Throw an instance of this exception if an expression evaluates to true. If the expression evaluates to false,
+ * this method does nothing.
+ *
+ * @param expression Expression to be evaluated
+ * @param errorType The enumerated type indicating the layer where the error occurred.
+ * @param errorTag The enumerated tag representing a more specific error cause.
+ * @param format Format string, according to {@link String#format(String, Object...)}.
+ * @param args Format string arguments, according to {@link String#format(String, Object...)}
+ * @throws RestconfDocumentedException if the expression evaluates to true.
+ */
+ public static void throwIf(final boolean expression, final ErrorType errorType, final ErrorTag errorTag,
+ final @NonNull String format, final Object... args) {
+ if (expression) {
+ throw new RestconfDocumentedException(String.format(format, args), errorType, errorTag);
+ }
+ }
+
+ /**
+ * Throw an instance of this exception if an expression evaluates to true. If the expression evaluates to false,
+ * this method does nothing.
+ *
+ * @param expression Expression to be evaluated
+ * @param message error message
+ * @param errorType The enumerated type indicating the layer where the error occurred.
+ * @param errorTag The enumerated tag representing a more specific error cause.
+ * @throws RestconfDocumentedException if the expression evaluates to true.
+ */
+ public static void throwIf(final boolean expression, final @NonNull String message,
+ final ErrorType errorType, final ErrorTag errorTag) {
+ if (expression) {
+ throw new RestconfDocumentedException(message, errorType, errorTag);
+ }
+ }
+
+ /**
+ * Throw an instance of this exception if an object is null. If the object is non-null, it will
+ * be returned as the result of this method.
+ *
+ * @param obj Object reference to be checked
+ * @param errorType The enumerated type indicating the layer where the error occurred.
+ * @param errorTag The enumerated tag representing a more specific error cause.
+ * @param format Format string, according to {@link String#format(String, Object...)}.
+ * @param args Format string arguments, according to {@link String#format(String, Object...)}
+ * @throws RestconfDocumentedException if the expression evaluates to true.
+ */
+ public static <T> @NonNull T throwIfNull(final @Nullable T obj, final ErrorType errorType, final ErrorTag errorTag,
+ final @NonNull String format, final Object... args) {
+ if (obj == null) {
+ throw new RestconfDocumentedException(String.format(format, args), errorType, errorTag);
+ }
+ return obj;
+ }
+
+ /**
+ * Throw an instance of this exception if the specified exception has a {@link YangNetconfError} attachment.
+ *
+ * @param cause Proposed cause of a RestconfDocumented exception
+ */
+ public static void throwIfYangError(final Throwable cause) {
+ if (cause instanceof YangNetconfErrorAware) {
+ throw new RestconfDocumentedException(cause, ((YangNetconfErrorAware) cause).getNetconfErrors().stream()
+ .map(error -> new RestconfError(error.type(), error.tag(), error.message(), error.appTag(),
+ // FIXME: pass down error info
+ null, error.path()))
+ .collect(ImmutableList.toImmutableList()));
+ }
+ }
+
+ private static List<RestconfError> convertToRestconfErrors(final Collection<? extends RpcError> rpcErrors) {
+ final List<RestconfError> errorList = new ArrayList<>();
+ if (rpcErrors != null) {
+ for (RpcError rpcError : rpcErrors) {
+ errorList.add(new RestconfError(rpcError));
+ }
+ }
+
+ return errorList;
+ }
+
+ public List<RestconfError> getErrors() {
+ return errors;
+ }
+
+ public Status getStatus() {
+ return status;
+ }
+
+ @Override
+ public String getMessage() {
+ return "errors: " + errors + (status != null ? ", status: " + status : "");
+ }
+}
diff --git a/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/errors/RestconfError.java b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/errors/RestconfError.java
new file mode 100644
index 0000000..0919c08
--- /dev/null
+++ b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/errors/RestconfError.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2014 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.restconf.common.errors;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.Serializable;
+import java.util.Locale;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+import org.opendaylight.yangtools.yang.common.RpcError;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+
+/**
+ * Encapsulates a restconf error as defined in the ietf restconf draft.
+ *
+ * <br>
+ * <br>
+ * <b>Note:</b> Enumerations defined within are provided by the ietf restconf draft.
+ *
+ * @author Devin Avery
+ * See also <a href="https://tools.ietf.org/html/draft-bierman-netconf-restconf-02">RESTCONF</a>.
+ */
+public class RestconfError implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private final ErrorType errorType;
+ private final ErrorTag errorTag;
+ private final String errorInfo;
+ private final String errorAppTag;
+ private final String errorMessage;
+ private final YangInstanceIdentifier errorPath;
+
+ /**
+ * Constructs a RestConfError.
+ *
+ * @param errorType
+ * The enumerated type indicating the layer where the error occurred.
+ * @param errorTag
+ * The enumerated tag representing a more specific error cause.
+ * @param errorMessage
+ * A string which provides a plain text string describing the error.
+ */
+ public RestconfError(final ErrorType errorType, final ErrorTag errorTag, final String errorMessage) {
+ this(errorType, errorTag, errorMessage, null, null, null);
+ }
+
+ /**
+ * Constructs a RestConfError object.
+ *
+ * @param errorType
+ * The enumerated type indicating the layer where the error occurred.
+ * @param errorTag
+ * The enumerated tag representing a more specific error cause.
+ * @param errorMessage
+ * A string which provides a plain text string describing the error.
+ * @param errorAppTag
+ * A string which represents an application-specific error tag that further specifies the error cause.
+ */
+ public RestconfError(final ErrorType errorType, final ErrorTag errorTag, final String errorMessage,
+ final String errorAppTag) {
+ this(errorType, errorTag, errorMessage, errorAppTag, null, null);
+ }
+
+ /**
+ * Constructs a RestConfError object.
+ *
+ * @param errorType
+ * The enumerated type indicating the layer where the error occurred.
+ * @param errorTag
+ * The enumerated tag representing a more specific error cause.
+ * @param errorMessage
+ * A string which provides a plain text string describing the error.
+ * @param errorPath
+ * An instance identifier which contains error path
+ */
+ public RestconfError(final ErrorType errorType, final ErrorTag errorTag, final String errorMessage,
+ final YangInstanceIdentifier errorPath) {
+ this(errorType, errorTag, errorMessage, null, null, errorPath);
+ }
+
+ /**
+ * Constructs a RestConfError object.
+ *
+ * @param errorType
+ * The enumerated type indicating the layer where the error occurred.
+ * @param errorTag
+ * The enumerated tag representing a more specific error cause.
+ * @param errorMessage
+ * A string which provides a plain text string describing the error.
+ * @param errorAppTag
+ * A string which represents an application-specific error tag that further specifies the error cause.
+ * @param errorInfo
+ * A string, <b>formatted as XML</b>, which contains additional error information.
+ */
+ public RestconfError(final ErrorType errorType, final ErrorTag errorTag, final String errorMessage,
+ final String errorAppTag, final String errorInfo) {
+ this(errorType, errorTag, errorMessage, errorAppTag, errorInfo, null);
+ }
+
+ /**
+ * Constructs a RestConfError object.
+ *
+ * @param errorType
+ * The enumerated type indicating the layer where the error occurred.
+ * @param errorTag
+ * The enumerated tag representing a more specific error cause.
+ * @param errorMessage
+ * A string which provides a plain text string describing the error.
+ * @param errorAppTag
+ * A string which represents an application-specific error tag that further specifies the error cause.
+ * @param errorInfo
+ * A string, <b>formatted as XML</b>, which contains additional error information.
+ * @param errorPath
+ * An instance identifier which contains error path
+ */
+ public RestconfError(final ErrorType errorType, final ErrorTag errorTag, final String errorMessage,
+ final String errorAppTag, final String errorInfo, final YangInstanceIdentifier errorPath) {
+ this.errorType = requireNonNull(errorType, "Error type is required for RestConfError");
+ this.errorTag = requireNonNull(errorTag, "Error tag is required for RestConfError");
+ this.errorMessage = errorMessage;
+ this.errorAppTag = errorAppTag;
+ this.errorInfo = errorInfo;
+ this.errorPath = errorPath;
+ }
+
+ /**
+ * Constructs a RestConfError object from an RpcError.
+ */
+ public RestconfError(final RpcError rpcError) {
+
+ errorType = rpcError.getErrorType();
+
+ final ErrorTag tag = rpcError.getTag();
+ errorTag = tag != null ? tag : ErrorTag.OPERATION_FAILED;
+
+ errorMessage = rpcError.getMessage();
+ errorAppTag = rpcError.getApplicationTag();
+
+ String localErrorInfo = null;
+ if (rpcError.getInfo() == null) {
+ if (rpcError.getCause() != null) {
+ localErrorInfo = rpcError.getCause().getMessage();
+ } else if (rpcError.getSeverity() != null) {
+ localErrorInfo = "<severity>" + rpcError.getSeverity().toString().toLowerCase(Locale.ROOT)
+ + "</severity>";
+ }
+ } else {
+ localErrorInfo = rpcError.getInfo();
+ }
+
+ errorInfo = localErrorInfo;
+ errorPath = null;
+ }
+
+ public ErrorType getErrorType() {
+ return errorType;
+ }
+
+ public ErrorTag getErrorTag() {
+ return errorTag;
+ }
+
+ public String getErrorInfo() {
+ return errorInfo;
+ }
+
+ public String getErrorAppTag() {
+ return errorAppTag;
+ }
+
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+
+ public YangInstanceIdentifier getErrorPath() {
+ return errorPath;
+ }
+
+ @Override
+ public String toString() {
+ return "RestconfError ["
+ + "error-type: " + errorType.elementBody() + ", error-tag: " + errorTag.elementBody()
+ + (errorAppTag != null ? ", error-app-tag: " + errorAppTag : "")
+ + (errorMessage != null ? ", error-message: " + errorMessage : "")
+ + (errorInfo != null ? ", error-info: " + errorInfo : "")
+ + (errorPath != null ? ", error-path: " + errorPath.toString() : "")
+ + "]";
+ }
+}
diff --git a/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/schema/SchemaExportContext.java b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/schema/SchemaExportContext.java
new file mode 100644
index 0000000..ac2d9c3
--- /dev/null
+++ b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/schema/SchemaExportContext.java
@@ -0,0 +1,38 @@
+/*
+ * 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.restconf.common.schema;
+
+import org.opendaylight.mdsal.dom.api.DOMYangTextSourceProvider;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+public class SchemaExportContext {
+
+ private final SchemaContext schemaContext;
+ private final Module module;
+ private final DOMYangTextSourceProvider sourceProvider;
+
+ public SchemaExportContext(final SchemaContext ctx, final Module module,
+ final DOMYangTextSourceProvider sourceProvider) {
+ schemaContext = ctx;
+ this.module = module;
+ this.sourceProvider = sourceProvider;
+ }
+
+ public SchemaContext getSchemaContext() {
+ return schemaContext;
+ }
+
+ public Module getModule() {
+ return module;
+ }
+
+ public DOMYangTextSourceProvider getSourceProvider() {
+ return sourceProvider;
+ }
+}
diff --git a/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/AbstractOperationDataSchemaNode.java b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/AbstractOperationDataSchemaNode.java
new file mode 100644
index 0000000..02dc370
--- /dev/null
+++ b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/AbstractOperationDataSchemaNode.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.restconf.common.util;
+
+import java.util.Optional;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Status;
+import org.opendaylight.yangtools.yang.xpath.api.YangXPathExpression.QualifiedBound;
+
+abstract class AbstractOperationDataSchemaNode implements DataSchemaNode {
+ @Override
+ public final Status getStatus() {
+ return Status.CURRENT;
+ }
+
+ @Override
+ public final Optional<Boolean> effectiveConfig() {
+ return Optional.empty();
+ }
+
+ @Override
+ public final boolean isAugmenting() {
+ return false;
+ }
+
+ @Override
+ public final boolean isAddedByUses() {
+ return false;
+ }
+
+ @Override
+ public final Optional<String> getDescription() {
+ return Optional.empty();
+ }
+
+ @Override
+ public final Optional<String> getReference() {
+ return Optional.empty();
+ }
+
+ @Override
+ public final Optional<? extends QualifiedBound> getWhenCondition() {
+ return Optional.empty();
+ }
+}
diff --git a/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/AbstractOperationsModule.java b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/AbstractOperationsModule.java
new file mode 100644
index 0000000..68b7fe5
--- /dev/null
+++ b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/AbstractOperationsModule.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.restconf.common.util;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.concepts.SemVer;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.UnresolvedQName;
+import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified;
+import org.opendaylight.yangtools.yang.common.YangVersion;
+import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Deviation;
+import org.opendaylight.yangtools.yang.model.api.ExtensionDefinition;
+import org.opendaylight.yangtools.yang.model.api.FeatureDefinition;
+import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
+import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.ModuleImport;
+import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.Submodule;
+import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.UsesNode;
+import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
+import org.opendaylight.yangtools.yang.model.api.meta.IdentifierNamespace;
+import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
+import org.opendaylight.yangtools.yang.model.api.meta.StatementOrigin;
+import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.ModuleStatement;
+
+abstract class AbstractOperationsModule implements Module, ModuleEffectiveStatement {
+ @Override
+ public final ModuleStatement getDeclared() {
+ return null;
+ }
+
+ @Override
+ public final StatementDefinition statementDefinition() {
+ return YangStmtMapping.MODULE;
+ }
+
+ @Override
+ public final StatementOrigin statementOrigin() {
+ return StatementOrigin.CONTEXT;
+ }
+
+ @Override
+ public final <K, V, N extends IdentifierNamespace<K, V>> Optional<V> get(final Class<N> namespace,
+ final K identifier) {
+ return Optional.empty();
+ }
+
+ @Override
+ public final <K, V, N extends IdentifierNamespace<K, V>> Map<K, V> getAll(final Class<N> namespace) {
+ return Map.of();
+ }
+
+ @Override
+ public final Unqualified argument() {
+ return UnresolvedQName.unqualified(getName());
+ }
+
+ @Override
+ public final QNameModule localQNameModule() {
+ return getQNameModule();
+ }
+
+ @Override
+ public final Collection<? extends @NonNull ModuleImport> getImports() {
+ // Yeah, not accurate, but this should not be needed
+ return Collections.emptySet();
+ }
+
+ @Override
+ public final YangVersion getYangVersion() {
+ return YangVersion.VERSION_1;
+ }
+
+ @Override
+ public final Collection<? extends TypeDefinition<?>> getTypeDefinitions() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public final Collection<? extends GroupingDefinition> getGroupings() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public final Collection<? extends UsesNode> getUses() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public final Optional<String> getDescription() {
+ return Optional.empty();
+ }
+
+ @Override
+ public final Optional<String> getReference() {
+ return Optional.empty();
+ }
+
+ @Override
+ public final Collection<? extends NotificationDefinition> getNotifications() {
+ return Collections.emptySet();
+ }
+
+ public final Optional<SemVer> getSemanticVersion() {
+ return Optional.empty();
+ }
+
+ @Override
+ public final Optional<String> getOrganization() {
+ return Optional.empty();
+ }
+
+ @Override
+ public final Optional<String> getContact() {
+ return Optional.empty();
+ }
+
+ @Override
+ public final Collection<? extends @NonNull Submodule> getSubmodules() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public final Collection<? extends @NonNull FeatureDefinition> getFeatures() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public final Collection<? extends @NonNull AugmentationSchemaNode> getAugmentations() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public final Collection<? extends @NonNull RpcDefinition> getRpcs() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public final Collection<? extends @NonNull Deviation> getDeviations() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public final Collection<? extends @NonNull IdentitySchemaNode> getIdentities() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public final Collection<? extends @NonNull ExtensionDefinition> getExtensionSchemaNodes() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public final ModuleEffectiveStatement asEffectiveStatement() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public final ConformanceType conformance() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/IdentityValuesDTO.java b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/IdentityValuesDTO.java
new file mode 100644
index 0000000..6cca3ae
--- /dev/null
+++ b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/IdentityValuesDTO.java
@@ -0,0 +1,139 @@
+/*
+ * 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.restconf.common.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public final class IdentityValuesDTO {
+
+ private final List<IdentityValue> elementData = new ArrayList<>();
+ private final String originValue;
+
+ public IdentityValuesDTO(
+ final String namespace, final String value, final String prefix, final String originValue) {
+ elementData.add(new IdentityValue(namespace, value));
+ this.originValue = originValue;
+ }
+
+ public IdentityValuesDTO(final String originValue) {
+ this.originValue = originValue;
+ }
+
+ public IdentityValuesDTO() {
+ originValue = null;
+ }
+
+ public void add(final String namespace, final String value, final String prefix) {
+ elementData.add(new IdentityValue(namespace, value));
+ }
+
+ public void add(final IdentityValue identityValue) {
+ elementData.add(identityValue);
+ }
+
+ public List<IdentityValue> getValuesWithNamespaces() {
+ return Collections.unmodifiableList(elementData);
+ }
+
+ @Override
+ public String toString() {
+ return elementData.toString();
+ }
+
+ public String getOriginValue() {
+ return originValue;
+ }
+
+ public static final class IdentityValue {
+
+ private final String namespace;
+ private final String value;
+ private List<Predicate> predicates;
+
+ public IdentityValue(final String namespace, final String value) {
+ this.namespace = namespace;
+ this.value = value;
+ }
+
+ public String getNamespace() {
+ return namespace;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+
+ public List<Predicate> getPredicates() {
+ if (predicates == null) {
+ return Collections.emptyList();
+ }
+ return Collections.unmodifiableList(predicates);
+ }
+
+ public void setPredicates(final List<Predicate> predicates) {
+ this.predicates = predicates;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ if (namespace != null) {
+ sb.append(namespace);
+ }
+ if (value != null) {
+ sb.append(" - ").append(value);
+ }
+ if (predicates != null && !predicates.isEmpty()) {
+ for (final Predicate predicate : predicates) {
+ sb.append("[").append(predicate.toString()).append("]");
+ }
+ }
+ return sb.toString();
+ }
+
+ }
+
+ public static final class Predicate {
+
+ private final IdentityValue name;
+ private final String value;
+
+ public Predicate(final IdentityValue name, final String value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ public IdentityValue getName() {
+ return name;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ if (name != null) {
+ sb.append(name.toString());
+ }
+ if (value != null) {
+ sb.append("=").append(value);
+ }
+ return sb.toString();
+ }
+
+ public boolean isLeafList() {
+ return name == null;
+ }
+
+ }
+}
diff --git a/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/OperationsContainerSchemaNode.java b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/OperationsContainerSchemaNode.java
new file mode 100644
index 0000000..a61a663
--- /dev/null
+++ b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/OperationsContainerSchemaNode.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.restconf.common.util;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.Maps;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
+import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
+import org.opendaylight.yangtools.yang.model.api.MustDefinition;
+import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
+import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.UsesNode;
+import org.opendaylight.yangtools.yang.model.api.stmt.ContainerEffectiveStatement;
+
+final class OperationsContainerSchemaNode extends AbstractOperationDataSchemaNode implements ContainerSchemaNode {
+ // There is no need to intern this nor add a revision, as we are providing the corresponding context anyway
+ static final @NonNull QName QNAME = QName.create(OperationsRestconfModule.NAMESPACE, "operations");
+
+ private final Map<QName, OperationsLeafSchemaNode> children;
+
+ OperationsContainerSchemaNode(final Collection<OperationsLeafSchemaNode> children) {
+ this.children = Maps.uniqueIndex(children, OperationsLeafSchemaNode::getQName);
+ }
+
+ @Override
+ public QName getQName() {
+ return QNAME;
+ }
+
+ @Override
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public Collection<DataSchemaNode> getChildNodes() {
+ return (Collection) children.values();
+ }
+
+ @Override
+ public DataSchemaNode dataChildByName(final QName name) {
+ return children.get(requireNonNull(name));
+ }
+
+ @Override
+ public Set<TypeDefinition<?>> getTypeDefinitions() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<GroupingDefinition> getGroupings() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<UsesNode> getUses() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<AugmentationSchemaNode> getAvailableAugmentations() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<NotificationDefinition> getNotifications() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<ActionDefinition> getActions() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Collection<@NonNull MustDefinition> getMustConstraints() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public boolean isPresenceContainer() {
+ return false;
+ }
+
+ @Override
+ public ContainerEffectiveStatement asEffectiveStatement() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/OperationsEffectiveModuleContext.java b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/OperationsEffectiveModuleContext.java
new file mode 100644
index 0000000..35fffa0
--- /dev/null
+++ b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/OperationsEffectiveModuleContext.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.restconf.common.util;
+
+import static com.google.common.base.Verify.verify;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.spi.SimpleSchemaContext;
+
+final class OperationsEffectiveModuleContext extends SimpleSchemaContext implements EffectiveModelContext {
+ private final Map<QNameModule, ModuleEffectiveStatement> modules;
+
+ OperationsEffectiveModuleContext(final Set<Module> modules) {
+ super(modules);
+ this.modules = modules.stream()
+ .map(module -> {
+ verify(module instanceof ModuleEffectiveStatement, "Module %s is not an effective statement");
+ return (ModuleEffectiveStatement) module;
+ })
+ .collect(ImmutableMap.toImmutableMap(ModuleEffectiveStatement::localQNameModule, Function.identity()));
+ }
+
+ @Override
+ public Map<QNameModule, ModuleEffectiveStatement> getModuleStatements() {
+ return modules;
+ }
+}
diff --git a/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/OperationsImportedModule.java b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/OperationsImportedModule.java
new file mode 100644
index 0000000..5551326
--- /dev/null
+++ b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/OperationsImportedModule.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.restconf.common.util;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Collection;
+import java.util.List;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
+
+final class OperationsImportedModule extends AbstractOperationsModule {
+ private final Module original;
+
+ OperationsImportedModule(final Module original) {
+ this.original = requireNonNull(original);
+ }
+
+ @Override
+ public String getName() {
+ return original.getName();
+ }
+
+ @Override
+ public QNameModule getQNameModule() {
+ return original.getQNameModule();
+ }
+
+ @Override
+ public String getPrefix() {
+ return original.getPrefix();
+ }
+
+ @Override
+ public Collection<DataSchemaNode> getChildNodes() {
+ return List.of();
+ }
+
+ @Override
+ public DataSchemaNode dataChildByName(final QName name) {
+ return null;
+ }
+
+ @Override
+ public List<EffectiveStatement<?, ?>> effectiveSubstatements() {
+ return List.of();
+ }
+}
diff --git a/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/OperationsLeafSchemaNode.java b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/OperationsLeafSchemaNode.java
new file mode 100644
index 0000000..a0d231f
--- /dev/null
+++ b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/OperationsLeafSchemaNode.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.restconf.common.util;
+
+import java.util.Collection;
+import java.util.Collections;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.MustDefinition;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.stmt.LeafEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.ri.type.BaseTypes;
+
+final class OperationsLeafSchemaNode extends AbstractOperationDataSchemaNode implements LeafSchemaNode {
+ private final QName qname;
+
+ OperationsLeafSchemaNode(final RpcDefinition rpc) {
+ qname = rpc.getQName();
+ }
+
+ @Override
+ public TypeDefinition<?> getType() {
+ return BaseTypes.emptyType();
+ }
+
+ @Override
+ public QName getQName() {
+ return qname;
+ }
+
+ @Override
+ public boolean isMandatory() {
+ // This leaf has to be present
+ return true;
+ }
+
+ @Override
+ public Collection<@NonNull MustDefinition> getMustConstraints() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public LeafEffectiveStatement asEffectiveStatement() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/OperationsResourceUtils.java b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/OperationsResourceUtils.java
new file mode 100644
index 0000000..ada0442
--- /dev/null
+++ b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/OperationsResourceUtils.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.restconf.common.util;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Map.Entry;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.mdsal.dom.api.DOMMountPoint;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.yangtools.yang.common.Empty;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+
+
+// FIXME: remove this class
+public final class OperationsResourceUtils {
+ private OperationsResourceUtils() {
+ // Hidden on purpose
+ }
+
+ public static @NonNull Entry<InstanceIdentifierContext, ContainerNode>
+ contextForModelContext(final @NonNull SchemaContext context, final @Nullable DOMMountPoint mountPoint) {
+ // Determine which modules we need and construct leaf schemas to correspond to all RPC definitions
+ final Collection<Module> modules = new ArrayList<>();
+ final ArrayList<OperationsLeafSchemaNode> rpcLeafSchemas = new ArrayList<>();
+ for (final Module m : context.getModules()) {
+ final Collection<? extends RpcDefinition> rpcs = m.getRpcs();
+ if (!rpcs.isEmpty()) {
+ modules.add(new OperationsImportedModule(m));
+ rpcLeafSchemas.ensureCapacity(rpcLeafSchemas.size() + rpcs.size());
+ for (RpcDefinition rpc : rpcs) {
+ rpcLeafSchemas.add(new OperationsLeafSchemaNode(rpc));
+ }
+ }
+ }
+
+ // Now generate a module for RESTCONF so that operations contain what they need
+ final OperationsContainerSchemaNode operatationsSchema = new OperationsContainerSchemaNode(rpcLeafSchemas);
+ modules.add(new OperationsRestconfModule(operatationsSchema));
+
+ // Now build the operations container and combine it with the context
+ final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> operationsBuilder = Builders.containerBuilder()
+ .withNodeIdentifier(new NodeIdentifier(OperationsContainerSchemaNode.QNAME));
+ for (final OperationsLeafSchemaNode leaf : rpcLeafSchemas) {
+ operationsBuilder.withChild(ImmutableNodes.leafNode(leaf.getQName(), Empty.value()));
+ }
+
+ final var opContext = new OperationsEffectiveModuleContext(ImmutableSet.copyOf(modules));
+ final var stack = SchemaInferenceStack.of(opContext);
+ stack.enterSchemaTree(operatationsSchema.getQName());
+
+ return Map.entry(InstanceIdentifierContext.ofStack(stack, mountPoint), operationsBuilder.build());
+ }
+}
diff --git a/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/OperationsRestconfModule.java b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/OperationsRestconfModule.java
new file mode 100644
index 0000000..467db9b
--- /dev/null
+++ b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/OperationsRestconfModule.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.restconf.common.util;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Collection;
+import java.util.List;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.XMLNamespace;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
+
+final class OperationsRestconfModule extends AbstractOperationsModule {
+ // There is no need to intern this nor add a revision, as we are providing the corresponding context anyway
+ static final @NonNull QNameModule NAMESPACE =
+ QNameModule.create(XMLNamespace.of("urn:ietf:params:xml:ns:yang:ietf-restconf"));
+
+ private final OperationsContainerSchemaNode operations;
+
+ OperationsRestconfModule(final OperationsContainerSchemaNode operations) {
+ this.operations = requireNonNull(operations);
+ }
+
+ @Override
+ public String getName() {
+ return "ietf-restconf";
+ }
+
+ @Override
+ public QNameModule getQNameModule() {
+ return NAMESPACE;
+ }
+
+ @Override
+ public String getPrefix() {
+ return "rc";
+ }
+
+ @Override
+ public Collection<DataSchemaNode> getChildNodes() {
+ return List.of(operations);
+ }
+
+ @Override
+ public DataSchemaNode dataChildByName(final QName name) {
+ return operations.getQName().equals(requireNonNull(name)) ? operations : null;
+ }
+
+ @Override
+ public List<EffectiveStatement<?, ?>> effectiveSubstatements() {
+ // This is not accurate, but works for now
+ return List.of();
+ }
+}
diff --git a/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/RestUtil.java b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/RestUtil.java
new file mode 100644
index 0000000..4086546
--- /dev/null
+++ b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/RestUtil.java
@@ -0,0 +1,184 @@
+/*
+ * 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.restconf.common.util;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.xml.stream.events.StartElement;
+import org.opendaylight.restconf.common.util.IdentityValuesDTO.IdentityValue;
+import org.opendaylight.restconf.common.util.IdentityValuesDTO.Predicate;
+import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+
+public final class RestUtil {
+
+ // FIXME: BUG-1275: this is code duplicates data.impl.codec
+
+ public static final String SQUOTE = "'";
+ public static final String DQUOTE = "\"";
+ private static final Pattern PREDICATE_PATTERN = Pattern.compile("\\[(.*?)\\]");
+
+ private RestUtil() {
+ }
+
+ public static TypeDefinition<?> resolveBaseTypeFrom(final TypeDefinition<?> type) {
+ TypeDefinition<?> superType = type;
+ while (superType.getBaseType() != null) {
+ superType = superType.getBaseType();
+ }
+ return superType;
+ }
+
+ /**
+ * Utility method to find out if is provided {@link InputStream} empty.
+ *
+ * @param entityStream {@link InputStream} to be checked if it is empty.
+ * @return Empty Optional if provided input stream is empty or Optional
+ * containing input stream otherwise.
+ *
+ * @throws IOException if an IO error arises during stream inspection.
+ * @throws NullPointerException if provided stream is null.
+ *
+ */
+ public static Optional<InputStream> isInputStreamEmpty(final InputStream entityStream) throws IOException {
+ final PushbackInputStream pushbackInputStream = new PushbackInputStream(requireNonNull(entityStream));
+
+ int firstByte = pushbackInputStream.read();
+ if (firstByte == -1) {
+ return Optional.empty();
+ } else {
+ pushbackInputStream.unread(firstByte);
+ return Optional.of(pushbackInputStream);
+ }
+ }
+
+ public static IdentityValuesDTO asInstanceIdentifier(final String value, final PrefixesMaping prefixMap) {
+ final String valueTrimmed = value.trim();
+ if (!valueTrimmed.startsWith("/")) {
+ return null;
+ }
+ final String[] xPathParts = valueTrimmed.split("/");
+ if (xPathParts.length < 2) { // must be at least "/pr:node"
+ return null;
+ }
+ final IdentityValuesDTO identityValuesDTO = new IdentityValuesDTO(value);
+ for (int i = 1; i < xPathParts.length; i++) {
+ final String xPathPartTrimmed = xPathParts[i].trim();
+
+ final String xPathPartStr = getIdAndPrefixAsStr(xPathPartTrimmed);
+ final IdentityValue identityValue = toIdentity(xPathPartStr, prefixMap);
+ if (identityValue == null) {
+ return null;
+ }
+
+ final List<Predicate> predicates = toPredicates(xPathPartTrimmed, prefixMap);
+ if (predicates == null) {
+ return null;
+ }
+ identityValue.setPredicates(predicates);
+
+ identityValuesDTO.add(identityValue);
+ }
+ return identityValuesDTO.getValuesWithNamespaces().isEmpty() ? null : identityValuesDTO;
+ }
+
+ private static String getIdAndPrefixAsStr(final String pathPart) {
+ final int predicateStartIndex = pathPart.indexOf("[");
+ return predicateStartIndex == -1 ? pathPart : pathPart.substring(0, predicateStartIndex);
+ }
+
+ private static IdentityValue toIdentity(final String xpathPart, final PrefixesMaping prefixMap) {
+ final String xPathPartTrimmed = xpathPart.trim();
+ if (xPathPartTrimmed.isEmpty()) {
+ return null;
+ }
+ final String[] prefixAndIdentifier = xPathPartTrimmed.split(":");
+ // it is not "prefix:value"
+ if (prefixAndIdentifier.length != 2) {
+ return null;
+ }
+ final String prefix = prefixAndIdentifier[0].trim();
+ final String identifier = prefixAndIdentifier[1].trim();
+ if (prefix.isEmpty() || identifier.isEmpty()) {
+ return null;
+ }
+ final String namespace = prefixMap.getNamespace(prefix);
+ return new IdentityValue(namespace, identifier);
+ }
+
+ private static List<Predicate> toPredicates(final String predicatesStr, final PrefixesMaping prefixMap) {
+ final List<Predicate> result = new ArrayList<>();
+ final List<String> predicates = new ArrayList<>();
+ final Matcher matcher = PREDICATE_PATTERN.matcher(predicatesStr);
+ while (matcher.find()) {
+ predicates.add(matcher.group(1).trim());
+ }
+ for (final String predicate : predicates) {
+ final int indexOfEqualityMark = predicate.indexOf("=");
+ if (indexOfEqualityMark != -1) {
+ final String predicateValue = toPredicateValue(predicate.substring(indexOfEqualityMark + 1));
+ if (predicate.startsWith(".")) { // it is leaf-list
+ if (predicateValue == null) {
+ return null;
+ }
+ result.add(new Predicate(null, predicateValue));
+ } else {
+ final IdentityValue identityValue =
+ toIdentity(predicate.substring(0, indexOfEqualityMark), prefixMap);
+ if (identityValue == null || predicateValue == null) {
+ return null;
+ }
+ result.add(new Predicate(identityValue, predicateValue));
+ }
+ }
+ }
+ return result;
+ }
+
+ private static String toPredicateValue(final String predicatedValue) {
+ final String predicatedValueTrimmed = predicatedValue.trim();
+ if ((predicatedValueTrimmed.startsWith(DQUOTE) || predicatedValueTrimmed.startsWith(SQUOTE))
+ && (predicatedValueTrimmed.endsWith(DQUOTE) || predicatedValueTrimmed.endsWith(SQUOTE))) {
+ return predicatedValueTrimmed.substring(1, predicatedValueTrimmed.length() - 1);
+ }
+ return null;
+ }
+
+ public interface PrefixesMaping {
+ String getNamespace(String prefix);
+ }
+
+ public static class PrefixMapingFromXml implements PrefixesMaping {
+ StartElement startElement = null;
+
+ public PrefixMapingFromXml(final StartElement startElement) {
+ this.startElement = startElement;
+ }
+
+ @Override
+ public String getNamespace(final String prefix) {
+ return startElement.getNamespaceContext().getNamespaceURI(prefix);
+ }
+ }
+
+ public static class PrefixMapingFromJson implements PrefixesMaping {
+
+ @Override
+ public String getNamespace(final String prefix) {
+ return prefix;
+ }
+ }
+
+}
diff --git a/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/SimpleUriInfo.java b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/SimpleUriInfo.java
new file mode 100644
index 0000000..b964b4c
--- /dev/null
+++ b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/util/SimpleUriInfo.java
@@ -0,0 +1,130 @@
+/*
+ * 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.restconf.common.util;
+
+import java.net.URI;
+import java.util.List;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.PathSegment;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
+/**
+ * Simple implementation of the {@link UriInfo} interface.
+ *
+ * @author Thomas Pantelis
+ */
+public class SimpleUriInfo implements UriInfo {
+ private final String path;
+ private final MultivaluedMap<String, String> queryParams;
+
+ public SimpleUriInfo(String path) {
+ this(path, new MultivaluedHashMap<>());
+ }
+
+ public SimpleUriInfo(String path, MultivaluedMap<String, String> queryParams) {
+ this.path = path;
+ this.queryParams = queryParams;
+ }
+
+ @Override
+ public String getPath() {
+ return path;
+ }
+
+ @Override
+ public String getPath(boolean decode) {
+ return path;
+ }
+
+ @Override
+ public List<PathSegment> getPathSegments() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<PathSegment> getPathSegments(boolean decode) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public URI getRequestUri() {
+ return URI.create(path);
+ }
+
+ @Override
+ public UriBuilder getRequestUriBuilder() {
+ return UriBuilder.fromUri(getRequestUri());
+ }
+
+ @Override
+ public URI getAbsolutePath() {
+ return getRequestUri();
+ }
+
+ @Override
+ public UriBuilder getAbsolutePathBuilder() {
+ return UriBuilder.fromUri(getAbsolutePath());
+ }
+
+ @Override
+ public URI getBaseUri() {
+ return URI.create("");
+ }
+
+ @Override
+ public UriBuilder getBaseUriBuilder() {
+ return UriBuilder.fromUri(getBaseUri());
+ }
+
+ @Override
+ public MultivaluedMap<String, String> getPathParameters() {
+ return new MultivaluedHashMap<>();
+ }
+
+ @Override
+ public MultivaluedMap<String, String> getPathParameters(boolean decode) {
+ return getPathParameters();
+ }
+
+ @Override
+ public MultivaluedMap<String, String> getQueryParameters() {
+ return queryParams;
+ }
+
+ @Override
+ public MultivaluedMap<String, String> getQueryParameters(boolean decode) {
+ return getQueryParameters();
+ }
+
+ @Override
+ public List<String> getMatchedURIs() {
+ return List.of();
+ }
+
+ @Override
+ public List<String> getMatchedURIs(boolean decode) {
+ return getMatchedURIs();
+ }
+
+ @Override
+ public List<Object> getMatchedResources() {
+ return List.of();
+ }
+
+ @Override
+ public URI resolve(URI uri) {
+ return uri;
+ }
+
+ @Override
+ public URI relativize(URI uri) {
+ return uri;
+ }
+}
diff --git a/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/validation/RestconfValidationUtils.java b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/validation/RestconfValidationUtils.java
new file mode 100644
index 0000000..987ce42
--- /dev/null
+++ b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/validation/RestconfValidationUtils.java
@@ -0,0 +1,46 @@
+/*
+ * 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.restconf.common.validation;
+
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+
+/**
+ * sal-rest-connector
+ * org.opendaylight.controller.md.sal.rest.common
+ *
+ * <p>
+ * Utility class is centralizing all needed validation functionality for a Restconf osgi module.
+ * All methods have to throw {@link RestconfDocumentedException} only, which is a representation
+ * for all error situation followed by restconf-netconf specification.
+ * See also <a href="https://tools.ietf.org/html/draft-bierman-netconf-restconf-02">RESTCONF</a>.
+ */
+public final class RestconfValidationUtils {
+ private RestconfValidationUtils() {
+ // Hidden on purpose
+ }
+
+ /**
+ * Method returns {@link RestconfDocumentedException} if value is NULL or same input value.
+ * {@link ErrorType} is relevant for server application layer
+ * {@link ErrorTag} is 404 data-missing
+ * See also <a href="https://tools.ietf.org/html/draft-bierman-netconf-restconf-02">RESTCONF</a>.
+ *
+ * @param value - some value from {@link org.opendaylight.yangtools.yang.model.api.Module}
+ * @param moduleName - name of {@link org.opendaylight.yangtools.yang.model.api.Module}
+ * @return - T value (same input value)
+ */
+ public static <T> T checkNotNullDocumented(final T value, final String moduleName) {
+ if (value == null) {
+ final String errMsg = "Module " + moduleName + " was not found.";
+ throw new RestconfDocumentedException(errMsg, ErrorType.APPLICATION, ErrorTag.DATA_MISSING);
+ }
+ return value;
+ }
+}