*/
package org.opendaylight.restconf.nb.rfc8040.rests.utils;
+import static org.opendaylight.restconf.nb.rfc8040.databind.jaxrs.QueryParams.getSingleParameter;
import static org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserFieldsParameter.parseFieldsParameter;
import static org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserFieldsParameter.parseFieldsPaths;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.Sets;
-import com.google.common.primitives.Ints;
import com.google.common.util.concurrent.ListenableFuture;
+import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
+import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriInfo;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
-import org.opendaylight.restconf.common.context.WriterParameters;
-import org.opendaylight.restconf.common.context.WriterParameters.WriterParametersBuilder;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfError;
-import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
-import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.nb.rfc8040.ContentParameter;
+import org.opendaylight.restconf.nb.rfc8040.DepthParameter;
+import org.opendaylight.restconf.nb.rfc8040.FieldsParameter;
+import org.opendaylight.restconf.nb.rfc8040.WithDefaultsParameter;
+import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
-import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfDataServiceConstant.ReadData.WithDefaults;
+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.QNameModule;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
* </ul>
*/
public final class ReadDataTransactionUtil {
+ private static final Set<String> ALLOWED_PARAMETERS = Set.of(ContentParameter.uriName(), DepthParameter.uriName(),
+ FieldsParameter.uriName(), WithDefaultsParameter.uriName());
+ private static final List<String> POSSIBLE_CONTENT = Arrays.stream(ContentParameter.values())
+ .map(ContentParameter::uriValue)
+ .collect(Collectors.toUnmodifiableList());
+ private static final List<String> POSSIBLE_WITH_DEFAULTS = Arrays.stream(WithDefaultsParameter.values())
+ .map(WithDefaultsParameter::uriValue)
+ .collect(Collectors.toUnmodifiableList());
+
private static final String READ_TYPE_TX = "READ";
private ReadDataTransactionUtil() {
- throw new UnsupportedOperationException("Util class.");
+ // Hidden on purpose
}
/**
*
* @param identifier {@link InstanceIdentifierContext}
* @param uriInfo URI info
- * @return {@link WriterParameters}
+ * @return {@link QueryParameters}
*/
- public static WriterParameters parseUriParameters(final InstanceIdentifierContext<?> identifier,
+ public static QueryParameters parseUriParameters(final InstanceIdentifierContext<?> identifier,
final UriInfo uriInfo) {
- final WriterParametersBuilder builder = new WriterParametersBuilder();
-
+ final QueryParameters.Builder builder = QueryParameters.builder();
if (uriInfo == null) {
return builder.build();
}
// check only allowed parameters
- checkParametersTypes(uriInfo.getQueryParameters().keySet(),
- RestconfDataServiceConstant.ReadData.CONTENT,
- RestconfDataServiceConstant.ReadData.DEPTH,
- RestconfDataServiceConstant.ReadData.FIELDS, RestconfDataServiceConstant.ReadData.WITH_DEFAULTS);
-
- // read parameters from URI or set default values
- final List<String> content = uriInfo.getQueryParameters().getOrDefault(
- RestconfDataServiceConstant.ReadData.CONTENT,
- Collections.singletonList(RestconfDataServiceConstant.ReadData.ALL));
- final List<String> depth = uriInfo.getQueryParameters().getOrDefault(
- RestconfDataServiceConstant.ReadData.DEPTH,
- Collections.singletonList(RestconfDataServiceConstant.ReadData.UNBOUNDED));
- final List<String> withDefaults = uriInfo.getQueryParameters().getOrDefault(
- RestconfDataServiceConstant.ReadData.WITH_DEFAULTS,
- Collections.emptyList());
- // fields
- final List<String> fields = uriInfo.getQueryParameters().getOrDefault(
- RestconfDataServiceConstant.ReadData.FIELDS,
- Collections.emptyList());
-
- // parameter can be in URI at most once
- checkParameterCount(content, RestconfDataServiceConstant.ReadData.CONTENT);
- checkParameterCount(depth, RestconfDataServiceConstant.ReadData.DEPTH);
- checkParameterCount(fields, RestconfDataServiceConstant.ReadData.FIELDS);
- checkParameterCount(fields, RestconfDataServiceConstant.ReadData.WITH_DEFAULTS);
+ final MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
+ checkParametersTypes(queryParams.keySet(), ALLOWED_PARAMETERS);
// check and set content
- final String contentValue = content.get(0);
- if (!contentValue.equals(RestconfDataServiceConstant.ReadData.ALL)) {
- if (!contentValue.equals(RestconfDataServiceConstant.ReadData.CONFIG)
- && !contentValue.equals(RestconfDataServiceConstant.ReadData.NONCONFIG)) {
- throw new RestconfDocumentedException(
- new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE,
- "Invalid content parameter: " + contentValue, null,
- "The content parameter value must be either config, nonconfig or all (default)"));
- }
+ final String contentStr = getSingleParameter(queryParams, ContentParameter.uriName());
+ if (contentStr != null) {
+ builder.setContent(RestconfDocumentedException.throwIfNull(
+ ContentParameter.forUriValue(contentStr), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
+ "Invalid content parameter: %s, allowed values are %s", contentStr, POSSIBLE_CONTENT));
}
- builder.setContent(content.get(0));
-
// check and set depth
- if (!depth.get(0).equals(RestconfDataServiceConstant.ReadData.UNBOUNDED)) {
- final Integer value = Ints.tryParse(depth.get(0));
-
- if (value == null || value < RestconfDataServiceConstant.ReadData.MIN_DEPTH
- || value > RestconfDataServiceConstant.ReadData.MAX_DEPTH) {
- throw new RestconfDocumentedException(
- new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE,
- "Invalid depth parameter: " + depth, null,
- "The depth parameter must be an integer between 1 and 65535 or \"unbounded\""));
- } else {
- builder.setDepth(value);
+ final String depthStr = getSingleParameter(queryParams, DepthParameter.uriName());
+ if (depthStr != null) {
+ try {
+ builder.setDepth(DepthParameter.forUriValue(depthStr));
+ } catch (IllegalArgumentException e) {
+ throw new RestconfDocumentedException(e, new RestconfError(ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
+ "Invalid depth parameter: " + depthStr, null,
+ "The depth parameter must be an integer between 1 and 65535 or \"unbounded\""));
}
}
// check and set fields
- if (!fields.isEmpty()) {
+ final String fieldsStr = getSingleParameter(queryParams, FieldsParameter.uriName());
+ if (fieldsStr != null) {
+ // FIXME: parse a FieldsParameter instead
if (identifier.getMountPoint() != null) {
- builder.setFieldPaths(parseFieldsPaths(identifier, fields.get(0)));
+ builder.setFieldPaths(parseFieldsPaths(identifier, fieldsStr));
} else {
- builder.setFields(parseFieldsParameter(identifier, fields.get(0)));
+ builder.setFields(parseFieldsParameter(identifier, fieldsStr));
}
}
// check and set withDefaults parameter
- if (!withDefaults.isEmpty()) {
- final String str = withDefaults.get(0);
- final WithDefaults val = WithDefaults.forValue(str);
+ final String withDefaultsStr = getSingleParameter(queryParams, WithDefaultsParameter.uriName());
+ if (withDefaultsStr != null) {
+ final WithDefaultsParameter val = WithDefaultsParameter.forUriValue(withDefaultsStr);
if (val == null) {
- throw new RestconfDocumentedException(new RestconfError(RestconfError.ErrorType.PROTOCOL,
- RestconfError.ErrorTag.INVALID_VALUE, "Invalid with-defaults parameter: " + str, null,
- "The with-defaults parameter must be a string in " + WithDefaults.possibleValues()));
+ throw new RestconfDocumentedException(new RestconfError(ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
+ "Invalid with-defaults parameter: " + withDefaultsStr, null,
+ "The with-defaults parameter must be a string in " + POSSIBLE_WITH_DEFAULTS));
}
switch (val) {
builder.setTagged(true);
break;
default:
- builder.setWithDefault(val.value());
+ builder.setWithDefault(val);
}
}
+
return builder.build();
}
* Read specific type of data from data store via transaction. Close {@link DOMTransactionChain} if any
* inside of object {@link RestconfStrategy} provided as a parameter.
*
- * @param valueOfContent type of data to read (config, state, all)
+ * @param content type of data to read (config, state, all)
* @param path the path to read
* @param strategy {@link RestconfStrategy} - object that perform the actual DS operations
* @param withDefa value of with-defaults parameter
* @param ctx schema context
* @return {@link NormalizedNode}
*/
- public static @Nullable NormalizedNode readData(final @NonNull String valueOfContent,
+ public static @Nullable NormalizedNode readData(final @NonNull ContentParameter content,
final @NonNull YangInstanceIdentifier path,
final @NonNull RestconfStrategy strategy,
- final String withDefa, final EffectiveModelContext ctx) {
- switch (valueOfContent) {
- case RestconfDataServiceConstant.ReadData.CONFIG:
- if (withDefa == null) {
- return readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path, true);
- } else {
- return prepareDataByParamWithDef(
- readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path, true),
- path, withDefa, ctx);
- }
- case RestconfDataServiceConstant.ReadData.NONCONFIG:
- return readDataViaTransaction(strategy, LogicalDatastoreType.OPERATIONAL, path, true);
- case RestconfDataServiceConstant.ReadData.ALL:
+ final WithDefaultsParameter withDefa,
+ final EffectiveModelContext ctx) {
+ // FIXME: use a switch expression when they are available, removing source of RestconfDocumentedException
+ switch (content) {
+ case ALL:
return readAllData(strategy, path, withDefa, ctx);
+ case CONFIG:
+ final NormalizedNode read = readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path);
+ return withDefa == null ? read : prepareDataByParamWithDef(read, path, withDefa, ctx);
+ case NONCONFIG:
+ return readDataViaTransaction(strategy, LogicalDatastoreType.OPERATIONAL, path);
default:
throw new RestconfDocumentedException(
- new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE,
- "Invalid content parameter: " + valueOfContent, null,
+ new RestconfError(ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
+ "Invalid content parameter: " + content.uriValue(), null,
"The content parameter value must be either config, nonconfig or all (default)"));
}
}
* Read specific type of data from data store via transaction with specified subtrees that should only be read.
* Close {@link DOMTransactionChain} inside of object {@link RestconfStrategy} provided as a parameter.
*
- * @param valueOfContent type of data to read (config, state, all)
+ * @param content type of data to read (config, state, all)
* @param path the parent path to read
* @param strategy {@link RestconfStrategy} - object that perform the actual DS operations
* @param withDefa value of with-defaults parameter
* @param fields paths to selected subtrees which should be read, relative to to the parent path
* @return {@link NormalizedNode}
*/
- public static @Nullable NormalizedNode readData(final @NonNull String valueOfContent,
+ public static @Nullable NormalizedNode readData(final @NonNull ContentParameter content,
final @NonNull YangInstanceIdentifier path, final @NonNull RestconfStrategy strategy,
- final @Nullable String withDefa, @NonNull final EffectiveModelContext ctx,
+ final @Nullable WithDefaultsParameter withDefa, @NonNull final EffectiveModelContext ctx,
final @NonNull List<YangInstanceIdentifier> fields) {
- switch (valueOfContent) {
- case RestconfDataServiceConstant.ReadData.CONFIG:
- if (withDefa == null) {
- return readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path, true, fields);
- } else {
- return prepareDataByParamWithDef(
- readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path, true, fields),
- path, withDefa, ctx);
- }
- case RestconfDataServiceConstant.ReadData.NONCONFIG:
- return readDataViaTransaction(strategy, LogicalDatastoreType.OPERATIONAL, path, true, fields);
- case RestconfDataServiceConstant.ReadData.ALL:
+ // FIXME: use a switch expression when they are available, removing source of RestconfDocumentedException
+ switch (content) {
+ case ALL:
return readAllData(strategy, path, withDefa, ctx, fields);
+ case CONFIG:
+ final NormalizedNode read = readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path,
+ fields);
+ return withDefa == null ? read : prepareDataByParamWithDef(read, path, withDefa, ctx);
+ case NONCONFIG:
+ return readDataViaTransaction(strategy, LogicalDatastoreType.OPERATIONAL, path, fields);
default:
- throw new RestconfDocumentedException(new RestconfError(RestconfError.ErrorType.PROTOCOL,
- RestconfError.ErrorTag.INVALID_VALUE, "Invalid content parameter: " + valueOfContent, null,
+ throw new RestconfDocumentedException(new RestconfError(ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
+ "Invalid content parameter: " + content.uriValue(), null,
"The content parameter value must be either config, nonconfig or all (default)"));
}
}
- /**
- * Check if URI does not contain value for the same parameter more than once.
- *
- * @param parameterValues URI parameter values
- * @param parameterName URI parameter name
- */
- @VisibleForTesting
- static void checkParameterCount(final @NonNull List<String> parameterValues, final @NonNull String parameterName) {
- if (parameterValues.size() > 1) {
- throw new RestconfDocumentedException(
- "Parameter " + parameterName + " can appear at most once in request URI",
- ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
- }
- }
-
/**
* Check if URI does not contain not allowed parameters for specified operation.
*
* @param allowedParameters allowed parameters for operation
*/
@VisibleForTesting
- static void checkParametersTypes(final @NonNull Set<String> usedParameters,
- final @NonNull String... allowedParameters) {
- // FIXME: there should be a speedier way to do this
- final Set<String> notAllowedParameters = Sets.newHashSet(usedParameters);
- notAllowedParameters.removeAll(Sets.newHashSet(allowedParameters));
-
- if (!notAllowedParameters.isEmpty()) {
+ static void checkParametersTypes(final Set<String> usedParameters, final Set<String> allowedParameters) {
+ if (!allowedParameters.containsAll(usedParameters)) {
+ final Set<String> notAllowedParameters = usedParameters.stream()
+ .filter(param -> !allowedParameters.contains(param))
+ .collect(Collectors.toSet());
throw new RestconfDocumentedException(
- "Not allowed parameters for " + READ_TYPE_TX + " operation: " + notAllowedParameters,
- RestconfError.ErrorType.PROTOCOL,
- RestconfError.ErrorTag.INVALID_VALUE);
+ "Not allowed parameters for " + READ_TYPE_TX + " operation: " + notAllowedParameters,
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
}
}
private static NormalizedNode prepareDataByParamWithDef(final NormalizedNode result,
- final YangInstanceIdentifier path, final String withDefa, final EffectiveModelContext ctx) {
+ final YangInstanceIdentifier path, final WithDefaultsParameter withDefa, final EffectiveModelContext ctx) {
boolean trim;
switch (withDefa) {
- case "trim":
+ case TRIM:
trim = true;
break;
- case "explicit":
+ case EXPLICIT:
trim = false;
break;
default:
- throw new RestconfDocumentedException("");
+ throw new RestconfDocumentedException("Unsupported with-defaults value " + withDefa.uriValue());
}
final DataSchemaContextTree baseSchemaCtxTree = DataSchemaContextTree.from(ctx);
* @return {@link NormalizedNode}
*/
static @Nullable NormalizedNode readDataViaTransaction(final @NonNull RestconfStrategy strategy,
- final LogicalDatastoreType store, final YangInstanceIdentifier path,
- final boolean closeTransactionChain) {
- final ListenableFuture<Optional<NormalizedNode>> listenableFuture = strategy.read(store, path);
- return extractReadData(strategy, path, closeTransactionChain, listenableFuture);
+ final LogicalDatastoreType store, final YangInstanceIdentifier path) {
+ return extractReadData(strategy, path, strategy.read(store, path));
}
/**
*/
private static @Nullable NormalizedNode readDataViaTransaction(final @NonNull RestconfStrategy strategy,
final @NonNull LogicalDatastoreType store, final @NonNull YangInstanceIdentifier path,
- final boolean closeTransactionChain, final @NonNull List<YangInstanceIdentifier> fields) {
- final ListenableFuture<Optional<NormalizedNode>> listenableFuture = strategy.read(store, path, fields);
- return extractReadData(strategy, path, closeTransactionChain, listenableFuture);
+ final @NonNull List<YangInstanceIdentifier> fields) {
+ return extractReadData(strategy, path, strategy.read(store, path, fields));
}
private static NormalizedNode extractReadData(final RestconfStrategy strategy,
- final YangInstanceIdentifier path, final boolean closeTransactionChain,
- final ListenableFuture<Optional<NormalizedNode>> dataFuture) {
+ final YangInstanceIdentifier path, final ListenableFuture<Optional<NormalizedNode>> dataFuture) {
final NormalizedNodeFactory dataFactory = new NormalizedNodeFactory();
- if (closeTransactionChain) {
- //Method close transactionChain if any
- FutureCallbackTx.addCallback(dataFuture, READ_TYPE_TX, dataFactory, strategy, path);
- } else {
- FutureCallbackTx.addCallback(dataFuture, READ_TYPE_TX, dataFactory);
- }
+ FutureCallbackTx.addCallback(dataFuture, READ_TYPE_TX, dataFactory, path);
return dataFactory.build();
}
* @return {@link NormalizedNode}
*/
private static @Nullable NormalizedNode readAllData(final @NonNull RestconfStrategy strategy,
- final YangInstanceIdentifier path, final String withDefa, final EffectiveModelContext ctx) {
+ final YangInstanceIdentifier path, final WithDefaultsParameter withDefa, final EffectiveModelContext ctx) {
// PREPARE STATE DATA NODE
- final NormalizedNode stateDataNode = readDataViaTransaction(strategy, LogicalDatastoreType.OPERATIONAL, path,
- false);
-
+ final NormalizedNode stateDataNode = readDataViaTransaction(strategy, LogicalDatastoreType.OPERATIONAL, path);
// PREPARE CONFIG DATA NODE
- final NormalizedNode configDataNode;
- //Here will be closed transactionChain if any
- if (withDefa == null) {
- configDataNode = readDataViaTransaction(
- strategy, LogicalDatastoreType.CONFIGURATION, path, true);
- } else {
- configDataNode = prepareDataByParamWithDef(
- readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path, true),
- path, withDefa, ctx);
- }
+ final NormalizedNode configDataNode = readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION,
+ path);
- return mergeConfigAndSTateDataIfNeeded(stateDataNode, configDataNode);
+ return mergeConfigAndSTateDataIfNeeded(stateDataNode,
+ withDefa == null ? configDataNode : prepareDataByParamWithDef(configDataNode, path, withDefa, ctx));
}
/**
* @return {@link NormalizedNode}
*/
private static @Nullable NormalizedNode readAllData(final @NonNull RestconfStrategy strategy,
- final @NonNull YangInstanceIdentifier path, final @Nullable String withDefa,
+ final @NonNull YangInstanceIdentifier path, final @Nullable WithDefaultsParameter withDefa,
final @NonNull EffectiveModelContext ctx, final @NonNull List<YangInstanceIdentifier> fields) {
// PREPARE STATE DATA NODE
final NormalizedNode stateDataNode = readDataViaTransaction(strategy, LogicalDatastoreType.OPERATIONAL, path,
- false, fields);
+ fields);
// PREPARE CONFIG DATA NODE
- final NormalizedNode configDataNode;
- //Here will be closed transactionChain if any
- if (withDefa == null) {
- configDataNode = readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path, true, fields);
- } else {
- configDataNode = prepareDataByParamWithDef(
- readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path, true, fields),
- path, withDefa, ctx);
- }
-
- return mergeConfigAndSTateDataIfNeeded(stateDataNode, configDataNode);
+ final NormalizedNode configDataNode = readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path,
+ fields);
+ return mergeConfigAndSTateDataIfNeeded(stateDataNode,
+ withDefa == null ? configDataNode : prepareDataByParamWithDef(configDataNode, path, withDefa, ctx));
}
private static NormalizedNode mergeConfigAndSTateDataIfNeeded(final NormalizedNode stateDataNode,