import com.google.common.util.concurrent.FluentFuture;
import com.google.common.util.concurrent.ListenableFuture;
-import java.net.URI;
-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.opendaylight.mdsal.common.api.CommitInfo;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
-import org.opendaylight.restconf.api.query.InsertParam;
import org.opendaylight.restconf.api.query.PointParam;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.nb.rfc8040.WriteDataParams;
-import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfTransaction;
-import org.opendaylight.restconf.nb.rfc8040.utils.parser.IdentifierCodec;
import org.opendaylight.restconf.nb.rfc8040.utils.parser.YangInstanceIdentifierDeserializer;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.opendaylight.yangtools.yang.common.ErrorType;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
-import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
*/
public final class PostDataTransactionUtil {
private static final Logger LOG = LoggerFactory.getLogger(PostDataTransactionUtil.class);
- private static final String POST_TX_TYPE = "POST";
private PostDataTransactionUtil() {
// Hidden on purpose
* Check mount point and prepare variables for post data. Close {@link DOMTransactionChain} if any inside of object
* {@link RestconfStrategy} provided as a parameter.
*
- * @param uriInfo uri info
- * @param payload data
+ * @param path path
+ * @param data data
* @param strategy Object that perform the actual DS operations
* @param schemaContext reference to actual {@link EffectiveModelContext}
* @param params {@link WriteDataParams}
- * @return {@link Response}
*/
- public static Response postData(final UriInfo uriInfo, final NormalizedNodePayload payload,
- final RestconfStrategy strategy,
- final EffectiveModelContext schemaContext, final WriteDataParams params) {
- final YangInstanceIdentifier path = payload.getInstanceIdentifierContext().getInstanceIdentifier();
- final ListenableFuture<? extends CommitInfo> future = submitData(path, payload.getData(),
- strategy, schemaContext, params);
- final URI location = resolveLocation(uriInfo, path, schemaContext, payload.getData());
- final ResponseFactory dataFactory = new ResponseFactory(Status.CREATED).location(location);
- //This method will close transactionChain if any
- FutureCallbackTx.addCallback(future, POST_TX_TYPE, dataFactory, path);
- return dataFactory.build();
+ public static void postData(final YangInstanceIdentifier path, final NormalizedNode data,
+ final RestconfStrategy strategy, final EffectiveModelContext schemaContext, final WriteDataParams params) {
+ TransactionUtil.syncCommit(submitData(path, data, strategy, schemaContext, params), "POST", path);
+ }
+
+ public static DataSchemaNode checkListAndOrderedType(final EffectiveModelContext ctx,
+ final YangInstanceIdentifier path) {
+ final var dataSchemaNode = DataSchemaContextTree.from(ctx).findChild(path).orElseThrow().dataSchemaNode();
+
+ final String message;
+ if (dataSchemaNode instanceof ListSchemaNode listSchema) {
+ if (listSchema.isUserOrdered()) {
+ return listSchema;
+ }
+ message = "Insert parameter can be used only with ordered-by user list.";
+ } else if (dataSchemaNode instanceof LeafListSchemaNode leafListSchema) {
+ if (leafListSchema.isUserOrdered()) {
+ return leafListSchema;
+ }
+ message = "Insert parameter can be used only with ordered-by user leaf-list.";
+ } else {
+ message = "Insert parameter can be used only with list or leaf-list";
+ }
+ throw new RestconfDocumentedException(message, ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
}
/**
private static ListenableFuture<? extends CommitInfo> submitData(final YangInstanceIdentifier path,
final NormalizedNode data, final RestconfStrategy strategy, final EffectiveModelContext schemaContext,
final WriteDataParams params) {
- final RestconfTransaction transaction = strategy.prepareWriteExecution();
- final InsertParam insert = params.insert();
+ final var transaction = strategy.prepareWriteExecution();
+ final var insert = params.insert();
if (insert == null) {
- makePost(path, data, schemaContext, transaction);
- return transaction.commit();
+ return makePost(path, data, schemaContext, transaction);
}
- PutDataTransactionUtil.checkListAndOrderedType(schemaContext, path);
- final NormalizedNode readData;
- switch (insert) {
- case FIRST:
- readData = PutDataTransactionUtil.readList(strategy, path.getParent().getParent());
- if (readData == null || ((NormalizedNodeContainer<?>) readData).isEmpty()) {
+ final var parentPath = path.coerceParent();
+ checkListAndOrderedType(schemaContext, parentPath);
+ final var grandParentPath = parentPath.coerceParent();
+
+ return switch (insert) {
+ case FIRST -> {
+ final var readData = transaction.readList(grandParentPath);
+ if (readData == null || readData.isEmpty()) {
transaction.replace(path, data, schemaContext);
- return transaction.commit();
+ } else {
+ checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
+ transaction.remove(grandParentPath);
+ transaction.replace(path, data, schemaContext);
+ transaction.replace(grandParentPath, readData, schemaContext);
}
- checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
- transaction.remove(path.getParent().getParent());
- transaction.replace(path, data, schemaContext);
- transaction.replace(path.getParent().getParent(), readData, schemaContext);
- return transaction.commit();
- case LAST:
- makePost(path, data, schemaContext, transaction);
- return transaction.commit();
- case BEFORE:
- readData = PutDataTransactionUtil.readList(strategy, path.getParent().getParent());
- if (readData == null || ((NormalizedNodeContainer<?>) readData).isEmpty()) {
+ yield transaction.commit();
+ }
+ case LAST -> makePost(path, data, schemaContext, transaction);
+ case BEFORE -> {
+ final var readData = transaction.readList(grandParentPath);
+ if (readData == null || readData.isEmpty()) {
transaction.replace(path, data, schemaContext);
- return transaction.commit();
+ } else {
+ checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
+ insertWithPointPost(path, data, schemaContext, params.getPoint(), readData, true, transaction);
}
- checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
- insertWithPointPost(path, data, schemaContext, params.getPoint(),
- (NormalizedNodeContainer<?>) readData, true, transaction);
- return transaction.commit();
- case AFTER:
- readData = PutDataTransactionUtil.readList(strategy, path.getParent().getParent());
- if (readData == null || ((NormalizedNodeContainer<?>) readData).isEmpty()) {
+ yield transaction.commit();
+ }
+ case AFTER -> {
+ final var readData = transaction.readList(grandParentPath);
+ if (readData == null || readData.isEmpty()) {
transaction.replace(path, data, schemaContext);
- return transaction.commit();
+ } else {
+ checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
+ insertWithPointPost(path, data, schemaContext, params.getPoint(), readData, false, transaction);
}
- checkItemDoesNotExists(strategy.exists(LogicalDatastoreType.CONFIGURATION, path), path);
- insertWithPointPost(path, data, schemaContext, params.getPoint(),
- (NormalizedNodeContainer<?>) readData, false, transaction);
- return transaction.commit();
- default:
- throw new RestconfDocumentedException(
- "Used bad value of insert parameter. Possible values are first, last, before or after, but was: "
- + insert, ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE);
- }
+ yield transaction.commit();
+ }
+ };
}
private static void insertWithPointPost(final YangInstanceIdentifier path, final NormalizedNode data,
final EffectiveModelContext schemaContext, final PointParam point,
final NormalizedNodeContainer<?> readList, final boolean before,
final RestconfTransaction transaction) {
- final YangInstanceIdentifier parent = path.getParent().getParent();
+ final YangInstanceIdentifier parent = path.coerceParent().coerceParent();
transaction.remove(parent);
final var pointArg = YangInstanceIdentifierDeserializer.create(schemaContext, point.value()).path
.getLastPathArgument();
}
}
- private static void makePost(final YangInstanceIdentifier path, final NormalizedNode data,
- final EffectiveModelContext schemaContext, final RestconfTransaction transaction) {
+ private static ListenableFuture<? extends CommitInfo> makePost(final YangInstanceIdentifier path,
+ final NormalizedNode data, final EffectiveModelContext schemaContext,
+ final RestconfTransaction transaction) {
try {
transaction.create(path, data, schemaContext);
} catch (RestconfDocumentedException e) {
transaction.cancel();
throw e;
}
- }
-
- /**
- * Get location from {@link YangInstanceIdentifier} and {@link UriInfo}.
- *
- * @param uriInfo uri info
- * @param initialPath data path
- * @param schemaContext reference to {@link SchemaContext}
- * @return {@link URI}
- */
- private static URI resolveLocation(final UriInfo uriInfo, final YangInstanceIdentifier initialPath,
- final EffectiveModelContext schemaContext, final NormalizedNode data) {
- if (uriInfo == null) {
- return null;
- }
- YangInstanceIdentifier path = initialPath;
- if (data instanceof MapNode mapData) {
- final var children = mapData.body();
- if (!children.isEmpty()) {
- path = path.node(children.iterator().next().name());
- }
- }
-
- return uriInfo.getBaseUriBuilder().path("data").path(IdentifierCodec.serialize(path, schemaContext)).build();
+ return transaction.commit();
}
/**
*/
public static void checkItemDoesNotExists(final ListenableFuture<Boolean> existsFuture,
final YangInstanceIdentifier path) {
- final boolean exists;
- try {
- exists = existsFuture.get();
- } catch (ExecutionException e) {
- throw new RestconfDocumentedException("Failed to access " + path, e);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- throw new RestconfDocumentedException("Interrupted while accessing " + path, e);
- }
-
- if (exists) {
+ if (TransactionUtil.syncAccess(existsFuture, path)) {
LOG.trace("Operation via Restconf was not executed because data at {} already exists", path);
- throw new RestconfDocumentedException(
- "Data already exists", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS, path);
+ throw new RestconfDocumentedException("Data already exists", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS,
+ path);
}
}
}