import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants.STREAM_PATH_PART;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.MoreExecutors;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
-import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
import org.opendaylight.mdsal.dom.api.DOMActionException;
import org.opendaylight.mdsal.dom.api.DOMActionResult;
import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
import org.opendaylight.mdsal.dom.api.DOMMountPoint;
import org.opendaylight.mdsal.dom.api.DOMMountPointService;
-import org.opendaylight.mdsal.dom.api.DOMSchemaService;
import org.opendaylight.mdsal.dom.spi.SimpleDOMActionResult;
import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.patch.PatchContext;
import org.opendaylight.restconf.common.patch.PatchStatusContext;
-import org.opendaylight.restconf.nb.rfc8040.InsertParameter;
-import org.opendaylight.restconf.nb.rfc8040.PointParameter;
+import org.opendaylight.restconf.nb.rfc8040.ReadDataParams;
import org.opendaylight.restconf.nb.rfc8040.Rfc8040;
+import org.opendaylight.restconf.nb.rfc8040.WriteDataParams;
+import org.opendaylight.restconf.nb.rfc8040.databind.jaxrs.QueryParams;
import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler;
import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
import org.opendaylight.restconf.nb.rfc8040.utils.mapping.RestconfMappingNodeUtil;
import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
-import org.opendaylight.yangtools.concepts.Immutable;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.opendaylight.yangtools.yang.common.ErrorType;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.Revision;
-import org.opendaylight.yangtools.yang.common.RpcError;
import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
*/
@Path("/")
public class RestconfDataServiceImpl implements RestconfDataService {
- // FIXME: we should be able to interpret 'point' and refactor this class into a behavior
- private static final class QueryParams implements Immutable {
- final @Nullable PointParameter point;
- final @Nullable InsertParameter insert;
-
- QueryParams(final @Nullable InsertParameter insert, final @Nullable PointParameter point) {
- this.insert = insert;
- this.point = point;
- }
- }
-
private static final Logger LOG = LoggerFactory.getLogger(RestconfDataServiceImpl.class);
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MMM-dd HH:mm:ss");
private static final QName NETCONF_BASE_QNAME = SchemaContext.NAME;
@Override
public Response readData(final String identifier, final UriInfo uriInfo) {
+ final ReadDataParams readParams = QueryParams.newReadDataParams(uriInfo);
+
final EffectiveModelContext schemaContextRef = schemaContextHandler.get();
- final InstanceIdentifierContext<?> instanceIdentifier = ParserIdentifier.toInstanceIdentifier(
+ final InstanceIdentifierContext instanceIdentifier = ParserIdentifier.toInstanceIdentifier(
identifier, schemaContextRef, Optional.of(mountPointService));
- final QueryParameters parameters = ReadDataTransactionUtil.parseUriParameters(instanceIdentifier, uriInfo);
-
final DOMMountPoint mountPoint = instanceIdentifier.getMountPoint();
// FIXME: this looks quite crazy, why do we even have it?
createAllYangNotificationStreams(schemaContextRef, uriInfo);
}
- final List<YangInstanceIdentifier> fieldPaths = parameters.getFieldPaths();
+ final QueryParameters queryParams = QueryParams.newQueryParameters(readParams, instanceIdentifier);
+ final List<YangInstanceIdentifier> fieldPaths = queryParams.fieldPaths();
final RestconfStrategy strategy = getRestconfStrategy(mountPoint);
final NormalizedNode node;
if (fieldPaths != null && !fieldPaths.isEmpty()) {
- node = ReadDataTransactionUtil.readData(parameters.getContent(), instanceIdentifier.getInstanceIdentifier(),
- strategy, parameters.getWithDefault(), schemaContextRef, fieldPaths);
+ node = ReadDataTransactionUtil.readData(readParams.content(), instanceIdentifier.getInstanceIdentifier(),
+ strategy, readParams.withDefaults(), schemaContextRef, fieldPaths);
} else {
- node = ReadDataTransactionUtil.readData(parameters.getContent(), instanceIdentifier.getInstanceIdentifier(),
- strategy, parameters.getWithDefault(), schemaContextRef);
+ node = ReadDataTransactionUtil.readData(readParams.content(), instanceIdentifier.getInstanceIdentifier(),
+ strategy, readParams.withDefaults(), schemaContextRef);
}
// FIXME: this is utter craziness, refactor it properly!
ErrorType.PROTOCOL, ErrorTag.DATA_MISSING);
}
- switch (parameters.getContent()) {
+ switch (readParams.content()) {
case ALL:
case CONFIG:
final QName type = node.getIdentifier().getNodeType();
return Response.status(Status.OK)
- .entity(NormalizedNodePayload.ofReadData(instanceIdentifier, node, parameters))
+ .entity(NormalizedNodePayload.ofReadData(instanceIdentifier, node, queryParams))
.header("ETag", '"' + type.getModule().getRevision().map(Revision::toString).orElse(null)
+ "-" + type.getLocalName() + '"')
.header("Last-Modified", FORMATTER.format(LocalDateTime.now(Clock.systemUTC())))
.build();
default:
return Response.status(Status.OK)
- .entity(NormalizedNodePayload.ofReadData(instanceIdentifier, node, parameters))
+ .entity(NormalizedNodePayload.ofReadData(instanceIdentifier, node, queryParams))
.build();
}
}
public Response putData(final String identifier, final NormalizedNodePayload payload, final UriInfo uriInfo) {
requireNonNull(payload);
- final QueryParams checkedParms = checkQueryParameters(uriInfo);
+ final WriteDataParams params = QueryParams.newWriteDataParams(uriInfo);
- final InstanceIdentifierContext<? extends SchemaNode> iid = payload.getInstanceIdentifierContext();
+ final InstanceIdentifierContext iid = payload.getInstanceIdentifierContext();
- validInputData(iid.getSchemaNode(), payload);
+ validInputData(iid.getSchemaNode() != null, payload);
validTopLevelNodeName(iid.getInstanceIdentifier(), payload);
validateListKeysEqualityInPayloadAndUri(payload);
- final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
- final EffectiveModelContext ref = mountPoint == null
- ? schemaContextHandler.get() : modelContext(mountPoint);
-
- final RestconfStrategy strategy = getRestconfStrategy(mountPoint);
- return PutDataTransactionUtil.putData(payload, ref, strategy, checkedParms.insert, checkedParms.point);
- }
-
- private static QueryParams checkQueryParameters(final UriInfo uriInfo) {
- boolean insertUsed = false;
- boolean pointUsed = false;
- InsertParameter insert = null;
- PointParameter point = null;
-
- for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
- final String uriName = entry.getKey();
- if (InsertParameter.uriName().equals(uriName)) {
- if (insertUsed) {
- throw new RestconfDocumentedException("Insert parameter can be used only once.",
- ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
- }
-
- insertUsed = true;
- final String str = entry.getValue().get(0);
- insert = InsertParameter.forUriValue(str);
- if (insert == null) {
- throw new RestconfDocumentedException("Unrecognized insert parameter value '" + str + "'",
- ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
- }
- } else if (PointParameter.uriName().equals(uriName)) {
- if (pointUsed) {
- throw new RestconfDocumentedException("Point parameter can be used only once.",
- ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
- }
-
- pointUsed = true;
- point = PointParameter.forUriValue(entry.getValue().get(0));
- } else {
- throw new RestconfDocumentedException("Bad parameter for post: " + uriName,
- ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
- }
- }
-
- checkQueryParams(insertUsed, pointUsed, insert);
- return new QueryParams(insert, point);
- }
-
- private static void checkQueryParams(final boolean insertUsed, final boolean pointUsed,
- final InsertParameter insert) {
- if (pointUsed) {
- if (!insertUsed) {
- throw new RestconfDocumentedException("Point parameter can't be used without Insert parameter.",
- ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
- }
- if (insert != InsertParameter.BEFORE && insert != InsertParameter.AFTER) {
- throw new RestconfDocumentedException(
- "Point parameter can be used only with 'after' or 'before' values of Insert parameter.",
- ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
- }
- }
+ final RestconfStrategy strategy = getRestconfStrategy(iid.getMountPoint());
+ return PutDataTransactionUtil.putData(payload, iid.getSchemaContext(), strategy, params);
}
@Override
@Override
public Response postData(final NormalizedNodePayload payload, final UriInfo uriInfo) {
requireNonNull(payload);
- if (payload.getInstanceIdentifierContext().getSchemaNode() instanceof ActionDefinition) {
+ final InstanceIdentifierContext iid = payload.getInstanceIdentifierContext();
+ if (iid.getSchemaNode() instanceof ActionDefinition) {
return invokeAction(payload);
}
- final QueryParams checkedParms = checkQueryParameters(uriInfo);
- final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
- final RestconfStrategy strategy = getRestconfStrategy(mountPoint);
- return PostDataTransactionUtil.postData(uriInfo, payload, strategy,
- getSchemaContext(mountPoint), checkedParms.insert, checkedParms.point);
+ final WriteDataParams params = QueryParams.newWriteDataParams(uriInfo);
+ final RestconfStrategy strategy = getRestconfStrategy(iid.getMountPoint());
+ return PostDataTransactionUtil.postData(uriInfo, payload, strategy, iid.getSchemaContext(), params);
}
@Override
public Response deleteData(final String identifier) {
- final InstanceIdentifierContext<?> instanceIdentifier = ParserIdentifier.toInstanceIdentifier(
+ final InstanceIdentifierContext instanceIdentifier = ParserIdentifier.toInstanceIdentifier(
identifier, schemaContextHandler.get(), Optional.of(mountPointService));
final DOMMountPoint mountPoint = instanceIdentifier.getMountPoint();
@Override
public PatchStatusContext patchData(final PatchContext context, final UriInfo uriInfo) {
- final DOMMountPoint mountPoint = RestconfDocumentedException.throwIfNull(context,
+ final InstanceIdentifierContext iid = RestconfDocumentedException.throwIfNull(context,
ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, "No patch documented provided")
- .getInstanceIdentifierContext().getMountPoint();
- final RestconfStrategy strategy = getRestconfStrategy(mountPoint);
- return PatchDataTransactionUtil.patchData(context, strategy, getSchemaContext(mountPoint));
+ .getInstanceIdentifierContext();
+ final RestconfStrategy strategy = getRestconfStrategy(iid.getMountPoint());
+ return PatchDataTransactionUtil.patchData(context, strategy, iid.getSchemaContext());
}
@Override
public Response patchData(final String identifier, final NormalizedNodePayload payload, final UriInfo uriInfo) {
requireNonNull(payload);
- final InstanceIdentifierContext<? extends SchemaNode> iid = payload.getInstanceIdentifierContext();
- validInputData(iid.getSchemaNode(), payload);
+ final InstanceIdentifierContext iid = payload.getInstanceIdentifierContext();
+ validInputData(iid.getSchemaNode() != null, payload);
validTopLevelNodeName(iid.getInstanceIdentifier(), payload);
validateListKeysEqualityInPayloadAndUri(payload);
- final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
- final EffectiveModelContext ref = mountPoint == null
- ? schemaContextHandler.get() : modelContext(mountPoint);
- final RestconfStrategy strategy = getRestconfStrategy(mountPoint);
-
- return PlainPatchDataTransactionUtil.patchData(payload, strategy, ref);
- }
-
- private EffectiveModelContext getSchemaContext(final DOMMountPoint mountPoint) {
- return mountPoint == null ? schemaContextHandler.get() : modelContext(mountPoint);
+ final RestconfStrategy strategy = getRestconfStrategy(iid.getMountPoint());
+ return PlainPatchDataTransactionUtil.patchData(payload, strategy, iid.getSchemaContext());
}
// FIXME: why is this synchronized?
* @return {@link NormalizedNodePayload} wrapped in {@link Response}
*/
public Response invokeAction(final NormalizedNodePayload payload) {
- final InstanceIdentifierContext<?> context = payload.getInstanceIdentifierContext();
- final DOMMountPoint mountPoint = context.getMountPoint();
- final Absolute schemaPath = Absolute.of(ImmutableList.copyOf(context.getSchemaNode().getPath()
- .getPathFromRoot()));
+ final InstanceIdentifierContext context = payload.getInstanceIdentifierContext();
final YangInstanceIdentifier yangIIdContext = context.getInstanceIdentifier();
final NormalizedNode data = payload.getData();
ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
}
+ final DOMMountPoint mountPoint = context.getMountPoint();
+ final Absolute schemaPath = context.inference().toSchemaInferenceStack().toSchemaNodeIdentifier();
final DOMActionResult response;
- final EffectiveModelContext schemaContextRef;
if (mountPoint != null) {
response = invokeAction((ContainerNode) data, schemaPath, yangIIdContext, mountPoint);
- schemaContextRef = modelContext(mountPoint);
} else {
response = invokeAction((ContainerNode) data, schemaPath, yangIIdContext, actionService);
- schemaContextRef = schemaContextHandler.get();
}
final DOMActionResult result = checkActionResponse(response);
- ActionDefinition resultNodeSchema = null;
ContainerNode resultData = null;
if (result != null) {
- final Optional<ContainerNode> optOutput = result.getOutput();
- if (optOutput.isPresent()) {
- resultData = optOutput.get();
- resultNodeSchema = (ActionDefinition) context.getSchemaNode();
- }
+ resultData = result.getOutput().orElse(null);
}
if (resultData != null && resultData.isEmpty()) {
}
return Response.status(Status.OK)
- .entity(NormalizedNodePayload.ofNullable(
- new InstanceIdentifierContext<>(yangIIdContext, resultNodeSchema, mountPoint, schemaContextRef),
- resultData))
+ .entity(NormalizedNodePayload.ofNullable(context, resultData))
.build();
}
-
/**
* Invoking Action via mount point.
*
return RestconfInvokeOperationsServiceImpl.checkedGet(Futures.catching(actionService.invokeAction(
schemaPath, new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, yangIId.getParent()), data),
DOMActionException.class,
- cause -> new SimpleDOMActionResult(ImmutableList.of(RpcResultBuilder.newError(
- RpcError.ErrorType.RPC, "operation-failed", cause.getMessage()))),
+ cause -> new SimpleDOMActionResult(List.of(RpcResultBuilder.newError(
+ ErrorType.RPC, ErrorTag.OPERATION_FAILED, cause.getMessage()))),
MoreExecutors.directExecutor()));
}
}
/**
- * Valid input data with {@link SchemaNode}.
+ * Valid input data based on presence of a schema node.
*
- * @param schemaNode {@link SchemaNode}
+ * @param haveSchemaNode true if there is an underlying schema node
* @param payload input data
*/
@VisibleForTesting
- public static void validInputData(final SchemaNode schemaNode, final NormalizedNodePayload payload) {
- if (schemaNode != null && payload.getData() == null) {
- throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
- } else if (schemaNode == null && payload.getData() != null) {
+ public static void validInputData(final boolean haveSchemaNode, final NormalizedNodePayload payload) {
+ final boolean haveData = payload.getData() != null;
+ if (haveSchemaNode) {
+ if (!haveData) {
+ throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL,
+ ErrorTag.MALFORMED_MESSAGE);
+ }
+ } else if (haveData) {
throw new RestconfDocumentedException("No input expected.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
}
}
*/
@VisibleForTesting
public static void validateListKeysEqualityInPayloadAndUri(final NormalizedNodePayload payload) {
- final InstanceIdentifierContext<?> iiWithData = payload.getInstanceIdentifierContext();
+ final InstanceIdentifierContext iiWithData = payload.getInstanceIdentifierContext();
final PathArgument lastPathArgument = iiWithData.getInstanceIdentifier().getLastPathArgument();
final SchemaNode schemaNode = iiWithData.getSchemaNode();
final NormalizedNode data = payload.getData();
}
}
}
-
- private static EffectiveModelContext modelContext(final DOMMountPoint mountPoint) {
- return mountPoint.getService(DOMSchemaService.class)
- .flatMap(svc -> Optional.ofNullable(svc.getGlobalContext()))
- .orElse(null);
- }
}