diff options
author | Dan Timoney <dtimoney@att.com> | 2023-01-31 16:57:11 -0500 |
---|---|---|
committer | Dan Timoney <dtimoney@att.com> | 2023-01-31 16:57:11 -0500 |
commit | d76e58f792700c96d864237efd4539505cbf2b60 (patch) | |
tree | 645019d6cbee581a30aba87c29305ba95e33e9d9 /netconf/restconf/restconf-common | |
parent | d4d6fbd430eb502cce6cb01a667ec799d487a510 (diff) |
Port Biermann-draft-02 API to Chlorine
Made changes to get a clean compile under Chlorine.
NOTE: changes in Chlorine break the jUnit testing for this feature -
mostly due to changes in yangtools making classes sealed, which
prevents use of mockito to simulate these classes. There are other
changes as well that caused breakage. For now, pom.xml has been changed
to disable jUnit, but this should be corrected.
Issue-ID: CCSDK-3843
Signed-off-by: Dan Timoney <dtimoney@att.com>
Change-Id: Ic53c6d580d644fab069a06f033db515c05dff6f2
Diffstat (limited to 'netconf/restconf/restconf-common')
19 files changed, 2178 insertions, 0 deletions
diff --git a/netconf/restconf/restconf-common/pom.xml b/netconf/restconf/restconf-common/pom.xml new file mode 100644 index 0000000..3021629 --- /dev/null +++ b/netconf/restconf/restconf-common/pom.xml @@ -0,0 +1,67 @@ +<!-- + ~ 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 + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.opendaylight.netconf</groupId> + <artifactId>netconf-parent</artifactId> + <version>4.0.4</version> + <relativePath/> + </parent> + + <groupId>org.onap.ccsdk.odl-legacy.netconf</groupId> + <artifactId>restconf-common</artifactId> + <version>1.0.0</version> + <packaging>bundle</packaging> + <name>${project.artifactId}</name> + + <dependencies> + <dependency> + <groupId>org.opendaylight.yangtools</groupId> + <artifactId>yang-data-api</artifactId> + </dependency> + <dependency> + <groupId>org.opendaylight.yangtools</groupId> + <artifactId>yang-data-codec-gson</artifactId> + </dependency> + <dependency> + <groupId>org.opendaylight.yangtools</groupId> + <artifactId>yang-data-codec-xml</artifactId> + </dependency> + <dependency> + <groupId>org.opendaylight.yangtools</groupId> + <artifactId>yang-data-util</artifactId> + </dependency> + <dependency> + <groupId>org.opendaylight.yangtools</groupId> + <artifactId>yang-data-impl</artifactId> + </dependency> + <dependency> + <groupId>org.opendaylight.yangtools</groupId> + <artifactId>yang-model-ri</artifactId> + </dependency> + <dependency> + <groupId>org.opendaylight.mdsal</groupId> + <artifactId>mdsal-dom-api</artifactId> + </dependency> + <dependency> + <groupId>jakarta.ws.rs</groupId> + <artifactId>jakarta.ws.rs-api</artifactId> + <version>2.1.6</version> + </dependency> + + <dependency> + <groupId>org.opendaylight.yangtools</groupId> + <artifactId>yang-test-util</artifactId> + </dependency> + </dependencies> +</project> 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; + } +} |