2 * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.restconf.nb.rfc8040.rests.utils;
10 import com.google.common.util.concurrent.FluentFuture;
11 import java.util.Optional;
12 import javax.ws.rs.core.Response;
13 import javax.ws.rs.core.Response.Status;
14 import org.opendaylight.mdsal.common.api.CommitInfo;
15 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
16 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
17 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
18 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
19 import org.opendaylight.restconf.nb.rfc8040.InsertParam;
20 import org.opendaylight.restconf.nb.rfc8040.PointParam;
21 import org.opendaylight.restconf.nb.rfc8040.WriteDataParams;
22 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
23 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
24 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfTransaction;
25 import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
26 import org.opendaylight.yangtools.yang.common.ErrorTag;
27 import org.opendaylight.yangtools.yang.common.ErrorType;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
29 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
30 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
31 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
32 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
33 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
34 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
36 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
41 * Util class for put data to DS.
44 public final class PutDataTransactionUtil {
45 private static final String PUT_TX_TYPE = "PUT";
47 private PutDataTransactionUtil() {
51 * Check mount point and prepare variables for put data to DS. Close {@link DOMTransactionChain} if any
52 * inside of object {@link RestconfStrategy} provided as a parameter if any.
54 * @param payload data to put
55 * @param schemaContext reference to {@link EffectiveModelContext}
56 * @param strategy object that perform the actual DS operations
57 * @param params {@link WriteDataParams}
58 * @return {@link Response}
60 public static Response putData(final NormalizedNodePayload payload, final EffectiveModelContext schemaContext,
61 final RestconfStrategy strategy, final WriteDataParams params) {
62 final YangInstanceIdentifier path = payload.getInstanceIdentifierContext().getInstanceIdentifier();
64 final FluentFuture<Boolean> existsFuture = strategy.exists(LogicalDatastoreType.CONFIGURATION, path);
65 final FutureDataFactory<Boolean> existsResponse = new FutureDataFactory<>();
66 FutureCallbackTx.addCallback(existsFuture, PUT_TX_TYPE, existsResponse);
68 final ResponseFactory responseFactory =
69 new ResponseFactory(existsResponse.result ? Status.NO_CONTENT : Status.CREATED);
70 final FluentFuture<? extends CommitInfo> submitData = submitData(path, schemaContext, strategy,
71 payload.getData(), params);
72 //This method will close transactionChain if any
73 FutureCallbackTx.addCallback(submitData, PUT_TX_TYPE, responseFactory, path);
74 return responseFactory.build();
80 * @param path path of data
81 * @param schemaContext {@link SchemaContext}
82 * @param strategy object that perform the actual DS operations
84 * @param params {@link WriteDataParams}
85 * @return {@link FluentFuture}
87 private static FluentFuture<? extends CommitInfo> submitData(final YangInstanceIdentifier path,
88 final EffectiveModelContext schemaContext,
89 final RestconfStrategy strategy,
90 final NormalizedNode data,
91 final WriteDataParams params) {
92 final RestconfTransaction transaction = strategy.prepareWriteExecution();
93 final InsertParam insert = params.insert();
95 return makePut(path, schemaContext, transaction, data);
98 checkListAndOrderedType(schemaContext, path);
99 final NormalizedNode readData;
102 readData = readList(strategy, path.getParent());
103 if (readData == null || ((NormalizedNodeContainer<?>) readData).isEmpty()) {
104 return makePut(path, schemaContext, transaction, data);
106 transaction.remove(path.getParent());
107 transaction.replace(path, data, schemaContext);
108 transaction.replace(path.getParent(), readData, schemaContext);
109 return transaction.commit();
111 return makePut(path, schemaContext, transaction, data);
113 readData = readList(strategy, path.getParent());
114 if (readData == null || ((NormalizedNodeContainer<?>) readData).isEmpty()) {
115 return makePut(path, schemaContext, transaction, data);
117 insertWithPointPut(transaction, path, data, schemaContext, params.getPoint(),
118 (NormalizedNodeContainer<?>) readData, true);
119 return transaction.commit();
121 readData = readList(strategy, path.getParent());
122 if (readData == null || ((NormalizedNodeContainer<?>) readData).isEmpty()) {
123 return makePut(path, schemaContext, transaction, data);
125 insertWithPointPut(transaction, path, data, schemaContext, params.getPoint(),
126 (NormalizedNodeContainer<?>) readData, false);
127 return transaction.commit();
129 throw new RestconfDocumentedException(
130 "Used bad value of insert parameter. Possible values are first, last, before or after, "
131 + "but was: " + insert, ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE);
135 // FIXME: this method is only called from a context where we are modifying data. This should be part of strategy,
136 // requiring an already-open transaction. It also must return a future, so it can be properly composed.
137 static NormalizedNode readList(final RestconfStrategy strategy, final YangInstanceIdentifier path) {
138 return ReadDataTransactionUtil.readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path);
141 private static void insertWithPointPut(final RestconfTransaction transaction,
142 final YangInstanceIdentifier path,
143 final NormalizedNode data,
144 final EffectiveModelContext schemaContext, final PointParam point,
145 final NormalizedNodeContainer<?> readList, final boolean before) {
146 transaction.remove(path.getParent());
147 final InstanceIdentifierContext instanceIdentifier =
148 // FIXME: Point should be able to give us this method
149 ParserIdentifier.toInstanceIdentifier(point.value(), schemaContext, Optional.empty());
150 int lastItemPosition = 0;
151 for (final NormalizedNode nodeChild : readList.body()) {
152 if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
160 int lastInsertedPosition = 0;
161 final NormalizedNode emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
162 transaction.merge(YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
163 for (final NormalizedNode nodeChild : readList.body()) {
164 if (lastInsertedPosition == lastItemPosition) {
165 transaction.replace(path, data, schemaContext);
167 final YangInstanceIdentifier childPath = path.getParent().node(nodeChild.getIdentifier());
168 transaction.replace(childPath, nodeChild, schemaContext);
169 lastInsertedPosition++;
173 private static FluentFuture<? extends CommitInfo> makePut(final YangInstanceIdentifier path,
174 final EffectiveModelContext schemaContext,
175 final RestconfTransaction transaction,
176 final NormalizedNode data) {
177 transaction.replace(path, data, schemaContext);
178 return transaction.commit();
181 public static DataSchemaNode checkListAndOrderedType(final EffectiveModelContext ctx,
182 final YangInstanceIdentifier path) {
183 final YangInstanceIdentifier parent = path.getParent();
184 final DataSchemaContextNode<?> node = DataSchemaContextTree.from(ctx).findChild(parent).orElseThrow();
185 final DataSchemaNode dataSchemaNode = node.getDataSchemaNode();
187 if (dataSchemaNode instanceof ListSchemaNode) {
188 if (!((ListSchemaNode) dataSchemaNode).isUserOrdered()) {
189 throw new RestconfDocumentedException("Insert parameter can be used only with ordered-by user list.",
190 ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
192 return dataSchemaNode;
194 if (dataSchemaNode instanceof LeafListSchemaNode) {
195 if (!((LeafListSchemaNode) dataSchemaNode).isUserOrdered()) {
196 throw new RestconfDocumentedException(
197 "Insert parameter can be used only with ordered-by user leaf-list.",
198 ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
200 return dataSchemaNode;
202 throw new RestconfDocumentedException("Insert parameter can be used only with list or leaf-list",
203 ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);