*/
package org.opendaylight.restconf.api;
+import static java.util.Objects.requireNonNull;
+
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.opendaylight.restconf.api.query.PrettyPrintParam;
import org.opendaylight.restconf.api.query.RestconfQueryParam;
/**
* The set of {@link RestconfQueryParam}s governing output formatting.
+ *
+ * @param prettyPrint the {@link PrettyPrintParam} parameter
*/
@NonNullByDefault
-public interface FormatParameters extends Immutable {
+public record FormatParameters(PrettyPrintParam prettyPrint) implements Immutable {
+ public static final FormatParameters COMPACT = new FormatParameters(PrettyPrintParam.FALSE);
+ public static final FormatParameters PRETTY = new FormatParameters(PrettyPrintParam.TRUE);
+
/**
* Return the {@link PrettyPrintParam} parameter.
- *
- * @return the {@link PrettyPrintParam} parameter
*/
- PrettyPrintParam prettyPrint();
+ public FormatParameters {
+ requireNonNull(prettyPrint);
+ }
}
*/
package org.opendaylight.restconf.api;
-import static java.util.Objects.requireNonNull;
-
import com.google.common.base.MoreObjects;
import com.google.common.base.MoreObjects.ToStringHelper;
import java.io.IOException;
import org.opendaylight.yangtools.concepts.Immutable;
/**
- * A body which is capable of being formatted to an {@link OutputStream} in either JSON or XML format.
+ * A body which is capable of being formatted to an {@link OutputStream} in either JSON or XML format using particular
+ * {@link FormatParameters}.
*/
@NonNullByDefault
public abstract class FormattableBody implements Immutable {
- private final FormatParameters format;
-
- protected FormattableBody(final FormatParameters format) {
- this.format = requireNonNull(format);
- }
-
/**
* Write the content of this body as a JSON document.
*
+ * @param format {@link FormatParameters}
* @param out output stream
* @throws IOException if an IO error occurs.
*/
- public final void formatToJSON(final OutputStream out) throws IOException {
- formatToJSON(requireNonNull(out), format);
- }
-
- protected abstract void formatToJSON(OutputStream out, FormatParameters format) throws IOException;
+ public abstract void formatToJSON(FormatParameters format, OutputStream out) throws IOException;
/**
* Write the content of this body as an XML document.
*
+ * @param format {@link FormatParameters}
* @param out output stream
* @throws IOException if an IO error occurs.
*/
- public final void formatToXML(final OutputStream out) throws IOException {
- formatToXML(requireNonNull(out), format);
- }
+ public abstract void formatToXML(FormatParameters format, OutputStream out) throws IOException;
- protected abstract void formatToXML(OutputStream out, FormatParameters format) throws IOException;
+ protected abstract ToStringHelper addToStringAttributes(ToStringHelper helper);
@Override
public final String toString() {
return addToStringAttributes(MoreObjects.toStringHelper(this).omitNullValues()).toString();
}
-
- protected ToStringHelper addToStringAttributes(final ToStringHelper helper) {
- return helper.add("prettyPrint", format.prettyPrint().value());
- }
}
+++ /dev/null
-/*
- * Copyright (c) 2024 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.api;
-
-import static java.util.Objects.requireNonNull;
-
-import com.google.common.collect.ImmutableMap;
-import java.util.Collection;
-import java.util.Map.Entry;
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.restconf.api.query.RestconfQueryParam;
-
-/**
- * Default implementation of {@link QueryParameters}.
- */
-@NonNullByDefault
-record ImmutableQueryParameters(ImmutableMap<String, String> params) implements QueryParameters {
- static final ImmutableQueryParameters EMPTY = new ImmutableQueryParameters(ImmutableMap.of());
-
- ImmutableQueryParameters {
- requireNonNull(params);
- }
-
- ImmutableQueryParameters(final Collection<RestconfQueryParam<?>> params) {
- // TODO: consider caching common request parameter combinations
- this(params.stream()
- .collect(ImmutableMap.toImmutableMap(RestconfQueryParam::paramName, RestconfQueryParam::paramValue)));
- }
-
- @Override
- public boolean isEmpty() {
- return params.isEmpty();
- }
-
- @Override
- public Collection<Entry<String, String>> asCollection() {
- return params.entrySet();
- }
-
- @Override
- public @Nullable String lookup(final String paramName) {
- return params.get(requireNonNull(paramName));
- }
-
- @Override
- public String toString() {
- return QueryParameters.class.getSimpleName() + "(" + params + ")";
- }
-}
*/
package org.opendaylight.restconf.api;
+import static java.util.Objects.requireNonNull;
+
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.function.Function;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.restconf.api.query.RestconfQueryParam;
* once.
*/
@NonNullByDefault
-public interface QueryParameters extends Immutable {
+public final class QueryParameters implements Immutable {
+ static final QueryParameters EMPTY = new QueryParameters(ImmutableMap.of());
- boolean isEmpty();
+ private final ImmutableMap<String, String> params;
- Collection<? extends Entry<String, String>> asCollection();
+ private QueryParameters(final ImmutableMap<String, String> params) {
+ this.params = requireNonNull(params);
+ }
- @Nullable String lookup(String paramName);
+ private QueryParameters(final Collection<RestconfQueryParam<?>> params) {
+ // TODO: consider caching common request parameter combinations
+ this(params.stream()
+ .collect(ImmutableMap.toImmutableMap(RestconfQueryParam::paramName, RestconfQueryParam::paramValue)));
+ }
- static QueryParameters of() {
- return ImmutableQueryParameters.EMPTY;
+ public static QueryParameters of() {
+ return EMPTY;
}
- static QueryParameters of(final String paramName, final String paramValue) {
- return new ImmutableQueryParameters(ImmutableMap.of(paramName, paramValue));
+ public static QueryParameters of(final String paramName, final String paramValue) {
+ return new QueryParameters(ImmutableMap.of(paramName, paramValue));
}
- static QueryParameters of(final Entry<String, String> entry) {
+ public static QueryParameters of(final Entry<String, String> entry) {
return of(entry.getKey(), entry.getValue());
}
- static QueryParameters of(final RestconfQueryParam<?> param) {
+ public static QueryParameters of(final RestconfQueryParam<?> param) {
return of(param.paramName(), param.paramValue());
}
- static QueryParameters of(final RestconfQueryParam<?>... params) {
+ public static QueryParameters of(final RestconfQueryParam<?>... params) {
return switch (params.length) {
case 0 -> of();
case 1 -> of(params[0]);
- default -> new ImmutableQueryParameters(Arrays.asList(params));
+ default -> new QueryParameters(Arrays.asList(params));
};
}
- static QueryParameters of(final Collection<RestconfQueryParam<?>> params) {
+ public static QueryParameters of(final Collection<RestconfQueryParam<?>> params) {
return params instanceof List ? of((List<RestconfQueryParam<?>>) params) : switch (params.size()) {
case 0 -> of();
case 1 -> of(params.iterator().next());
- default -> new ImmutableQueryParameters(params);
+ default -> new QueryParameters(params);
};
}
- static QueryParameters of(final List<RestconfQueryParam<?>> params) {
+ public static QueryParameters of(final List<RestconfQueryParam<?>> params) {
return switch (params.size()) {
case 0 -> of();
case 1 -> of(params.get(0));
- default -> new ImmutableQueryParameters(params);
+ default -> new QueryParameters(params);
};
}
- static QueryParameters of(final Map<String, String> params) {
- return params.isEmpty() ? of() : new ImmutableQueryParameters(ImmutableMap.copyOf(params));
+ public static QueryParameters of(final Map<String, String> params) {
+ return params.isEmpty() ? of() : new QueryParameters(ImmutableMap.copyOf(params));
}
/**
* from JAX-RS's {@code UriInfo}.
*
* @param multiParams Input map
- * @return An {@link ImmutableQueryParameters} instance
+ * @return A {@link QueryParameters} instance
* @throws NullPointerException if {@code uriInfo} is {@code null}
* @throws IllegalArgumentException if there are multiple values for a parameter
*/
- static QueryParameters ofMultiValue(final Map<String, List<String>> multiParams) {
+ public static QueryParameters ofMultiValue(final Map<String, List<String>> multiParams) {
if (multiParams.isEmpty()) {
return of();
}
}
final var params = builder.build();
- return params.isEmpty() ? of() : new ImmutableQueryParameters(params);
+ return params.isEmpty() ? of() : new QueryParameters(params);
+ }
+
+ public boolean isEmpty() {
+ return params.isEmpty();
+ }
+
+ public Collection<Entry<String, String>> asCollection() {
+ return params.entrySet();
+ }
+
+ public @Nullable String lookup(final String paramName) {
+ return params.get(requireNonNull(paramName));
}
+
+ public <T> @Nullable T lookup(final String paramName, final Function<String, T> parseFunction) {
+ final var str = lookup(paramName);
+ if (str != null) {
+ return parseFunction.apply(str);
+ }
+ return null;
+ }
+
+ public QueryParameters withoutParam(final String paramName) {
+ return params.containsKey(paramName) ? of(Maps.filterKeys(params, key -> !key.equals(paramName))) : this;
+ }
+
+ @Override
+ public String toString() {
+ return QueryParameters.class.getSimpleName() + "(" + params + ")";
+ }
+
}
case "config" -> CONFIG;
case "nonconfig" -> NONCONFIG;
default -> throw new IllegalArgumentException(
- "Value can be 'all', 'config' or 'nonconfig', not '" + uriValue + "'");
+ "Invalid " + uriName + " value: Value can be 'all', 'config' or 'nonconfig', not '" + uriValue + "'");
};
}
}
try {
return parse(uriValue);
} catch (ParseException e) {
- throw new IllegalArgumentException(e.getMessage() + " [at offset " + e.getErrorOffset() + "]", e);
+ throw new IllegalArgumentException("Invalid " + uriName + " value: " + e.getMessage()
+ + " [at offset " + e.getErrorOffset() + "]", e);
}
}
return switch (uriValue) {
case "false" -> FALSE;
case "true" -> TRUE;
- default -> throw new IllegalArgumentException("Value can be 'false' or 'true', not '" + uriValue + "'");
+ default -> throw new IllegalArgumentException(
+ "Invalid " + uriName + " value: Value can be 'false' or 'true', not '" + uriValue + "'");
};
}
}
public static @NonNull WithDefaultsParam forUriValue(final String uriValue) {
- return of(WithDefaultsMode.ofName(uriValue));
+ try {
+ return of(WithDefaultsMode.ofName(uriValue));
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Invalid " + uriName + " value: " + e.getMessage(), e);
+ }
}
@Override
*/
package org.opendaylight.restconf.nb.jaxrs;
+import static java.util.Objects.requireNonNull;
+
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.core.Response;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.restconf.api.FormatParameters;
import org.opendaylight.restconf.api.FormattableBody;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
* A {@link JaxRsRestconfCallback} producing a {@link FormattableBody}.
*/
final class FormattableBodyCallback extends JaxRsRestconfCallback<FormattableBody> {
- FormattableBodyCallback(final AsyncResponse ar) {
+ private final @NonNull FormatParameters format;
+
+ FormattableBodyCallback(final AsyncResponse ar, final FormatParameters format) {
super(ar);
+ this.format = requireNonNull(format);
}
@Override
Response transform(final FormattableBody result) throws RestconfDocumentedException {
- return Response.ok().entity(result).build();
+ return Response.ok().entity(new JaxRsFormattableBody(result, format)).build();
}
}
--- /dev/null
+/*
+ * Copyright (c) 2024 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.nb.jaxrs;
+
+import static java.util.Objects.requireNonNull;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.restconf.api.FormatParameters;
+import org.opendaylight.restconf.api.FormattableBody;
+
+/**
+ * A bridge capturing a {@link FormattableBody} and {@link FormatParameters}./
+ */
+@NonNullByDefault
+record JaxRsFormattableBody(FormattableBody body, FormatParameters format) {
+ JaxRsFormattableBody {
+ requireNonNull(body);
+ requireNonNull(format);
+ }
+}
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
-import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.restconf.api.FormatParameters;
import org.opendaylight.restconf.api.FormattableBody;
-abstract sealed class FormattableBodyWriter implements MessageBodyWriter<FormattableBody>
- permits JsonFormattableBody, XmlFormattableBody {
+abstract sealed class JaxRsFormattableBodyWriter implements MessageBodyWriter<JaxRsFormattableBody>
+ permits JsonJaxRsFormattableBodyWriter, XmlJaxRsFormattableBodyWriter {
@Override
public final boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations,
final MediaType mediaType) {
}
@Override
- public final void writeTo(final FormattableBody body, final Class<?> type, final Type genericType,
+ public final void writeTo(final JaxRsFormattableBody entity, final Class<?> type, final Type genericType,
final Annotation[] annotations, final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders,
final OutputStream entityStream) throws IOException {
- writeTo(requireNonNull(body), requireNonNull(entityStream));
+ writeTo(entity.body(), entity.format(), requireNonNull(entityStream));
}
- abstract void writeTo(@NonNull FormattableBody body, @NonNull OutputStream out) throws IOException;
+ @NonNullByDefault
+ abstract void writeTo(FormattableBody body, FormatParameters format, OutputStream out) throws IOException;
}
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.restconf.api.ApiPath;
+import org.opendaylight.restconf.api.FormatParameters;
import org.opendaylight.restconf.api.MediaTypes;
import org.opendaylight.restconf.api.QueryParameters;
+import org.opendaylight.restconf.api.query.PrettyPrintParam;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfError;
import org.opendaylight.restconf.common.errors.RestconfFuture;
import org.opendaylight.restconf.server.api.OperationInputBody;
import org.opendaylight.restconf.server.api.PatchStatusContext;
import org.opendaylight.restconf.server.api.RestconfServer;
+import org.opendaylight.restconf.server.api.ServerRequest;
import org.opendaylight.restconf.server.api.XmlChildBody;
import org.opendaylight.restconf.server.api.XmlDataPostBody;
import org.opendaylight.restconf.server.api.XmlOperationInputBody;
};
private final @NonNull RestconfServer server;
+ private final @NonNull ServerRequest emptyRequest;
+ private final @NonNull PrettyPrintParam prettyPrint;
- public JaxRsRestconf(final RestconfServer server) {
+ public JaxRsRestconf(final RestconfServer server, final PrettyPrintParam prettyPrint) {
this.server = requireNonNull(server);
+ this.prettyPrint = requireNonNull(prettyPrint);
+ emptyRequest = ServerRequest.of(QueryParameters.of(), prettyPrint);
+ }
+
+ private @NonNull ServerRequest requestOf(final UriInfo uriInfo) {
+ final QueryParameters params;
+ try {
+ params = QueryParameters.ofMultiValue(uriInfo.getQueryParameters());
+ } catch (IllegalArgumentException e) {
+ throw new BadRequestException(e.getMessage(), e);
+ }
+ return params.isEmpty() ? emptyRequest : ServerRequest.of(params, prettyPrint);
}
@Override
return ApiPath.class.equals(rawType) ? (ParamConverter<T>) API_PATH_CONVERTER : null;
}
- private static @NonNull QueryParameters queryParams(final UriInfo uriInfo) {
- try {
- return QueryParameters.ofMultiValue(uriInfo.getQueryParameters());
- } catch (IllegalArgumentException e) {
- throw new BadRequestException(e.getMessage(), e);
- }
- }
-
/**
* Delete the target data resource.
*
@SuppressWarnings("checkstyle:abbreviationAsWordInName")
public void dataDELETE(@Encoded @PathParam("identifier") final ApiPath identifier,
@Suspended final AsyncResponse ar) {
- server.dataDELETE(identifier).addCallback(new JaxRsRestconfCallback<>(ar) {
+ server.dataDELETE(emptyRequest, identifier).addCallback(new JaxRsRestconfCallback<>(ar) {
@Override
Response transform(final Empty result) {
return Response.noContent().build();
MediaType.TEXT_XML
})
public void dataGET(@Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
- completeDataGET(server.dataGET(queryParams(uriInfo)), ar);
+ completeDataGET(server.dataGET(requestOf(uriInfo)), ar);
}
/**
})
public void dataGET(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
@Suspended final AsyncResponse ar) {
- completeDataGET(server.dataGET(identifier, queryParams(uriInfo)), ar);
+ completeDataGET(server.dataGET(requestOf(uriInfo), identifier), ar);
}
private static void completeDataGET(final RestconfFuture<DataGetResult> future, final AsyncResponse ar) {
})
public void dataXmlPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
try (var xmlBody = new XmlResourceBody(body)) {
- completeDataPATCH(server.dataPATCH(xmlBody), ar);
+ completeDataPATCH(server.dataPATCH(emptyRequest, xmlBody), ar);
}
}
public void dataXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
@Suspended final AsyncResponse ar) {
try (var xmlBody = new XmlResourceBody(body)) {
- completeDataPATCH(server.dataPATCH(identifier, xmlBody), ar);
+ completeDataPATCH(server.dataPATCH(emptyRequest, identifier, xmlBody), ar);
}
}
})
public void dataJsonPATCH(final InputStream body, @Suspended final AsyncResponse ar) {
try (var jsonBody = new JsonResourceBody(body)) {
- completeDataPATCH(server.dataPATCH(jsonBody), ar);
+ completeDataPATCH(server.dataPATCH(emptyRequest, jsonBody), ar);
}
}
public void dataJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
@Suspended final AsyncResponse ar) {
try (var jsonBody = new JsonResourceBody(body)) {
- completeDataPATCH(server.dataPATCH(identifier, jsonBody), ar);
+ completeDataPATCH(server.dataPATCH(emptyRequest, identifier, jsonBody), ar);
}
}
public void dataYangJsonPATCH(final InputStream body, @Context final UriInfo uriInfo,
@Suspended final AsyncResponse ar) {
try (var jsonBody = new JsonPatchBody(body)) {
- completeDataYangPATCH(server.dataPATCH(queryParams(uriInfo), jsonBody), ar);
+ completeDataYangPATCH(server.dataPATCH(requestOf(uriInfo), jsonBody), ar);
}
}
public void dataYangJsonPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
@Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
try (var jsonBody = new JsonPatchBody(body)) {
- completeDataYangPATCH(server.dataPATCH(identifier, queryParams(uriInfo), jsonBody), ar);
+ completeDataYangPATCH(server.dataPATCH(requestOf(uriInfo), identifier, jsonBody), ar);
}
}
public void dataYangXmlPATCH(final InputStream body, @Context final UriInfo uriInfo,
@Suspended final AsyncResponse ar) {
try (var xmlBody = new XmlPatchBody(body)) {
- completeDataYangPATCH(server.dataPATCH(queryParams(uriInfo), xmlBody), ar);
+ completeDataYangPATCH(server.dataPATCH(requestOf(uriInfo), xmlBody), ar);
}
}
public void dataYangXmlPATCH(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
@Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
try (var xmlBody = new XmlPatchBody(body)) {
- completeDataYangPATCH(server.dataPATCH(identifier, queryParams(uriInfo), xmlBody), ar);
+ completeDataYangPATCH(server.dataPATCH(requestOf(uriInfo), identifier, xmlBody), ar);
}
}
Response transform(final DataYangPatchResult result) {
final var status = result.status();
final var builder = Response.status(statusOf(status))
- .entity(new YangPatchStatusBody(result.params(), status));
+ .entity(new YangPatchStatusBody(status));
fillConfigurationMetadata(builder, result);
return builder.build();
}
public void postDataJSON(final InputStream body, @Context final UriInfo uriInfo,
@Suspended final AsyncResponse ar) {
try (var jsonBody = new JsonChildBody(body)) {
- completeDataPOST(server.dataPOST(queryParams(uriInfo), jsonBody), uriInfo, ar);
+ final var request = requestOf(uriInfo);
+ completeDataPOST(server.dataPOST(request, jsonBody), request.format(), uriInfo, ar);
}
}
})
public void postDataJSON(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
@Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
- completeDataPOST(server.dataPOST(identifier, queryParams(uriInfo), new JsonDataPostBody(body)), uriInfo, ar);
+ final var request = requestOf(uriInfo);
+ completeDataPOST(server.dataPOST(request, identifier, new JsonDataPostBody(body)), request.format(), uriInfo,
+ ar);
}
/**
})
public void postDataXML(final InputStream body, @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
try (var xmlBody = new XmlChildBody(body)) {
- completeDataPOST(server.dataPOST(queryParams(uriInfo), xmlBody), uriInfo, ar);
+ final var request = requestOf(uriInfo);
+ completeDataPOST(server.dataPOST(request, xmlBody), request.format(), uriInfo, ar);
}
}
})
public void postDataXML(@Encoded @PathParam("identifier") final ApiPath identifier, final InputStream body,
@Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
- completeDataPOST(server.dataPOST(identifier, queryParams(uriInfo), new XmlDataPostBody(body)), uriInfo, ar);
+ final var request = requestOf(uriInfo);
+ completeDataPOST(server.dataPOST(request, identifier, new XmlDataPostBody(body)), request.format(), uriInfo,
+ ar);
}
- private static void completeDataPOST(final RestconfFuture<? extends DataPostResult> future, final UriInfo uriInfo,
- final AsyncResponse ar) {
+ private static void completeDataPOST(final RestconfFuture<? extends DataPostResult> future,
+ final FormatParameters format, final UriInfo uriInfo, final AsyncResponse ar) {
future.addCallback(new JaxRsRestconfCallback<DataPostResult>(ar) {
@Override
Response transform(final DataPostResult result) {
}
if (result instanceof InvokeResult invokeOperation) {
final var output = invokeOperation.output();
- return output == null ? Response.noContent().build() : Response.ok().entity(output).build();
+ return output == null ? Response.noContent().build()
+ : Response.ok().entity(new JaxRsFormattableBody(output, format)).build();
}
LOG.error("Unhandled result {}", result);
return Response.serverError().build();
})
public void dataJsonPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
try (var jsonBody = new JsonResourceBody(body)) {
- completeDataPUT(server.dataPUT(queryParams(uriInfo), jsonBody), ar);
+ completeDataPUT(server.dataPUT(requestOf(uriInfo), jsonBody), ar);
}
}
public void dataJsonPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
final InputStream body, @Suspended final AsyncResponse ar) {
try (var jsonBody = new JsonResourceBody(body)) {
- completeDataPUT(server.dataPUT(identifier, queryParams(uriInfo), jsonBody), ar);
+ completeDataPUT(server.dataPUT(requestOf(uriInfo), identifier, jsonBody), ar);
}
}
})
public void dataXmlPUT(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
try (var xmlBody = new XmlResourceBody(body)) {
- completeDataPUT(server.dataPUT(queryParams(uriInfo), xmlBody), ar);
+ completeDataPUT(server.dataPUT(requestOf(uriInfo), xmlBody), ar);
}
}
public void dataXmlPUT(@Encoded @PathParam("identifier") final ApiPath identifier, @Context final UriInfo uriInfo,
final InputStream body, @Suspended final AsyncResponse ar) {
try (var xmlBody = new XmlResourceBody(body)) {
- completeDataPUT(server.dataPUT(identifier, queryParams(uriInfo), xmlBody), ar);
+ completeDataPUT(server.dataPUT(requestOf(uriInfo), identifier, xmlBody), ar);
}
}
MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON
})
public void operationsGET(@Suspended final AsyncResponse ar) {
- server.operationsGET().addCallback(new FormattableBodyCallback(ar));
+ server.operationsGET(emptyRequest).addCallback(new FormattableBodyCallback(ar, emptyRequest.format()));
}
/**
MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON
})
public void operationsGET(@PathParam("operation") final ApiPath operation, @Suspended final AsyncResponse ar) {
- server.operationsGET(operation).addCallback(new FormattableBodyCallback(ar));
+ server.operationsGET(emptyRequest, operation)
+ .addCallback(new FormattableBodyCallback(ar, emptyRequest.format()));
}
/**
private void operationsPOST(final ApiPath identifier, final UriInfo uriInfo, final AsyncResponse ar,
final OperationInputBody body) {
- server.operationsPOST(uriInfo.getBaseUri(), identifier, queryParams(uriInfo), body)
+ server.operationsPOST(requestOf(uriInfo), uriInfo.getBaseUri(), identifier, body)
.addCallback(new JaxRsRestconfCallback<>(ar) {
@Override
Response transform(final InvokeResult result) {
MediaType.TEXT_XML
})
public void yangLibraryVersionGET(@Suspended final AsyncResponse ar) {
- server.yangLibraryVersionGET().addCallback(new FormattableBodyCallback(ar));
+ server.yangLibraryVersionGET(emptyRequest).addCallback(new FormattableBodyCallback(ar, emptyRequest.format()));
}
// FIXME: References to these resources are generated by our yang-library implementation. That means:
@Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
public void modulesYangGET(@PathParam("fileName") final String fileName,
@QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
- completeModulesGET(server.modulesYangGET(fileName, revision), ar);
+ completeModulesGET(server.modulesYangGET(emptyRequest, fileName, revision), ar);
}
/**
public void modulesYangGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
@PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
@Suspended final AsyncResponse ar) {
- completeModulesGET(server.modulesYangGET(mountPath, fileName, revision), ar);
+ completeModulesGET(server.modulesYangGET(emptyRequest, mountPath, fileName, revision), ar);
}
/**
@Path("/" + URLConstants.MODULES_SUBPATH + "/{fileName : [^/]+}")
public void modulesYinGET(@PathParam("fileName") final String fileName,
@QueryParam("revision") final String revision, @Suspended final AsyncResponse ar) {
- completeModulesGET(server.modulesYinGET(fileName, revision), ar);
+ completeModulesGET(server.modulesYinGET(emptyRequest, fileName, revision), ar);
}
/**
public void modulesYinGET(@Encoded @PathParam("mountPath") final ApiPath mountPath,
@PathParam("fileName") final String fileName, @QueryParam("revision") final String revision,
@Suspended final AsyncResponse ar) {
- completeModulesGET(server.modulesYinGET(mountPath, fileName, revision), ar);
+ completeModulesGET(server.modulesYinGET(emptyRequest, mountPath, fileName, revision), ar);
}
private static void completeModulesGET(final RestconfFuture<ModulesGetResult> future, final AsyncResponse ar) {
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;
+import org.opendaylight.restconf.api.FormatParameters;
import org.opendaylight.restconf.api.FormattableBody;
import org.opendaylight.restconf.api.MediaTypes;
@Provider
@Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
-public final class JsonFormattableBody extends FormattableBodyWriter {
+public final class JsonJaxRsFormattableBodyWriter extends JaxRsFormattableBodyWriter {
@Override
- void writeTo(final FormattableBody body, final OutputStream out) throws IOException {
- body.formatToJSON(out);
+ void writeTo(final FormattableBody body, final FormatParameters format, final OutputStream out) throws IOException {
+ body.formatToJSON(format, out);
}
}
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;
+import org.opendaylight.restconf.api.FormatParameters;
import org.opendaylight.restconf.api.FormattableBody;
import org.opendaylight.restconf.api.MediaTypes;
@Provider
@Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
-public final class XmlFormattableBody extends FormattableBodyWriter {
+public final class XmlJaxRsFormattableBodyWriter extends JaxRsFormattableBodyWriter {
@Override
- void writeTo(final FormattableBody body, final OutputStream out) throws IOException {
- body.formatToXML(out);
+ void writeTo(final FormattableBody body, final FormatParameters format, final OutputStream out) throws IOException {
+ body.formatToXML(format, out);
}
}
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.restconf.api.ApiPath;
+import org.opendaylight.restconf.api.QueryParameters;
import org.opendaylight.restconf.api.query.InsertParam;
import org.opendaylight.restconf.api.query.PointParam;
import org.opendaylight.restconf.server.api.DatabindContext;
-import org.opendaylight.restconf.server.api.QueryParams;
import org.opendaylight.restconf.server.spi.ApiPathNormalizer;
import org.opendaylight.yangtools.concepts.Immutable;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
* @throws NullPointerException if any argument is {@code null}
* @throws IllegalArgumentException if the parameters are invalid
*/
- public static @Nullable Insert of(final DatabindContext databind, final QueryParams params) {
+ public static @Nullable Insert of(final DatabindContext databind, final QueryParameters params) {
InsertParam insert = null;
PointParam point = null;
import org.opendaylight.aaa.web.servlet.ServletSupport;
import org.opendaylight.restconf.nb.jaxrs.JaxRsRestconf;
import org.opendaylight.restconf.nb.jaxrs.JaxRsWebHostMetadata;
-import org.opendaylight.restconf.nb.jaxrs.JsonFormattableBody;
-import org.opendaylight.restconf.nb.jaxrs.XmlFormattableBody;
+import org.opendaylight.restconf.nb.jaxrs.JsonJaxRsFormattableBodyWriter;
+import org.opendaylight.restconf.nb.jaxrs.XmlJaxRsFormattableBodyWriter;
import org.opendaylight.restconf.nb.rfc8040.jersey.providers.JsonNormalizedNodeBodyWriter;
import org.opendaylight.restconf.nb.rfc8040.jersey.providers.XmlNormalizedNodeBodyWriter;
import org.opendaylight.restconf.nb.rfc8040.jersey.providers.errors.RestconfDocumentedExceptionMapper;
@Override
public Set<Object> getSingletons() {
return Set.of(
- new JsonFormattableBody(), new XmlFormattableBody(),
+ new JsonJaxRsFormattableBodyWriter(), new XmlJaxRsFormattableBodyWriter(),
new RestconfDocumentedExceptionMapper(databindProvider),
- new JaxRsRestconf(server));
+ new JaxRsRestconf(server, servletFactory.prettyPrint()));
}
}).build())
.asyncSupported(true)
import static java.util.Objects.requireNonNull;
import java.util.Map;
+import org.opendaylight.restconf.api.query.PrettyPrintParam;
import org.opendaylight.restconf.nb.rfc8040.streams.DefaultPingExecutor;
import org.opendaylight.restconf.nb.rfc8040.streams.DefaultRestconfStreamServletFactory;
import org.opendaylight.restconf.nb.rfc8040.streams.StreamsConfiguration;
@AttributeDefinition(name = "{+restconf}", description = """
The value of RFC8040 {+restconf} URI template, pointing to the root resource. Must not end with '/'.""")
String restconf() default "rests";
+
+ @AttributeDefinition(
+ name = "default pretty-print",
+ description = "Control the default value of the '" + PrettyPrintParam.uriName + "' query parameter.")
+ boolean pretty$_$print() default false;
}
private static final Logger LOG = LoggerFactory.getLogger(OSGiNorthbound.class);
registry = registryFactory.newInstance(FrameworkUtil.asDictionary(MdsalRestconfStreamRegistry.props(useSSE)));
servletProps = DefaultRestconfStreamServletFactory.props(configuration.restconf(), registry.getInstance(),
- useSSE,
+ PrettyPrintParam.of(configuration.pretty$_$print()), useSSE,
new StreamsConfiguration(configuration.maximum$_$fragment$_$length(),
configuration.idle$_$timeout(), configuration.heartbeat$_$interval()),
configuration.ping$_$executor$_$name$_$prefix(), configuration.max$_$thread$_$count());
MdsalRestconfStreamRegistry.props(useSSE)));
LOG.debug("ListenersBroker restarted with {}", newUseSSE ? "SSE" : "Websockets");
}
-
final var newServletProps = DefaultRestconfStreamServletFactory.props(configuration.restconf(),
- registry.getInstance(), useSSE,
+ registry.getInstance(), PrettyPrintParam.of(configuration.pretty$_$print()), useSSE,
new StreamsConfiguration(configuration.maximum$_$fragment$_$length(),
configuration.idle$_$timeout(), configuration.heartbeat$_$interval()),
configuration.ping$_$executor$_$name$_$prefix(), configuration.max$_$thread$_$count());
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
-import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.restconf.api.FormatParameters;
import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
import org.opendaylight.restconf.nb.rfc8040.legacy.WriterParameters;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
public final void writeTo(final NormalizedNodePayload context, final Class<?> type, final Type genericType,
final Annotation[] annotations, final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders,
final OutputStream entityStream) throws IOException {
- writeData(context.inference().toSchemaInferenceStack(), context.writerParameters(), context.data(),
- requireNonNull(entityStream));
+ writeData(context.inference().toSchemaInferenceStack(), context.data(), context.writerParameters(),
+ context.format(), requireNonNull(entityStream));
}
- abstract void writeData(@NonNull SchemaInferenceStack stack, @NonNull WriterParameters writerParameters,
- @NonNull NormalizedNode data, @NonNull OutputStream entityStream) throws IOException;
+ @NonNullByDefault
+ abstract void writeData(SchemaInferenceStack stack, NormalizedNode data, WriterParameters writerParameters,
+ FormatParameters format, OutputStream out) throws IOException;
}
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;
import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.restconf.api.FormatParameters;
import org.opendaylight.restconf.api.MediaTypes;
import org.opendaylight.restconf.nb.rfc8040.jersey.providers.api.RestconfNormalizedNodeWriter;
import org.opendaylight.restconf.nb.rfc8040.legacy.WriterParameters;
@Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
public final class JsonNormalizedNodeBodyWriter extends AbstractNormalizedNodeBodyWriter {
@Override
- void writeData(final SchemaInferenceStack stack, final WriterParameters writerParameters, final NormalizedNode data,
- final OutputStream entityStream) throws IOException {
+ void writeData(final SchemaInferenceStack stack, final NormalizedNode data, final WriterParameters writerParameters,
+ final FormatParameters format, final OutputStream out) throws IOException {
if (!stack.isEmpty()) {
stack.exit();
}
.build()
: data;
- try (var jsonWriter = FormattableBodySupport.createJsonWriter(entityStream, writerParameters)) {
+ try (var jsonWriter = FormattableBodySupport.createJsonWriter(out, format)) {
jsonWriter.beginObject();
final var nnWriter = createNormalizedNodeWriter(stack.toInference(), jsonWriter, writerParameters, null);
import javax.xml.XMLConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
+import org.opendaylight.restconf.api.FormatParameters;
import org.opendaylight.restconf.api.MediaTypes;
import org.opendaylight.restconf.nb.rfc8040.jersey.providers.api.RestconfNormalizedNodeWriter;
import org.opendaylight.restconf.nb.rfc8040.legacy.WriterParameters;
@Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
public final class XmlNormalizedNodeBodyWriter extends AbstractNormalizedNodeBodyWriter {
@Override
- void writeData(final SchemaInferenceStack stack, final WriterParameters writerParameters, final NormalizedNode data,
- final OutputStream entityStream) throws IOException {
+ void writeData(final SchemaInferenceStack stack, final NormalizedNode data, final WriterParameters writerParameters,
+ final FormatParameters format, final OutputStream out) throws IOException {
final boolean isRoot;
if (!stack.isEmpty()) {
stack.exit();
isRoot = true;
}
- final var xmlWriter = FormattableBodySupport.createXmlWriter(entityStream, writerParameters);
+ final var xmlWriter = FormattableBodySupport.createXmlWriter(out, format);
final var nnWriter = createNormalizedNodeWriter(xmlWriter, stack.toInference(), writerParameters);
if (data instanceof MapEntryNode mapEntry) {
// Restconf allows returning one list item. We need to wrap it
import static java.util.Objects.requireNonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.restconf.api.FormatParameters;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
* messy details needed to deal with the payload.
*/
@NonNullByDefault
-public record NormalizedNodePayload(Inference inference, NormalizedNode data, WriterParameters writerParameters) {
+public record NormalizedNodePayload(
+ Inference inference,
+ NormalizedNode data,
+ WriterParameters writerParameters,
+ FormatParameters format) {
public NormalizedNodePayload {
requireNonNull(inference);
requireNonNull(data);
requireNonNull(writerParameters);
+ requireNonNull(format);
}
}
*/
package org.opendaylight.restconf.nb.rfc8040.legacy;
-import static java.util.Objects.requireNonNull;
-
import com.google.common.annotations.Beta;
import java.util.List;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.restconf.api.FormatParameters;
import org.opendaylight.restconf.api.query.DepthParam;
-import org.opendaylight.restconf.api.query.PrettyPrintParam;
import org.opendaylight.yangtools.yang.common.QName;
/**
* needs to be processed (for example filtered).
*/
@Beta
-public record WriterParameters(
- @NonNull PrettyPrintParam prettyPrint,
- @Nullable DepthParam depth,
- @Nullable List<Set<QName>> fields) implements FormatParameters {
- public static final @NonNull WriterParameters EMPTY = new WriterParameters(PrettyPrintParam.FALSE, null, null);
-
- public WriterParameters {
- requireNonNull(prettyPrint);
- }
+public record WriterParameters(@Nullable DepthParam depth, @Nullable List<Set<QName>> fields) {
+ public static final @NonNull WriterParameters EMPTY = new WriterParameters(null, null);
- public static @NonNull WriterParameters of(final @NonNull PrettyPrintParam prettyPrint,
- final @Nullable DepthParam depth) {
- return depth == null && !prettyPrint.value() ? EMPTY : new WriterParameters(prettyPrint, depth, null);
+ public static @NonNull WriterParameters of(final @Nullable DepthParam depth) {
+ return depth == null ? EMPTY : new WriterParameters(depth, null);
}
}
import org.opendaylight.restconf.server.api.DataGetResult;
import org.opendaylight.restconf.server.api.DatabindContext;
import org.opendaylight.restconf.server.api.DatabindPath.Data;
-import org.opendaylight.restconf.server.api.QueryParams;
+import org.opendaylight.restconf.server.api.ServerRequest;
import org.opendaylight.restconf.server.spi.HttpGetResource;
import org.opendaylight.restconf.server.spi.RpcImplementation;
import org.opendaylight.restconf.server.spi.YangLibraryVersionResource;
}
@NonNullByDefault
- public RestconfFuture<FormattableBody> yangLibraryVersionGET(final QueryParams params) {
- return yangLibraryVersion.httpGET(params);
+ public RestconfFuture<FormattableBody> yangLibraryVersionGET(final ServerRequest request) {
+ return yangLibraryVersion.httpGET(request);
}
@Override
}
@Override
- void delete(final SettableRestconfFuture<Empty> future, final YangInstanceIdentifier path) {
+ void delete(final SettableRestconfFuture<Empty> future, final ServerRequest request,
+ final YangInstanceIdentifier path) {
final var tx = dataBroker.newReadWriteTransaction();
tx.exists(CONFIGURATION, path).addCallback(new FutureCallback<>() {
@Override
}
@Override
- RestconfFuture<DataGetResult> dataGET(final Data path, final DataGetParams params) {
+ RestconfFuture<DataGetResult> dataGET(final ServerRequest request, final Data path, final DataGetParams params) {
final var inference = path.inference();
final var fields = params.fields();
- return completeDataGET(inference,
- fields == null ? WriterParameters.of(params.prettyPrint(), params.depth())
- : new WriterParameters(params.prettyPrint(), params.depth(),
+ return completeDataGET(request.format(), inference,
+ fields == null ? WriterParameters.of(params.depth())
+ : new WriterParameters(params.depth(),
translateFieldsParam(inference.modelContext(), path.schema(), fields)),
readData(params.content(), path.instance(), params.withDefaults()), null);
}
import org.opendaylight.restconf.server.api.DataGetResult;
import org.opendaylight.restconf.server.api.DatabindContext;
import org.opendaylight.restconf.server.api.DatabindPath.Data;
+import org.opendaylight.restconf.server.api.ServerRequest;
import org.opendaylight.yangtools.yang.common.Empty;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.opendaylight.yangtools.yang.common.ErrorType;
}
@Override
- void delete(final SettableRestconfFuture<Empty> future, final YangInstanceIdentifier path) {
+ void delete(final SettableRestconfFuture<Empty> future, final ServerRequest request,
+ final YangInstanceIdentifier path) {
final var tx = prepareWriteExecution();
tx.delete(path);
Futures.addCallback(tx.commit(), new FutureCallback<CommitInfo>() {
}
@Override
- RestconfFuture<DataGetResult> dataGET(final Data path, final DataGetParams params) {
+ RestconfFuture<DataGetResult> dataGET(final ServerRequest request, final Data path, final DataGetParams params) {
final var inference = path.inference();
final var fields = params.fields();
final List<YangInstanceIdentifier> fieldPaths;
node = readData(params.content(), path.instance(), params.withDefaults());
}
- return completeDataGET(inference, WriterParameters.of(params.prettyPrint(), params.depth()), node, null);
+ return completeDataGET(request.format(), inference, WriterParameters.of(params.depth()), node, null);
}
@Override
import org.opendaylight.mdsal.dom.spi.SimpleDOMActionResult;
import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
import org.opendaylight.restconf.api.ApiPath;
+import org.opendaylight.restconf.api.FormatParameters;
import org.opendaylight.restconf.api.FormattableBody;
import org.opendaylight.restconf.api.query.ContentParam;
import org.opendaylight.restconf.api.query.WithDefaultsParam;
import org.opendaylight.restconf.server.api.DataPostBody;
import org.opendaylight.restconf.server.api.DataPostResult;
import org.opendaylight.restconf.server.api.DataPutResult;
-import org.opendaylight.restconf.server.api.DataYangPatchParams;
import org.opendaylight.restconf.server.api.DataYangPatchResult;
import org.opendaylight.restconf.server.api.DatabindContext;
import org.opendaylight.restconf.server.api.DatabindPath;
import org.opendaylight.restconf.server.api.DatabindPath.InstanceReference;
import org.opendaylight.restconf.server.api.DatabindPath.OperationPath;
import org.opendaylight.restconf.server.api.DatabindPath.Rpc;
-import org.opendaylight.restconf.server.api.InvokeParams;
import org.opendaylight.restconf.server.api.InvokeResult;
import org.opendaylight.restconf.server.api.OperationInputBody;
import org.opendaylight.restconf.server.api.PatchBody;
import org.opendaylight.restconf.server.api.PatchStatusContext;
import org.opendaylight.restconf.server.api.PatchStatusEntity;
-import org.opendaylight.restconf.server.api.QueryParams;
import org.opendaylight.restconf.server.api.ResourceBody;
+import org.opendaylight.restconf.server.api.ServerRequest;
import org.opendaylight.restconf.server.spi.ApiPathCanonizer;
import org.opendaylight.restconf.server.spi.ApiPathNormalizer;
import org.opendaylight.restconf.server.spi.DefaultResourceContext;
}, MoreExecutors.directExecutor());
}
- public @NonNull RestconfFuture<DataPutResult> dataPUT(final ApiPath apiPath, final QueryParams params,
+ public @NonNull RestconfFuture<DataPutResult> dataPUT(final ServerRequest request, final ApiPath apiPath,
final ResourceBody body) {
final Data path;
try {
final Insert insert;
try {
- insert = Insert.of(databind, params);
+ insert = Insert.of(databind, request.queryParameters());
} catch (IllegalArgumentException e) {
return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(),
ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e));
return merge(path.instance(), data);
}
- public final @NonNull RestconfFuture<DataYangPatchResult> dataPATCH(final ApiPath apiPath, final QueryParams params,
- final PatchBody body) {
- final DataYangPatchParams patchParams;
- try {
- patchParams = DataYangPatchParams.of(params);
- } catch (IllegalArgumentException e) {
- return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(),
- ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e));
- }
-
+ public final @NonNull RestconfFuture<DataYangPatchResult> dataPATCH(final ApiPath apiPath, final PatchBody body) {
final Data path;
try {
path = pathNormalizer.normalizeDataPath(apiPath);
return RestconfFuture.failed(new RestconfDocumentedException("Error parsing input: " + e.getMessage(),
ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, e));
}
- return patchData(patchParams, patch);
+ return patchData(patch);
}
/**
* @return {@link PatchStatusContext}
*/
@VisibleForTesting
- public final @NonNull RestconfFuture<DataYangPatchResult> patchData(final DataYangPatchParams params,
- final PatchContext patch) {
+ public final @NonNull RestconfFuture<DataYangPatchResult> patchData(final PatchContext patch) {
final var editCollection = new ArrayList<PatchStatusEntity>();
final var tx = prepareWriteExecution();
// We have errors
if (!noError) {
tx.cancel();
- ret.set(new DataYangPatchResult(params,
+ ret.set(new DataYangPatchResult(
new PatchStatusContext(databind(), patch.patchId(), List.copyOf(editCollection), false, null)));
return ret;
}
Futures.addCallback(tx.commit(), new FutureCallback<CommitInfo>() {
@Override
public void onSuccess(final CommitInfo result) {
- ret.set(new DataYangPatchResult(params,
+ ret.set(new DataYangPatchResult(
new PatchStatusContext(databind(), patch.patchId(), List.copyOf(editCollection), true, null)));
}
@Override
public void onFailure(final Throwable cause) {
// if errors occurred during transaction commit then patch failed and global errors are reported
- ret.set(new DataYangPatchResult(params,
+ ret.set(new DataYangPatchResult(
new PatchStatusContext(databind(), patch.patchId(), List.copyOf(editCollection), false,
TransactionUtil.decodeException(cause, "PATCH", null, modelContext()).getErrors())));
}
* @return A {@link RestconfFuture}
* @throws NullPointerException if {@code apiPath} is {@code null}
*/
+ @NonNullByDefault
@SuppressWarnings("checkstyle:abbreviationAsWordInName")
- public final @NonNull RestconfFuture<Empty> dataDELETE(final ApiPath apiPath) {
+ public final RestconfFuture<Empty> dataDELETE(final ServerRequest request, final ApiPath apiPath) {
final Data path;
try {
path = pathNormalizer.normalizeDataPath(apiPath);
// FIXME: reject empty YangInstanceIdentifier, as datastores may not be deleted
final var ret = new SettableRestconfFuture<Empty>();
- delete(ret, path.instance());
+ delete(ret, request, path.instance());
return ret;
}
- abstract void delete(@NonNull SettableRestconfFuture<Empty> future, @NonNull YangInstanceIdentifier path);
+ @NonNullByDefault
+ abstract void delete(SettableRestconfFuture<Empty> future, ServerRequest request, YangInstanceIdentifier path);
- public final @NonNull RestconfFuture<DataGetResult> dataGET(final ApiPath apiPath, final QueryParams params) {
+ public final @NonNull RestconfFuture<DataGetResult> dataGET(final ServerRequest request, final ApiPath apiPath) {
final Data path;
try {
path = pathNormalizer.normalizeDataPath(apiPath);
final DataGetParams getParams;
try {
- getParams = DataGetParams.of(params);
+ getParams = DataGetParams.of(request.queryParameters());
} catch (IllegalArgumentException e) {
return RestconfFuture.failed(new RestconfDocumentedException(e,
new RestconfError(ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, "Invalid GET /data parameters", null,
e.getMessage())));
}
- return dataGET(path, getParams);
+ return dataGET(request, path, getParams);
}
- abstract @NonNull RestconfFuture<DataGetResult> dataGET(Data path, DataGetParams params);
+ abstract @NonNull RestconfFuture<DataGetResult> dataGET(ServerRequest request, Data path, DataGetParams params);
- static final @NonNull RestconfFuture<DataGetResult> completeDataGET(final Inference inference,
- final WriterParameters writerParams, final @Nullable NormalizedNode node,
+ static final @NonNull RestconfFuture<DataGetResult> completeDataGET(final FormatParameters format,
+ final Inference inference, final WriterParameters writerParams, final @Nullable NormalizedNode node,
final @Nullable ConfigurationMetadata metadata) {
if (node == null) {
return RestconfFuture.failed(new RestconfDocumentedException(
ErrorType.PROTOCOL, ErrorTag.DATA_MISSING));
}
- final var payload = new NormalizedNodePayload(inference, node, writerParams);
+ final var payload = new NormalizedNodePayload(inference, node, writerParams, format);
return RestconfFuture.of(metadata == null ? new DataGetResult(payload)
: new DataGetResult(payload, metadata.entityTag(), metadata.lastModified()));
}
}
@NonNullByDefault
- public RestconfFuture<FormattableBody> operationsGET(final QueryParams params) {
- return operations.httpGET(params);
+ public RestconfFuture<FormattableBody> operationsGET(final ServerRequest request) {
+ return operations.httpGET(request);
}
@NonNullByDefault
- public RestconfFuture<FormattableBody> operationsGET(final ApiPath apiPath, final QueryParams params) {
- return operations.httpGET(apiPath, params);
+ public RestconfFuture<FormattableBody> operationsGET(final ServerRequest request, final ApiPath apiPath) {
+ return operations.httpGET(request, apiPath);
}
- public @NonNull RestconfFuture<InvokeResult> operationsPOST(final URI restconfURI, final ApiPath apiPath,
- final QueryParams params, final OperationInputBody body) {
+ public @NonNull RestconfFuture<InvokeResult> operationsPOST(final ServerRequest request, final URI restconfURI,
+ final ApiPath apiPath, final OperationInputBody body) {
final Rpc path;
try {
path = pathNormalizer.normalizeRpcPath(apiPath);
return RestconfFuture.failed(e);
}
- final InvokeParams invokeParams;
- try {
- invokeParams = InvokeParams.of(params);
- } catch (IllegalArgumentException e) {
- return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(),
- ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e));
- }
-
final ContainerNode data;
try {
data = body.toContainerNode(path);
final var local = localRpcs.get(type);
if (local != null) {
return local.invoke(restconfURI, new OperationInput(path, data))
- .transform(output -> outputToInvokeResult(path, invokeParams, output));
+ .transform(output -> outputToInvokeResult(path, output));
}
if (rpcService == null) {
LOG.debug("RPC invocation is not available");
public void onSuccess(final DOMRpcResult response) {
final var errors = response.errors();
if (errors.isEmpty()) {
- ret.set(outputToInvokeResult(path, invokeParams, response.value()));
+ ret.set(outputToInvokeResult(path, response.value()));
} else {
LOG.debug("RPC invocation reported {}", response.errors());
ret.setFailure(new RestconfDocumentedException("RPC implementation reported errors", null,
}
private static @NonNull InvokeResult outputToInvokeResult(final @NonNull OperationPath path,
- final @NonNull InvokeParams params, final @Nullable ContainerNode value) {
+ final @Nullable ContainerNode value) {
return value == null || value.isEmpty() ? InvokeResult.EMPTY
- : new InvokeResult(new OperationOutputBody(params, path, value));
+ : new InvokeResult(new OperationOutputBody(path, value));
}
public @NonNull RestconfFuture<CharSource> resolveSource(final SourceIdentifier source,
ErrorType.APPLICATION, ErrorTag.DATA_MISSING));
}
- public final @NonNull RestconfFuture<? extends DataPostResult> dataPOST(final ApiPath apiPath,
- final QueryParams params, final DataPostBody body) {
+ public final @NonNull RestconfFuture<? extends DataPostResult> dataPOST(final ServerRequest request,
+ final ApiPath apiPath, final DataPostBody body) {
if (apiPath.isEmpty()) {
- return dataCreatePOST(params, body.toResource());
+ return dataCreatePOST(request, body.toResource());
}
final InstanceReference path;
try {
}
if (path instanceof Data dataPath) {
try (var resourceBody = body.toResource()) {
- return dataCreatePOST(dataPath, params, resourceBody);
+ return dataCreatePOST(request, dataPath, resourceBody);
}
}
if (path instanceof Action actionPath) {
try (var inputBody = body.toOperationInput()) {
- return dataInvokePOST(actionPath, params, inputBody);
+ return dataInvokePOST(actionPath, inputBody);
}
}
// Note: this should never happen
return RestconfFuture.failed(new RestconfDocumentedException("Unhandled path " + path));
}
- public @NonNull RestconfFuture<CreateResourceResult> dataCreatePOST(final QueryParams params,
+ public @NonNull RestconfFuture<CreateResourceResult> dataCreatePOST(final ServerRequest request,
final ChildBody body) {
- return dataCreatePOST(new DatabindPath.Data(databind), params, body);
+ return dataCreatePOST(request, new DatabindPath.Data(databind), body);
}
- private @NonNull RestconfFuture<CreateResourceResult> dataCreatePOST(final DatabindPath.Data path,
- final QueryParams params, final ChildBody body) {
+ private @NonNull RestconfFuture<CreateResourceResult> dataCreatePOST(final ServerRequest request,
+ final DatabindPath.Data path, final ChildBody body) {
final Insert insert;
try {
- insert = Insert.of(path.databind(), params);
+ insert = Insert.of(path.databind(), request.queryParameters());
} catch (IllegalArgumentException e) {
return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(),
ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e));
}
private @NonNull RestconfFuture<InvokeResult> dataInvokePOST(final @NonNull Action path,
- final @NonNull QueryParams params, final @NonNull OperationInputBody body) {
- final InvokeParams invokeParams;
- try {
- invokeParams = InvokeParams.of(params);
- } catch (IllegalArgumentException e) {
- return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(),
- ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e));
- }
-
+ final @NonNull OperationInputBody body) {
final ContainerNode input;
try {
input = body.toContainerNode(path);
}
return dataInvokePOST(actionService, path, input)
- .transform(result -> outputToInvokeResult(path, invokeParams, result.getOutput().orElse(null)));
+ .transform(result -> outputToInvokeResult(path, result.getOutput().orElse(null)));
}
/**
import javax.ws.rs.core.Application;
import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.aaa.web.servlet.ServletSupport;
+import org.opendaylight.restconf.api.query.PrettyPrintParam;
import org.opendaylight.restconf.server.spi.RestconfStream;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
private static final String PROP_USE_WEBSOCKETS = ".useWebsockets";
private static final String PROP_STREAMS_CONFIGURATION = ".streamsConfiguration";
private static final String PROP_RESTCONF = ".restconf";
+ private static final String PROP_PRETTY_PRINT = ".prettyPrint";
private final @NonNull String restconf;
+ private final @NonNull PrettyPrintParam prettyPrint;
private final RestconfStream.Registry streamRegistry;
private final ServletSupport servletSupport;
public DefaultRestconfStreamServletFactory(final ServletSupport servletSupport, final String restconf,
final RestconfStream.Registry streamRegistry, final StreamsConfiguration streamsConfiguration,
- final String namePrefix, final int corePoolSize, final boolean useWebsockets) {
+ final PrettyPrintParam prettyPrint, final String namePrefix, final int corePoolSize,
+ final boolean useWebsockets) {
this.servletSupport = requireNonNull(servletSupport);
this.restconf = requireNonNull(restconf);
if (restconf.endsWith("/")) {
}
this.streamRegistry = requireNonNull(streamRegistry);
this.streamsConfiguration = requireNonNull(streamsConfiguration);
+ this.prettyPrint = requireNonNull(prettyPrint);
pingExecutor = new DefaultPingExecutor(namePrefix, corePoolSize);
this.useWebsockets = useWebsockets;
if (useWebsockets) {
this(servletSupport, (String) props.get(PROP_RESTCONF),
(RestconfStream.Registry) props.get(PROP_STREAM_REGISTRY),
(StreamsConfiguration) props.get(PROP_STREAMS_CONFIGURATION),
+ (PrettyPrintParam) props.get(PROP_PRETTY_PRINT),
(String) props.get(PROP_NAME_PREFIX), (int) requireNonNull(props.get(PROP_CORE_POOL_SIZE)),
(boolean) requireNonNull(props.get(PROP_USE_WEBSOCKETS)));
}
}).build();
}
+ @Override
+ public PrettyPrintParam prettyPrint() {
+ return prettyPrint;
+ }
+
@Override
@Deactivate
public void close() {
}
public static Map<String, ?> props(final String restconf, final RestconfStream.Registry streamRegistry,
- final boolean useSSE, final StreamsConfiguration streamsConfiguration, final String namePrefix,
- final int corePoolSize) {
+ final PrettyPrintParam prettyPrint, final boolean useSSE, final StreamsConfiguration streamsConfiguration,
+ final String namePrefix, final int corePoolSize) {
return Map.of(
PROP_RESTCONF, restconf,
PROP_STREAM_REGISTRY, streamRegistry,
+ PROP_PRETTY_PRINT, prettyPrint,
PROP_USE_WEBSOCKETS, !useSSE,
PROP_STREAMS_CONFIGURATION, streamsConfiguration,
PROP_NAME_PREFIX, namePrefix,
import javax.servlet.http.HttpServlet;
import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.restconf.api.query.PrettyPrintParam;
import org.opendaylight.restconf.server.spi.RestconfStream;
/**
@NonNull String restconf();
@NonNull HttpServlet newStreamServlet();
+
+ @NonNull PrettyPrintParam prettyPrint();
}
import javax.ws.rs.sse.SseEventSink;
import javax.xml.xpath.XPathExpressionException;
import org.opendaylight.restconf.api.QueryParameters;
-import org.opendaylight.restconf.api.query.PrettyPrintParam;
import org.opendaylight.restconf.server.api.EventStreamGetParams;
-import org.opendaylight.restconf.server.api.QueryParams;
import org.opendaylight.restconf.server.spi.RestconfStream;
import org.opendaylight.restconf.server.spi.RestconfStream.EncodingName;
import org.slf4j.Logger;
final EventStreamGetParams getParams;
try {
- getParams = EventStreamGetParams.of(new QueryParams(
- // FIXME: figure this out
- QueryParameters.ofMultiValue(uriInfo.getQueryParameters()), PrettyPrintParam.FALSE));
+ getParams = EventStreamGetParams.of(QueryParameters.ofMultiValue(uriInfo.getQueryParameters()));
} catch (IllegalArgumentException e) {
throw new BadRequestException(e.getMessage(), e);
}
import static java.util.Objects.requireNonNull;
-import java.util.function.Function;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.restconf.api.FormatParameters;
+import org.opendaylight.restconf.api.QueryParameters;
import org.opendaylight.restconf.api.query.ContentParam;
import org.opendaylight.restconf.api.query.DepthParam;
import org.opendaylight.restconf.api.query.FieldsParam;
-import org.opendaylight.restconf.api.query.PrettyPrintParam;
import org.opendaylight.restconf.api.query.WithDefaultsParam;
/**
@NonNull ContentParam content,
@Nullable DepthParam depth,
@Nullable FieldsParam fields,
- @Nullable WithDefaultsParam withDefaults,
- @NonNull PrettyPrintParam prettyPrint) implements FormatParameters {
- public static final @NonNull DataGetParams EMPTY =
- new DataGetParams(ContentParam.ALL, null, null, null, PrettyPrintParam.FALSE);
+ @Nullable WithDefaultsParam withDefaults) {
+ public static final @NonNull DataGetParams EMPTY = new DataGetParams(ContentParam.ALL, null, null, null);
public DataGetParams {
requireNonNull(content);
- requireNonNull(prettyPrint);
}
/**
* @throws NullPointerException if {@code queryParameters} is {@code null}
* @throws IllegalArgumentException if the parameters are invalid
*/
- public static @NonNull DataGetParams of(final QueryParams params) {
+ public static @NonNull DataGetParams of(final QueryParameters params) {
ContentParam content = ContentParam.ALL;
DepthParam depth = null;
FieldsParam fields = null;
WithDefaultsParam withDefaults = null;
- PrettyPrintParam prettyPrint = params.prettyPrint();
for (var entry : params.asCollection()) {
final var name = entry.getKey();
switch (name) {
case ContentParam.uriName:
- content = parseParam(ContentParam::forUriValue, name, value);
+ content = ContentParam.forUriValue(value);
break;
case DepthParam.uriName:
depth = DepthParam.forUriValue(value);
break;
case FieldsParam.uriName:
- fields = parseParam(FieldsParam::forUriValue, name, value);
+ fields = FieldsParam.forUriValue(value);
break;
case WithDefaultsParam.uriName:
- withDefaults = parseParam(WithDefaultsParam::forUriValue, name, value);
- break;
- case PrettyPrintParam.uriName:
- prettyPrint = parseParam(PrettyPrintParam::forUriValue, name, value);
+ withDefaults = WithDefaultsParam.forUriValue(value);
break;
default:
throw new IllegalArgumentException("Unknown parameter in /data GET: " + name);
}
}
- return new DataGetParams(content, depth, fields, withDefaults, prettyPrint);
- }
-
- private static <T> @NonNull T parseParam(final Function<@NonNull String, @NonNull T> method, final String name,
- final @NonNull String value) {
- try {
- return method.apply(value);
- } catch (IllegalArgumentException e) {
- throw new IllegalArgumentException("Invalid " + name + " value: " + e.getMessage(), e);
- }
+ return new DataGetParams(content, depth, fields, withDefaults);
}
}
+++ /dev/null
-/*
- * Copyright (c) 2024 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.server.api;
-
-import static java.util.Objects.requireNonNull;
-
-import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.restconf.api.FormatParameters;
-import org.opendaylight.restconf.api.query.PrettyPrintParam;
-
-/**
- * Supported query parameters of {@code PATCH} HTTP operation when the request is to invoke a YANG Patch operation.
- * There is no such thing in RFC8073, but we support pretty-printing of the resulting {@code yang-patch-status}.
- */
-public record DataYangPatchParams(@NonNull PrettyPrintParam prettyPrint) implements FormatParameters {
- public static final @NonNull DataYangPatchParams COMPACT = new DataYangPatchParams(PrettyPrintParam.FALSE);
- public static final @NonNull DataYangPatchParams PRETTY = new DataYangPatchParams(PrettyPrintParam.TRUE);
-
- public DataYangPatchParams {
- requireNonNull(prettyPrint);
- }
-
- /**
- * Return {@link DataYangPatchParams} for specified query parameters.
- *
- * @param params Parameters and their values
- * @return A {@link DataYangPatchParams}
- * @throws NullPointerException if {@code queryParameters} is {@code null}
- * @throws IllegalArgumentException if the parameters are invalid
- */
- public static @NonNull DataYangPatchParams of(final QueryParams params) {
- return FormatParametersHelper.of(params, COMPACT, PRETTY);
- }
-}
* Result of a {@code PATCH} request as defined in
* <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
*
- * @param params A {@link DataYangPatchParams}
* @param status A {@link PatchStatusContext}
* @param entityTag response {@code ETag} header, or {@code null} if not applicable
* @param lastModified response {@code Last-Modified} header, or {@code null} if not applicable
*/
public record DataYangPatchResult(
- @NonNull DataYangPatchParams params,
@NonNull PatchStatusContext status,
@Nullable EntityTag entityTag,
@Nullable Instant lastModified) implements ConfigurationMetadata {
requireNonNull(status);
}
- public DataYangPatchResult(final @NonNull DataYangPatchParams params, final @NonNull PatchStatusContext status) {
- this(params, status, null, null);
+ public DataYangPatchResult(final @NonNull PatchStatusContext status) {
+ this(status, null, null);
}
}
* A {@link FormattableBody} which has an attached {@link DatabindContext}.
*/
@NonNullByDefault
-public abstract class DatabindFormattableBody extends FormattableBody implements DatabindAware {
+public abstract class DatabindFormattableBody extends FormattableBody {
private final DatabindContext databind;
- protected DatabindFormattableBody(final FormatParameters format, final DatabindContext databind) {
- super(format);
+ protected DatabindFormattableBody(final DatabindContext databind) {
this.databind = requireNonNull(databind);
}
@Override
- public final DatabindContext databind() {
- return databind;
+ public final void formatToJSON(final FormatParameters format, final OutputStream out) throws IOException {
+ formatToJSON(databind, format, out);
}
- @Override
- protected final void formatToJSON(final OutputStream out, final FormatParameters format) throws IOException {
- formatToJSON(out, format, databind());
- }
-
- protected abstract void formatToJSON(OutputStream out, FormatParameters format, DatabindContext databind)
+ protected abstract void formatToJSON(DatabindContext databind, FormatParameters format, OutputStream out)
throws IOException;
@Override
- protected final void formatToXML(final OutputStream out, final FormatParameters format) throws IOException {
- formatToXML(out, format, databind());
+ public final void formatToXML(final FormatParameters format, final OutputStream out) throws IOException {
+ formatToXML(databind, format, out);
}
- protected abstract void formatToXML(OutputStream out, FormatParameters format, DatabindContext databind)
+ protected abstract void formatToXML(DatabindContext databind, FormatParameters format, OutputStream out)
throws IOException;
}
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.restconf.api.FormatParameters;
import org.opendaylight.restconf.api.FormattableBody;
/**
public abstract class DatabindPathFormattableBody<P extends DatabindPath> extends FormattableBody {
private final @NonNull P path;
- protected DatabindPathFormattableBody(final FormatParameters format, final P path) {
- super(format);
+ protected DatabindPathFormattableBody(final P path) {
this.path = requireNonNull(path);
}
@Override
protected ToStringHelper addToStringAttributes(final ToStringHelper helper) {
- return super.addToStringAttributes(helper.add("path", path).add("body", bodyAttribute()));
+ return helper.add("path", path).add("body", bodyAttribute());
}
protected abstract @Nullable Object bodyAttribute();
import com.google.common.base.MoreObjects;
import java.util.function.Function;
import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.restconf.api.QueryParameters;
import org.opendaylight.restconf.api.query.ChangedLeafNodesOnlyParam;
import org.opendaylight.restconf.api.query.ChildNodesOnlyParam;
import org.opendaylight.restconf.api.query.FilterParam;
/**
* Return {@link EventStreamGetParams} for specified query parameters.
*
- * @param parameters Parameters and their values
+ * @param parames Parameters and their values
* @return A {@link EventStreamGetParams}
* @throws NullPointerException if {@code queryParameters} is {@code null}
* @throws IllegalArgumentException if the parameters are invalid
*/
- public static @NonNull EventStreamGetParams of(final QueryParams parameters) {
+ public static @NonNull EventStreamGetParams of(final QueryParameters parames) {
StartTimeParam startTime = null;
StopTimeParam stopTime = null;
FilterParam filter = null;
ChangedLeafNodesOnlyParam changedLeafNodesOnly = null;
ChildNodesOnlyParam childNodesOnly = null;
- for (var entry : parameters.asCollection()) {
+ for (var entry : parames.asCollection()) {
final var paramName = entry.getKey();
final var paramValue = entry.getValue();
+++ /dev/null
-/*
- * Copyright (c) 2024 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.server.api;
-
-import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.restconf.api.FormatParameters;
-import org.opendaylight.restconf.api.query.PrettyPrintParam;
-
-/**
- * Helper utilities for structures which contain plain {@link FormatParameters}.
- */
-final class FormatParametersHelper {
- private FormatParametersHelper() {
- // Hidden on purpose
- }
-
- static <T extends FormatParameters> @NonNull T of(final QueryParams params, final @NonNull T compact,
- final @NonNull T pretty) {
- var prettyPrint = params.prettyPrint();
- for (var entry : params.asCollection()) {
- final var paramName = entry.getKey();
-
- prettyPrint = switch (paramName) {
- case PrettyPrintParam.uriName -> EventStreamGetParams.mandatoryParam(PrettyPrintParam::forUriValue,
- paramName, entry.getValue());
- default -> throw new IllegalArgumentException("Invalid parameter: " + paramName);
- };
- }
- return prettyPrint.value() ? pretty : compact;
- }
-}
+++ /dev/null
-/*
- * 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.server.api;
-
-import static java.util.Objects.requireNonNull;
-
-import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.restconf.api.FormatParameters;
-import org.opendaylight.restconf.api.query.PrettyPrintParam;
-
-/**
- * Supported query parameters of {@code POST} HTTP operation when the request is to invoke a YANG operation, be it
- * {@code rpc} or {@code action}. There is no such thing in RFC8040, but we support pretty-printing of the resulting
- * {@code output} container.
- */
-public record InvokeParams(@NonNull PrettyPrintParam prettyPrint) implements FormatParameters {
- public static final @NonNull InvokeParams COMPACT = new InvokeParams(PrettyPrintParam.FALSE);
- public static final @NonNull InvokeParams PRETTY = new InvokeParams(PrettyPrintParam.TRUE);
-
- public InvokeParams {
- requireNonNull(prettyPrint);
- }
-
- /**
- * Return {@link InvokeParams} for specified query parameters.
- *
- * @param params Parameters and their values
- * @return A {@link InvokeParams}
- * @throws NullPointerException if {@code queryParameters} is {@code null}
- * @throws IllegalArgumentException if the parameters are invalid
- */
- public static @NonNull InvokeParams of(final QueryParams params) {
- return FormatParametersHelper.of(params, COMPACT, PRETTY);
- }
-}
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
- * Result of an {@link RestconfServer#modulesYangGET(String, String)} invocation.
+ * Result of an {@link RestconfServer#modulesYangGET(ServerRequest, String, String)} invocation.
*
* @param source A {@link CharSource} containing the body
*/
+++ /dev/null
-/*
- * Copyright (c) 2024 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.server.api;
-
-import static java.util.Objects.requireNonNull;
-
-import java.util.Collection;
-import java.util.Map.Entry;
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.restconf.api.QueryParameters;
-import org.opendaylight.restconf.api.query.PrettyPrintParam;
-
-/**
- * A {@link QueryParameters} implementation which is congnizant of a default {@link PrettyPrintParam}.
- */
-@NonNullByDefault
-public record QueryParams(QueryParameters delegate, PrettyPrintParam prettyPrint) implements QueryParameters {
- public QueryParams {
- requireNonNull(delegate);
- requireNonNull(prettyPrint);
- }
-
- @Override
- public boolean isEmpty() {
- return delegate.isEmpty();
- }
-
- @Override
- public Collection<? extends Entry<String, String>> asCollection() {
- return delegate.asCollection();
- }
-
- @Override
- public @Nullable String lookup(final String paramName) {
- final var ret = delegate.lookup(paramName);
- if (ret != null) {
- return ret;
- }
- return PrettyPrintParam.uriName.equals(paramName) ? prettyPrint.paramValue() : null;
- }
-}
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.restconf.api.ApiPath;
import org.opendaylight.restconf.api.FormattableBody;
-import org.opendaylight.restconf.api.QueryParameters;
import org.opendaylight.restconf.common.errors.RestconfFuture;
import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
import org.opendaylight.yangtools.yang.common.Empty;
* @return A {@link RestconfFuture} of the operation
*/
@SuppressWarnings("checkstyle:abbreviationAsWordInName")
- RestconfFuture<Empty> dataDELETE(ApiPath identifier);
+ RestconfFuture<Empty> dataDELETE(ServerRequest request, ApiPath identifier);
/**
* Return the content of the datastore.
*
- * @param params {@link DataGetParams} for this request
+ * @param request {@link ServerRequest} for this request
* @return A {@link RestconfFuture} of the {@link DataGetResult} content
*/
- RestconfFuture<DataGetResult> dataGET(QueryParameters params);
+ RestconfFuture<DataGetResult> dataGET(ServerRequest request);
/**
* Return the content of a data resource.
*
+ * @param request {@link ServerRequest} for this request
* @param identifier resource identifier
- * @param params {@link DataGetParams} for this request
* @return A {@link RestconfFuture} of the {@link DataGetResult} content
*/
- RestconfFuture<DataGetResult> dataGET(ApiPath identifier, QueryParameters params);
+ RestconfFuture<DataGetResult> dataGET(ServerRequest request, ApiPath identifier);
/**
* Partially modify the target data resource, as defined in
* <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
*
+ * @param request {@link ServerRequest} for this request
* @param body data node for put to config DS
* @return A {@link RestconfFuture} of the operation
*/
- RestconfFuture<DataPatchResult> dataPATCH(ResourceBody body);
+ RestconfFuture<DataPatchResult> dataPATCH(ServerRequest request, ResourceBody body);
/**
* Partially modify the target data resource, as defined in
* <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
*
+ * @param request {@link ServerRequest} for this request
* @param identifier resource identifier
* @param body data node for put to config DS
* @return A {@link RestconfFuture} of the operation
*/
- RestconfFuture<DataPatchResult> dataPATCH(ApiPath identifier, ResourceBody body);
+ RestconfFuture<DataPatchResult> dataPATCH(ServerRequest request, ApiPath identifier, ResourceBody body);
/**
* Ordered list of edits that are applied to the datastore by the server, as defined in
* <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
*
- * @param params query parameters
+ * @param request {@link ServerRequest} for this request
* @param body YANG Patch body
* @return A {@link RestconfFuture} of the {@link DataYangPatchResult} content
*/
- RestconfFuture<DataYangPatchResult> dataPATCH(QueryParameters params, PatchBody body);
+ RestconfFuture<DataYangPatchResult> dataPATCH(ServerRequest request, PatchBody body);
/**
* Ordered list of edits that are applied to the datastore by the server, as defined in
* <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
*
+ * @param request {@link ServerRequest} for this request
* @param identifier path to target
- * @param params query parameters
* @param body YANG Patch body
* @return A {@link RestconfFuture} of the {@link DataYangPatchResult} content
*/
- RestconfFuture<DataYangPatchResult> dataPATCH(ApiPath identifier, QueryParameters params, PatchBody body);
+ RestconfFuture<DataYangPatchResult> dataPATCH(ServerRequest request, ApiPath identifier, PatchBody body);
- RestconfFuture<CreateResourceResult> dataPOST(QueryParameters params, ChildBody body);
+ RestconfFuture<CreateResourceResult> dataPOST(ServerRequest request, ChildBody body);
/**
* Create or invoke a operation, as described in
* <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.4">RFC8040 section 4.4</a>.
*
+ * @param request {@link ServerRequest} for this request
* @param identifier path to target
- * @param params query parameters
* @param body body of the post request
*/
- RestconfFuture<? extends DataPostResult> dataPOST(ApiPath identifier, QueryParameters params, DataPostBody body);
+ RestconfFuture<? extends DataPostResult> dataPOST(ServerRequest request, ApiPath identifier, DataPostBody body);
/**
* Replace the data store, as described in
* <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.5">RFC8040 section 4.5</a>.
*
+ * @param request {@link ServerRequest} for this request
* @param body data node for put to config DS
- * @param params query parameters
* @return A {@link RestconfFuture} completing with {@link DataPutResult}
*/
- RestconfFuture<DataPutResult> dataPUT(QueryParameters params, ResourceBody body);
+ RestconfFuture<DataPutResult> dataPUT(ServerRequest request, ResourceBody body);
/**
* Create or replace a data store resource, as described in
* <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.5">RFC8040 section 4.5</a>.
*
+ * @param request {@link ServerRequest} for this request
* @param identifier resource identifier
- * @param params query parameters
* @param body data node for put to config DS
* @return A {@link RestconfFuture} completing with {@link DataPutResult}
*/
- RestconfFuture<DataPutResult> dataPUT(ApiPath identifier, QueryParameters params, ResourceBody body);
+ RestconfFuture<DataPutResult> dataPUT(ServerRequest request, ApiPath identifier, ResourceBody body);
/**
* Return the set of supported RPCs supported by
- * {@link #operationsPOST(URI, ApiPath, QueryParameters, OperationInputBody)},
+ * {@link #operationsPOST(ServerRequest, URI, ApiPath, OperationInputBody)},
* as expressed in the <a href="https://www.rfc-editor.org/rfc/rfc8040#page-84">ietf-restconf.yang</a>
* {@code container operations} statement.
*
+ * @param request {@link ServerRequest} for this request
* @return A {@link RestconfFuture} completing with an {@link FormattableBody}
*/
- RestconfFuture<FormattableBody> operationsGET();
+ RestconfFuture<FormattableBody> operationsGET(ServerRequest request);
/*
* Return the details about a particular operation supported by
* <a href="https://www.rfc-editor.org/rfc/rfc8040#page-84">ietf-restconfig.yang</a>
* {@code container operations} statement.
*
+ * @param request {@link ServerRequest} for this request
* @param operation An operation
* @return A {@link RestconfFuture} completing with an {@link FormattableBody}
*/
- RestconfFuture<FormattableBody> operationsGET(ApiPath operation);
+ RestconfFuture<FormattableBody> operationsGET(ServerRequest request, ApiPath operation);
/**
* Invoke an RPC operation, as defined in
* <a href="https://www.rfc-editor.org/rfc/rfc8040#section-3.6">RFC8040 Operation Resource</a>.
*
+ * @param request {@link ServerRequest} for this request
* @param restconfURI Base URI of the request
* @param operation {@code <operation>} path, really an {@link ApiPath} to an {@code rpc}
- * @param params query parameters
* @param body RPC operation
* @return A {@link RestconfFuture} completing with {@link InvokeResult}
*/
// FIXME: 'operation' should really be an ApiIdentifier with non-null module, but we also support yang-ext:mount,
// and hence it is a path right now
- RestconfFuture<InvokeResult> operationsPOST(URI restconfURI, ApiPath operation, QueryParameters params,
+ RestconfFuture<InvokeResult> operationsPOST(ServerRequest request, URI restconfURI, ApiPath operation,
OperationInputBody body);
/**
* Return the revision of {@code ietf-yang-library} module implemented by this server, as defined in
* <a href="https://www.rfc-editor.org/rfc/rfc8040#section-3.3.3">RFC8040 {+restconf}/yang-library-version</a>.
*
+ * @param request {@link ServerRequest} for this request
* @return A {@link RestconfFuture} completing with {@link NormalizedNodePayload} containing a single
* {@code yang-library-version} leaf element.
*/
- // FIXME: this is a simple encoding-variadic return, similar to how OperationsContent is handled use a common
- // construct for both cases -- in this case it carries a yang.common.Revision
- RestconfFuture<FormattableBody> yangLibraryVersionGET();
+ RestconfFuture<FormattableBody> yangLibraryVersionGET(ServerRequest request);
- RestconfFuture<ModulesGetResult> modulesYangGET(String fileName, @Nullable String revision);
+ RestconfFuture<ModulesGetResult> modulesYangGET(ServerRequest request, String fileName, @Nullable String revision);
- RestconfFuture<ModulesGetResult> modulesYangGET(ApiPath mountPath, String fileName, @Nullable String revision);
+ RestconfFuture<ModulesGetResult> modulesYangGET(ServerRequest request, ApiPath mountPath, String fileName,
+ @Nullable String revision);
- RestconfFuture<ModulesGetResult> modulesYinGET(String fileName, @Nullable String revision);
+ RestconfFuture<ModulesGetResult> modulesYinGET(ServerRequest request, String fileName, @Nullable String revision);
- RestconfFuture<ModulesGetResult> modulesYinGET(ApiPath mountPath, String fileName, @Nullable String revision);
+ RestconfFuture<ModulesGetResult> modulesYinGET(ServerRequest request, ApiPath mountPath, String fileName,
+ @Nullable String revision);
}
--- /dev/null
+/*
+ * Copyright (c) 2024 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.server.api;
+
+import static java.util.Objects.requireNonNull;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.restconf.api.FormatParameters;
+import org.opendaylight.restconf.api.QueryParameters;
+import org.opendaylight.restconf.api.query.PrettyPrintParam;
+
+/**
+ * A request to {@link RestconfServer}. It contains state and binding established by whoever is performing binding to
+ * HTTP transport layer. This includes:
+ * <ul>
+ * <li>HTTP request {@link #queryParameters() query parameters},</li>
+ * <li>{@link #format() format parameters}, including those affected by query parameters<li>
+ * </ul>
+ * It notably does <b>not</b> hold the HTTP request path, nor the request body. Those are passed as separate arguments
+ * to server methods as implementations of those methods are expected to act on them.
+ */
+@NonNullByDefault
+public record ServerRequest(QueryParameters queryParameters, FormatParameters format) {
+ // TODO: this is where a binding to security principal and access control should be:
+ // - we would like to be able to have java.security.Principal#name() for logging purposes
+ // - we need to have a NACM-capable interface, through which we can check permissions (such as data PUT) and
+ // establish output filters (i.e. excluding paths inaccessible path to user from a data GET a ContainerNode)
+ public ServerRequest {
+ requireNonNull(queryParameters);
+ requireNonNull(format);
+ }
+
+ private ServerRequest(final QueryParameters queryParameters, final PrettyPrintParam prettyPrint) {
+ this(queryParameters, prettyPrint.value() ? FormatParameters.PRETTY : FormatParameters.COMPACT);
+ }
+
+ public static ServerRequest of(final QueryParameters queryParameters, final PrettyPrintParam defaultPrettyPrint) {
+ final var prettyPrint = queryParameters.lookup(PrettyPrintParam.uriName, PrettyPrintParam::forUriValue);
+ return prettyPrint == null ? new ServerRequest(queryParameters, defaultPrettyPrint)
+ : new ServerRequest(queryParameters.withoutParam(PrettyPrintParam.uriName), prettyPrint);
+ }
+}
import org.opendaylight.mdsal.dom.api.DOMRpcService;
import org.opendaylight.restconf.api.ApiPath;
import org.opendaylight.restconf.api.FormattableBody;
-import org.opendaylight.restconf.api.QueryParameters;
-import org.opendaylight.restconf.api.query.PrettyPrintParam;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfFuture;
import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy;
import org.opendaylight.restconf.server.api.ModulesGetResult;
import org.opendaylight.restconf.server.api.OperationInputBody;
import org.opendaylight.restconf.server.api.PatchBody;
-import org.opendaylight.restconf.server.api.QueryParams;
import org.opendaylight.restconf.server.api.ResourceBody;
import org.opendaylight.restconf.server.api.RestconfServer;
-import org.opendaylight.restconf.server.spi.RestconfServerConfiguration;
+import org.opendaylight.restconf.server.api.ServerRequest;
import org.opendaylight.restconf.server.spi.RpcImplementation;
import org.opendaylight.yangtools.yang.common.Empty;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferencePolicyOption;
-import org.osgi.service.metatype.annotations.Designate;
/**
* A RESTCONF server implemented on top of MD-SAL.
*/
@Singleton
-@Component(service = RestconfServer.class, configurationPid = "org.opendaylight.restconf.server")
-@Designate(ocd = RestconfServerConfiguration.class)
+@Component(service = RestconfServer.class)
public final class MdsalRestconfServer implements RestconfServer, AutoCloseable {
private static final VarHandle LOCAL_STRATEGY;
private final @NonNull DOMDataBroker dataBroker;
private final @Nullable DOMRpcService rpcService;
private final @Nullable DOMActionService actionService;
- private final @NonNull QueryParams emptyQueryParams;
@SuppressFBWarnings(value = "URF_UNREAD_FIELD", justification = "https://github.com/spotbugs/spotbugs/issues/2749")
private volatile MdsalRestconfStrategy localStrategy;
- public MdsalRestconfServer(final MdsalDatabindProvider databindProvider, final DOMDataBroker dataBroker,
- final DOMRpcService rpcService, final DOMActionService actionService,
- final DOMMountPointService mountPointService, final List<RpcImplementation> localRpcs,
- final PrettyPrintParam prettyPrint) {
+ @Inject
+ @Activate
+ public MdsalRestconfServer(@Reference final MdsalDatabindProvider databindProvider,
+ @Reference final DOMDataBroker dataBroker, @Reference final DOMRpcService rpcService,
+ @Reference final DOMActionService actionService,
+ @Reference final DOMMountPointService mountPointService,
+ @Reference(policyOption = ReferencePolicyOption.GREEDY) final List<RpcImplementation> localRpcs) {
this.databindProvider = requireNonNull(databindProvider);
this.dataBroker = requireNonNull(dataBroker);
this.rpcService = requireNonNull(rpcService);
this.actionService = requireNonNull(actionService);
this.mountPointService = requireNonNull(mountPointService);
- emptyQueryParams = new QueryParams(QueryParameters.of(), prettyPrint);
this.localRpcs = Maps.uniqueIndex(localRpcs, RpcImplementation::qname);
localStrategy = createLocalStrategy(databindProvider.currentDatabind());
}
- @Inject
- public MdsalRestconfServer(final MdsalDatabindProvider databindProvider, final DOMDataBroker dataBroker,
- final DOMRpcService rpcService, final DOMActionService actionService,
- final DOMMountPointService mountPointService, final List<RpcImplementation> localRpcs) {
- this(databindProvider, dataBroker, rpcService, actionService, mountPointService, localRpcs,
- PrettyPrintParam.FALSE);
- }
-
- @Activate
- public MdsalRestconfServer(@Reference final MdsalDatabindProvider databindProvider,
- @Reference final DOMDataBroker dataBroker, @Reference final DOMRpcService rpcService,
- @Reference final DOMActionService actionService,
- @Reference final DOMMountPointService mountPointService,
- @Reference(policyOption = ReferencePolicyOption.GREEDY) final List<RpcImplementation> localRpcs,
- // FIXME: dynamic at some point
- final RestconfServerConfiguration configuration) {
- this(databindProvider, dataBroker, rpcService, actionService, mountPointService, localRpcs,
- PrettyPrintParam.of(configuration.pretty$_$print()));
- }
-
- public MdsalRestconfServer(final MdsalDatabindProvider databindProvider, final DOMDataBroker dataBroker,
- final DOMRpcService rpcService, final DOMActionService actionService,
- final DOMMountPointService mountPointService, final PrettyPrintParam prettyPrint,
- final RpcImplementation... localRpcs) {
- this(databindProvider, dataBroker, rpcService, actionService, mountPointService, List.of(localRpcs),
- prettyPrint);
- }
-
public MdsalRestconfServer(final MdsalDatabindProvider databindProvider, final DOMDataBroker dataBroker,
final DOMRpcService rpcService, final DOMActionService actionService,
final DOMMountPointService mountPointService, final RpcImplementation... localRpcs) {
- this(databindProvider, dataBroker, rpcService, actionService, mountPointService, PrettyPrintParam.FALSE,
- localRpcs);
+ this(databindProvider, dataBroker, rpcService, actionService, mountPointService, List.of(localRpcs));
}
private @NonNull MdsalRestconfStrategy createLocalStrategy(final DatabindContext databind) {
localStrategy = null;
}
- private @NonNull QueryParams queryParams(final @NonNull QueryParameters params) {
- return params.isEmpty() ? emptyQueryParams : new QueryParams(params, emptyQueryParams.prettyPrint());
- }
-
@Override
- public RestconfFuture<Empty> dataDELETE(final ApiPath identifier) {
+ public RestconfFuture<Empty> dataDELETE(final ServerRequest request, final ApiPath identifier) {
final StrategyAndTail stratAndTail;
try {
stratAndTail = localStrategy().resolveStrategy(identifier);
} catch (RestconfDocumentedException e) {
return RestconfFuture.failed(e);
}
- return stratAndTail.strategy().dataDELETE(stratAndTail.tail());
+ return stratAndTail.strategy().dataDELETE(request, stratAndTail.tail());
}
@Override
- public RestconfFuture<DataGetResult> dataGET(final QueryParameters params) {
- return localStrategy().dataGET(ApiPath.empty(), queryParams(params));
+ public RestconfFuture<DataGetResult> dataGET(final ServerRequest request) {
+ return localStrategy().dataGET(request, ApiPath.empty());
}
@Override
- public RestconfFuture<DataGetResult> dataGET(final ApiPath identifier, final QueryParameters params) {
+ public RestconfFuture<DataGetResult> dataGET(final ServerRequest request, final ApiPath identifier) {
final StrategyAndTail stratAndTail;
try {
stratAndTail = localStrategy().resolveStrategy(identifier);
} catch (RestconfDocumentedException e) {
return RestconfFuture.failed(e);
}
- return stratAndTail.strategy().dataGET(stratAndTail.tail(), queryParams(params));
+ return stratAndTail.strategy().dataGET(request, stratAndTail.tail());
}
@Override
- public RestconfFuture<DataPatchResult> dataPATCH(final ResourceBody body) {
+ public RestconfFuture<DataPatchResult> dataPATCH(final ServerRequest request, final ResourceBody body) {
return localStrategy().dataPATCH(ApiPath.empty(), body);
}
@Override
- public RestconfFuture<DataPatchResult> dataPATCH(final ApiPath identifier, final ResourceBody body) {
+ public RestconfFuture<DataPatchResult> dataPATCH(final ServerRequest request, final ApiPath identifier,
+ final ResourceBody body) {
final StrategyAndTail strategyAndTail;
try {
strategyAndTail = localStrategy().resolveStrategy(identifier);
}
@Override
- public RestconfFuture<DataYangPatchResult> dataPATCH(final QueryParameters params, final PatchBody body) {
- return localStrategy().dataPATCH(ApiPath.empty(), queryParams(params), body);
+ public RestconfFuture<DataYangPatchResult> dataPATCH(final ServerRequest request, final PatchBody body) {
+ return localStrategy().dataPATCH(ApiPath.empty(), body);
}
@Override
- public RestconfFuture<DataYangPatchResult> dataPATCH(final ApiPath identifier, final QueryParameters params,
+ public RestconfFuture<DataYangPatchResult> dataPATCH(final ServerRequest request, final ApiPath identifier,
final PatchBody body) {
final StrategyAndTail strategyAndTail;
try {
} catch (RestconfDocumentedException e) {
return RestconfFuture.failed(e);
}
- return strategyAndTail.strategy().dataPATCH(strategyAndTail.tail(), queryParams(params), body);
+ return strategyAndTail.strategy().dataPATCH(strategyAndTail.tail(), body);
}
@Override
- public RestconfFuture<CreateResourceResult> dataPOST(final QueryParameters params, final ChildBody body) {
- return localStrategy().dataCreatePOST(queryParams(params), body);
+ public RestconfFuture<CreateResourceResult> dataPOST(final ServerRequest request, final ChildBody body) {
+ return localStrategy().dataCreatePOST(request, body);
}
@Override
- public RestconfFuture<? extends DataPostResult> dataPOST(final ApiPath identifier, final QueryParameters params,
+ public RestconfFuture<? extends DataPostResult> dataPOST(final ServerRequest request, final ApiPath identifier,
final DataPostBody body) {
final StrategyAndTail strategyAndTail;
try {
} catch (RestconfDocumentedException e) {
return RestconfFuture.failed(e);
}
- return strategyAndTail.strategy().dataPOST(strategyAndTail.tail(), queryParams(params), body);
+ return strategyAndTail.strategy().dataPOST(request, strategyAndTail.tail(), body);
}
@Override
- public RestconfFuture<DataPutResult> dataPUT(final QueryParameters params, final ResourceBody body) {
- return localStrategy().dataPUT(ApiPath.empty(), queryParams(params), body);
+ public RestconfFuture<DataPutResult> dataPUT(final ServerRequest request, final ResourceBody body) {
+ return localStrategy().dataPUT(request, ApiPath.empty(), body);
}
@Override
- public RestconfFuture<DataPutResult> dataPUT(final ApiPath identifier, final QueryParameters params,
+ public RestconfFuture<DataPutResult> dataPUT(final ServerRequest request, final ApiPath identifier,
final ResourceBody body) {
final StrategyAndTail strategyAndTail;
try {
} catch (RestconfDocumentedException e) {
return RestconfFuture.failed(e);
}
- return strategyAndTail.strategy().dataPUT(strategyAndTail.tail(), queryParams(params), body);
+ return strategyAndTail.strategy().dataPUT(request, strategyAndTail.tail(), body);
}
@Override
- public RestconfFuture<ModulesGetResult> modulesYangGET(final String fileName, final String revision) {
+ public RestconfFuture<ModulesGetResult> modulesYangGET(final ServerRequest request, final String fileName,
+ final String revision) {
return modulesGET(fileName, revision, YangTextSource.class);
}
@Override
- public RestconfFuture<ModulesGetResult> modulesYangGET(final ApiPath mountPath, final String fileName,
- final String revision) {
+ public RestconfFuture<ModulesGetResult> modulesYangGET(final ServerRequest request, final ApiPath mountPath,
+ final String fileName, final String revision) {
return modulesGET(mountPath, fileName, revision, YangTextSource.class);
}
@Override
- public RestconfFuture<ModulesGetResult> modulesYinGET(final String fileName, final String revision) {
+ public RestconfFuture<ModulesGetResult> modulesYinGET(final ServerRequest request, final String fileName,
+ final String revision) {
return modulesGET(fileName, revision, YinTextSource.class);
}
@Override
- public RestconfFuture<ModulesGetResult> modulesYinGET(final ApiPath mountPath, final String fileName,
- final String revision) {
+ public RestconfFuture<ModulesGetResult> modulesYinGET(final ServerRequest request, final ApiPath mountPath,
+ final String fileName, final String revision) {
return modulesGET(mountPath, fileName, revision, YinTextSource.class);
}
}
@Override
- public RestconfFuture<FormattableBody> operationsGET() {
- return localStrategy().operationsGET(emptyQueryParams);
+ public RestconfFuture<FormattableBody> operationsGET(final ServerRequest request) {
+ return localStrategy().operationsGET(request);
}
@Override
- public RestconfFuture<FormattableBody> operationsGET(final ApiPath operation) {
+ public RestconfFuture<FormattableBody> operationsGET(final ServerRequest request, final ApiPath operation) {
final StrategyAndTail strategyAndTail;
try {
strategyAndTail = localStrategy().resolveStrategy(operation);
final var strategy = strategyAndTail.strategy();
final var tail = strategyAndTail.tail();
- return tail.isEmpty() ? strategy.operationsGET(emptyQueryParams)
- : strategy.operationsGET(tail, emptyQueryParams);
+ return tail.isEmpty() ? strategy.operationsGET(request) : strategy.operationsGET(request, tail);
}
@Override
- public RestconfFuture<InvokeResult> operationsPOST(final URI restconfURI, final ApiPath apiPath,
- final QueryParameters params, final OperationInputBody body) {
+ public RestconfFuture<InvokeResult> operationsPOST(final ServerRequest request, final URI restconfURI,
+ final ApiPath apiPath, final OperationInputBody body) {
final StrategyAndTail strategyAndTail;
try {
strategyAndTail = localStrategy().resolveStrategy(apiPath);
return RestconfFuture.failed(e);
}
final var strategy = strategyAndTail.strategy();
- return strategy.operationsPOST(restconfURI, strategyAndTail.tail(), queryParams(params), body);
+ return strategy.operationsPOST(request, restconfURI, strategyAndTail.tail(), body);
}
@Override
- public RestconfFuture<FormattableBody> yangLibraryVersionGET() {
- return localStrategy().yangLibraryVersionGET(emptyQueryParams);
+ public RestconfFuture<FormattableBody> yangLibraryVersionGET(final ServerRequest request) {
+ return localStrategy().yangLibraryVersionGET(request);
}
}
import static java.util.Objects.requireNonNull;
+import com.google.common.base.MoreObjects.ToStringHelper;
import com.google.common.collect.ImmutableSetMultimap;
import java.io.IOException;
import java.io.Writer;
}
out.write("\n</operations>");
}
+
+ @Override
+ protected ToStringHelper addToStringAttributes(final ToStringHelper helper) {
+ return helper.add("rpcs", rpcs);
+ }
}
\ No newline at end of file
import org.opendaylight.restconf.api.FormattableBody;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfFuture;
-import org.opendaylight.restconf.server.api.QueryParams;
+import org.opendaylight.restconf.server.api.ServerRequest;
@NonNullByDefault
public record FailedHttpGetResource(RestconfDocumentedException cause) implements HttpGetResource {
}
@Override
- public RestconfFuture<FormattableBody> httpGET(final QueryParams params) {
+ public RestconfFuture<FormattableBody> httpGET(final ServerRequest request) {
return RestconfFuture.failed(cause);
}
@Override
- public RestconfFuture<FormattableBody> httpGET(final ApiPath apiPath, final QueryParams params) {
+ public RestconfFuture<FormattableBody> httpGET(final ServerRequest request, final ApiPath apiPath) {
throw new UnsupportedOperationException();
}
}
import org.opendaylight.restconf.api.ApiPath;
import org.opendaylight.restconf.api.FormattableBody;
import org.opendaylight.restconf.common.errors.RestconfFuture;
-import org.opendaylight.restconf.server.api.QueryParams;
+import org.opendaylight.restconf.server.api.ServerRequest;
/**
* A resource which supports HTTP GET and produces a {@link FormattableBody}.
@NonNullByDefault
public interface HttpGetResource {
- RestconfFuture<FormattableBody> httpGET(QueryParams params);
+ RestconfFuture<FormattableBody> httpGET(ServerRequest request);
- RestconfFuture<FormattableBody> httpGET(ApiPath apiPath, QueryParams params);
+ RestconfFuture<FormattableBody> httpGET(ServerRequest request, ApiPath apiPath);
}
private final Inference parent;
private final N data;
- public NormalizedFormattableBody(final FormatParameters format, final DatabindContext databind,
- final Inference parent, final N data) {
- super(format, databind);
+ public NormalizedFormattableBody(final DatabindContext databind, final Inference parent, final N data) {
+ super(databind);
this.parent = requireNonNull(parent);
this.data = requireNonNull(data);
// RESTCONF allows returning one list item. We need to wrap it in map node in order to serialize it properly,
}
@Override
- protected void formatToJSON(final OutputStream out, final FormatParameters format, final DatabindContext databind)
+ protected void formatToJSON(final DatabindContext databind, final FormatParameters format, final OutputStream out)
throws IOException {
writeTo(JSONNormalizedNodeStreamWriter.createExclusiveWriter(databind.jsonCodecs(), parent, null,
FormattableBodySupport.createJsonWriter(out, format)));
}
@Override
- protected void formatToXML(final OutputStream out, final FormatParameters format, final DatabindContext databind)
+ protected void formatToXML(final DatabindContext databind, final FormatParameters format, final OutputStream out)
throws IOException {
final var xmlWriter = FormattableBodySupport.createXmlWriter(out, format);
writeTo(XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, parent));
@Override
protected ToStringHelper addToStringAttributes(final ToStringHelper helper) {
- return super.addToStringAttributes(helper.add("body", data.prettyTree()));
+ return helper.add("body", data.prettyTree());
}
private void writeTo(final NormalizedNodeStreamWriter streamWriter) throws IOException {
@Override
protected ToStringHelper addToStringAttributes(final ToStringHelper helper) {
- return super.addToStringAttributes(helper.add("rpc", rpc));
+ return helper.add("rpc", rpc);
}
}
\ No newline at end of file
public final class OperationOutputBody extends DatabindPathFormattableBody<OperationPath> {
private final ContainerNode output;
- public OperationOutputBody(final FormatParameters format, final OperationPath path, final ContainerNode output) {
- super(format, path);
+ public OperationOutputBody(final OperationPath path, final ContainerNode output) {
+ super(path);
this.output = requireNonNull(output);
if (output.isEmpty()) {
throw new IllegalArgumentException("output may not be empty");
}
@Override
- protected void formatToJSON(final OutputStream out, final FormatParameters format)
- throws IOException {
+ public void formatToJSON(final FormatParameters format, final OutputStream out) throws IOException {
final var stack = prepareStack();
// RpcDefinition/ActionDefinition is not supported as initial codec in JSONStreamWriter, so we need to emit
}
@Override
- protected void formatToXML(final OutputStream out, final FormatParameters format) throws IOException {
+ public void formatToXML(final FormatParameters format, final OutputStream out) throws IOException {
final var stack = prepareStack();
// RpcDefinition/ActionDefinition is not supported as initial codec in XMLStreamWriter, so we need to emit
import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.restconf.api.FormatParameters;
import org.opendaylight.restconf.api.FormattableBody;
-import org.opendaylight.restconf.api.query.PrettyPrintParam;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.QNameModule;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
private final EffectiveModelContext modelContext;
OperationsBody(final EffectiveModelContext modelContext) {
- super(() -> PrettyPrintParam.TRUE);
this.modelContext = requireNonNull(modelContext);
}
@Override
- protected final void formatToJSON(final OutputStream out, final FormatParameters format) throws IOException {
+ public final void formatToJSON(final FormatParameters format, final OutputStream out) throws IOException {
try (var writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) {
formatToJSON(writer);
}
abstract void formatToJSON(@NonNull Writer out) throws IOException;
@Override
- protected final void formatToXML(final OutputStream out, final FormatParameters format) throws IOException {
+ public final void formatToXML(final FormatParameters format, final OutputStream out) throws IOException {
try (var writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) {
formatToXML(writer);
}
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfFuture;
import org.opendaylight.restconf.server.api.DatabindPath.Rpc;
-import org.opendaylight.restconf.server.api.QueryParams;
+import org.opendaylight.restconf.server.api.ServerRequest;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.QNameModule;
import org.opendaylight.yangtools.yang.common.Revision;
}
@Override
- public RestconfFuture<FormattableBody> httpGET(final QueryParams params) {
+ public RestconfFuture<FormattableBody> httpGET(final ServerRequest request) {
// RPC QNames by their XMLNamespace/Revision. This should be a Table, but Revision can be null, which wrecks us.
final var table = new HashMap<XMLNamespace, Map<Revision, ImmutableSet<QName>>>();
final var modelContext = pathNormalizer.databind().modelContext();
}
@Override
- public RestconfFuture<FormattableBody> httpGET(final ApiPath apiPath, final QueryParams params) {
+ public RestconfFuture<FormattableBody> httpGET(final ServerRequest request, final ApiPath apiPath) {
final Rpc path;
try {
path = pathNormalizer.normalizeRpcPath(apiPath);
+++ /dev/null
-/*
- * Copyright (c) 2024 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.server.spi;
-
-import org.opendaylight.restconf.api.query.PrettyPrintParam;
-import org.opendaylight.restconf.server.api.RestconfServer;
-import org.osgi.service.metatype.annotations.AttributeDefinition;
-import org.osgi.service.metatype.annotations.ObjectClassDefinition;
-
-/**
- * OSGi configuration of a typical {@link RestconfServer}.
- */
-@ObjectClassDefinition
-public @interface RestconfServerConfiguration {
- @AttributeDefinition(
- name = "default pretty-print",
- description = "Control the default value of the '" + PrettyPrintParam.uriName + "' query parameter.")
- boolean pretty$_$print() default false;
-}
\ No newline at end of file
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfFuture;
import org.opendaylight.restconf.server.api.DatabindContext;
-import org.opendaylight.restconf.server.api.QueryParams;
+import org.opendaylight.restconf.server.api.ServerRequest;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev170126.YangApi;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev170126.restconf.Restconf;
import org.opendaylight.yangtools.yang.common.QName;
}
@Override
- public RestconfFuture<FormattableBody> httpGET(final QueryParams params) {
- return RestconfFuture.of(new NormalizedFormattableBody<>(params::prettyPrint, databind, restconf, leaf));
+ public RestconfFuture<FormattableBody> httpGET(final ServerRequest request) {
+ return RestconfFuture.of(new NormalizedFormattableBody<>(databind, restconf, leaf));
}
@Override
- public RestconfFuture<FormattableBody> httpGET(final ApiPath apiPath, final QueryParams params) {
+ public RestconfFuture<FormattableBody> httpGET(final ServerRequest request, final ApiPath apiPath) {
throw new UnsupportedOperationException();
}
}
import static java.util.Objects.requireNonNull;
+import com.google.common.base.MoreObjects.ToStringHelper;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.io.OutputStream;
private final PatchStatusContext status;
- public YangPatchStatusBody(final FormatParameters format, final PatchStatusContext status) {
- super(format);
+ public YangPatchStatusBody(final PatchStatusContext status) {
this.status = requireNonNull(status);
}
@Override
- protected void formatToJSON(final OutputStream out, final FormatParameters format) throws IOException {
+ public void formatToJSON(final FormatParameters format, final OutputStream out) throws IOException {
try (var writer = FormattableBodySupport.createJsonWriter(out, format)) {
writer.beginObject().name("ietf-yang-patch:yang-patch-status")
.beginObject().name("patch-id").value(status.patchId());
}
@Override
- protected void formatToXML(final OutputStream out, final FormatParameters format) throws IOException {
+ public void formatToXML(final FormatParameters format, final OutputStream out) throws IOException {
final var writer = FormattableBodySupport.createXmlWriter(out, format);
try {
formatToXML(writer);
writer.writeEndElement();
}
+
+ @Override
+ protected ToStringHelper addToStringAttributes(final ToStringHelper helper) {
+ return helper.add("status", status);
+ }
}
import java.util.List;
import java.util.function.Consumer;
import javax.ws.rs.container.AsyncResponse;
-import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
-import javax.ws.rs.ext.MessageBodyWriter;
import org.eclipse.jdt.annotation.NonNull;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.opendaylight.mdsal.dom.api.DOMRpcService;
import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
import org.opendaylight.restconf.api.ApiPath;
+import org.opendaylight.restconf.api.FormatParameters;
import org.opendaylight.restconf.api.FormattableBody;
-import org.opendaylight.restconf.api.MediaTypes;
+import org.opendaylight.restconf.api.query.PrettyPrintParam;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfError;
import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest;
-import org.opendaylight.restconf.nb.rfc8040.jersey.providers.JsonNormalizedNodeBodyWriter;
-import org.opendaylight.restconf.nb.rfc8040.jersey.providers.XmlNormalizedNodeBodyWriter;
import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
import org.opendaylight.restconf.server.mdsal.MdsalDatabindProvider;
import org.opendaylight.restconf.server.mdsal.MdsalRestconfServer;
@BeforeEach
final void setupRestconf() {
- restconf = new JaxRsRestconf(new MdsalRestconfServer(
- new MdsalDatabindProvider(new FixedDOMSchemaService(modelContext())), dataBroker, rpcService, actionService,
- mountPointService));
+ restconf = new JaxRsRestconf(
+ new MdsalRestconfServer(new MdsalDatabindProvider(new FixedDOMSchemaService(modelContext())),
+ dataBroker, rpcService, actionService, mountPointService),
+ PrettyPrintParam.FALSE);
}
EffectiveModelContext modelContext() {
return JUKEBOX_SCHEMA;
}
- static final void assertJson(final String expectedJson, final NormalizedNodePayload payload) {
- assertPayload(expectedJson, payload, new JsonNormalizedNodeBodyWriter(),
- MediaTypes.APPLICATION_YANG_DATA_JSON);
- }
-
static final void assertJson(final String expectedJson, final OperationOutputBody payload) {
final var baos = new ByteArrayOutputStream();
try {
- payload.formatToJSON(baos);
+ payload.formatToJSON(FormatParameters.COMPACT, baos);
} catch (IOException e) {
throw new AssertionError(e);
}
assertEquals(expectedJson, baos.toString(StandardCharsets.UTF_8));
}
- static final void assertXml(final String expectedXml, final NormalizedNodePayload payload) {
- assertPayload(expectedXml, payload, new XmlNormalizedNodeBodyWriter(), MediaTypes.APPLICATION_YANG_DATA_XML);
- }
-
static final void assertXml(final String expectedXml, final OperationOutputBody payload) {
final var baos = new ByteArrayOutputStream();
try {
- payload.formatToXML(baos);
+ payload.formatToXML(FormatParameters.COMPACT, baos);
} catch (IOException e) {
throw new AssertionError(e);
}
assertEquals(expectedXml, baos.toString(StandardCharsets.UTF_8));
}
- private static void assertPayload(final String expected, final NormalizedNodePayload payload,
- final MessageBodyWriter<NormalizedNodePayload> writer, final String mediaType) {
- final var baos = new ByteArrayOutputStream();
- try {
- writer.writeTo(payload, null, null, null, MediaType.valueOf(mediaType), null, baos);
- } catch (IOException e) {
- throw new AssertionError(e);
- }
- assertEquals(expected, baos.toString(StandardCharsets.UTF_8));
- }
-
- static final FormattableBody assertFormatableBody(final int status, final Consumer<AsyncResponse> invocation) {
- return assertEntity(FormattableBody.class, status, invocation);
+ static final FormattableBody assertFormattableBody(final int status, final Consumer<AsyncResponse> invocation) {
+ return assertEntity(JaxRsFormattableBody.class, status, invocation).body();
}
static final ContainerNode assertOperationOutput(final int status, final Consumer<AsyncResponse> invocation) {
package org.opendaylight.restconf.nb.jaxrs;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
import org.opendaylight.mdsal.dom.spi.SimpleDOMActionResult;
import org.opendaylight.restconf.api.ApiPath;
+import org.opendaylight.restconf.api.query.PrettyPrintParam;
import org.opendaylight.restconf.nb.rfc8040.AbstractInstanceIdentifierTest;
+import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest;
import org.opendaylight.restconf.server.mdsal.MdsalDatabindProvider;
import org.opendaylight.restconf.server.mdsal.MdsalRestconfServer;
-import org.opendaylight.restconf.server.spi.OperationOutputBody;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
.build())))
.when(actionService).invokeAction(eq(RESET_PATH), any(), any());
- final var restconf = new JaxRsRestconf(new MdsalRestconfServer(
- new MdsalDatabindProvider(new FixedDOMSchemaService(IID_SCHEMA)), dataBroker, rpcService, actionService,
- mountPointService));
+ final var restconf = new JaxRsRestconf(
+ new MdsalRestconfServer(new MdsalDatabindProvider(new FixedDOMSchemaService(IID_SCHEMA)),
+ dataBroker, rpcService, actionService, mountPointService),
+ PrettyPrintParam.FALSE);
doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
doReturn(true).when(asyncResponse).resume(captor.capture());
restconf.postDataJSON(ApiPath.parse("instance-identifier-module:cont/cont1/reset"),
.build())))
.when(actionService).invokeAction(eq(RESET_PATH), any(), any());
- final var restconf = new JaxRsRestconf(new MdsalRestconfServer(
- new MdsalDatabindProvider(new FixedDOMSchemaService(IID_SCHEMA)), dataBroker, rpcService, actionService,
- mountPointService));
+ final var restconf = new JaxRsRestconf(
+ new MdsalRestconfServer(new MdsalDatabindProvider(new FixedDOMSchemaService(IID_SCHEMA)),
+ dataBroker, rpcService, actionService, mountPointService),
+ PrettyPrintParam.FALSE);
doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
- doReturn(true).when(asyncResponse).resume(captor.capture());
- restconf.postDataJSON(ApiPath.parse("instance-identifier-module:cont/cont1/reset"),
- stringInputStream("""
- {
- "instance-identifier-module:input": {
- "delay": 600
- }
- }"""), uriInfo, asyncResponse);
- final var response = captor.getValue();
- assertEquals(200, response.getStatus());
- final var payload = assertInstanceOf(OperationOutputBody.class, response.getEntity());
- AbstractRestconfTest.assertJson("""
- {"instance-identifier-module:output":{"timestamp":"somevalue"}}""", payload);
- AbstractRestconfTest.assertXml("""
- <output xmlns="instance:identifier:module"><timestamp>somevalue</timestamp></output>""", payload);
+ final var apiPath = ApiPath.parse("instance-identifier-module:cont/cont1/reset");
+ final var body = AbstractRestconfTest.assertFormattableBody(200, ar -> {
+ restconf.postDataJSON(apiPath,
+ stringInputStream("""
+ {
+ "instance-identifier-module:input": {
+ "delay": 600
+ }
+ }"""), uriInfo, ar);
+ });
+
+ AbstractJukeboxTest.assertFormat("""
+ {"instance-identifier-module:output":{"timestamp":"somevalue"}}""", body::formatToJSON, false);
+ AbstractJukeboxTest.assertFormat("""
+ <output xmlns="instance:identifier:module"><timestamp>somevalue</timestamp></output>""", body::formatToXML,
+ false);
}
}
@Test
void testOperationsContent() {
- final var body = assertFormatableBody(200, ar -> restconf.operationsGET(ar));
+ final var body = assertFormattableBody(200, ar -> restconf.operationsGET(ar));
assertFormat("""
{
"foo:new" : [null],
"foo:new1" : [null]
}
- }""", body::formatToJSON);
+ }""", body::formatToJSON, true);
assertFormat("""
<operations xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf">
<new xmlns="foo"/>
<new1 xmlns="foo"/>
- </operations>""", body::formatToXML);
+ </operations>""", body::formatToXML, true);
}
@Test
void testOperationsContentByIdentifier() {
- final var body = assertFormatableBody(200, ar -> restconf.operationsGET(apiPath("foo:new1"), ar));
+ final var body = assertFormattableBody(200, ar -> restconf.operationsGET(apiPath("foo:new1"), ar));
assertFormat("""
- { "foo:new1" : [null] }""", body::formatToJSON);
+ { "foo:new1" : [null] }""", body::formatToJSON, false);
assertFormat("""
- <new1 xmlns="foo"/>""", body::formatToXML);
+ <new1 xmlns="foo"/>""", body::formatToXML, false);
}
}
null
]
}
- }""", body::formatToJSON);
+ }""", body::formatToJSON, true);
}
@Test
]
}
}
- }""", body::formatToJSON);
+ }""", body::formatToJSON, true);
}
@Test
<yang-patch-status xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
<patch-id>test patch id</patch-id>
<ok/>
- </yang-patch-status>""", body::formatToXML);
+ </yang-patch-status>""", body::formatToXML, true);
}
}
@Test
void testOperations() {
- final var body = assertFormatableBody(200, ar -> restconf.operationsGET(ar));
+ final var body = assertFormattableBody(200, ar -> restconf.operationsGET(ar));
- assertFormat(EXPECTED_JSON, body::formatToJSON);
- assertFormat(EXPECTED_XML, body::formatToXML);
+ assertFormat(EXPECTED_JSON, body::formatToJSON, true);
+ assertFormat(EXPECTED_XML, body::formatToXML, true);
}
private void mockMountPoint() {
void testMountPointOperations() {
mockMountPoint();
- final var body = assertFormatableBody(200, ar -> restconf.operationsGET(DEVICE_ID, ar));
- assertFormat(EXPECTED_JSON, body::formatToJSON);
- assertFormat(EXPECTED_XML, body::formatToXML);
+ final var body = assertFormattableBody(200, ar -> restconf.operationsGET(DEVICE_ID, ar));
+ assertFormat(EXPECTED_JSON, body::formatToJSON, true);
+ assertFormat(EXPECTED_XML, body::formatToXML, true);
}
@Test
void testMountPointSpecificOperationsJson() {
mockMountPoint();
- final var body = assertFormatableBody(200, ar -> restconf.operationsGET(DEVICE_RPC1_MODULE1_ID, ar));
+ final var body = assertFormattableBody(200, ar -> restconf.operationsGET(DEVICE_RPC1_MODULE1_ID, ar));
assertFormat("""
- { "module1:dummy-rpc1-module1" : [null] }""", body::formatToJSON);
+ { "module1:dummy-rpc1-module1" : [null] }""", body::formatToJSON, false);
assertFormat("""
- <dummy-rpc1-module1 xmlns="module:1"/>""", body::formatToXML);
+ <dummy-rpc1-module1 xmlns="module:1"/>""", body::formatToXML, false);
}
}
import org.opendaylight.mdsal.dom.broker.DOMMountPointServiceImpl;
import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
import org.opendaylight.restconf.api.ApiPath;
+import org.opendaylight.restconf.api.query.PrettyPrintParam;
import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags;
import org.opendaylight.restconf.server.mdsal.MdsalDatabindProvider;
import org.opendaylight.restconf.server.mdsal.MdsalRestconfServer;
.createMountPoint(YangInstanceIdentifier.of(QName.create("mount:point:2", "2016-01-01", "cont")))
.register();
- restconf = new JaxRsRestconf(new MdsalRestconfServer(
- new MdsalDatabindProvider(new FixedDOMSchemaService(SCHEMA_CONTEXT_WITH_MOUNT_POINTS)), dataBroker,
- rpcService, actionService, mountPointService));
+ restconf = new JaxRsRestconf(
+ new MdsalRestconfServer(new MdsalDatabindProvider(
+ new FixedDOMSchemaService(SCHEMA_CONTEXT_WITH_MOUNT_POINTS)), dataBroker, rpcService, actionService,
+ mountPointService),
+ PrettyPrintParam.FALSE);
}
/**
import org.opendaylight.mdsal.dom.api.DOMRpcService;
import org.opendaylight.mdsal.dom.api.DOMSchemaService.YangTextSourceExtension;
import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
+import org.opendaylight.restconf.api.query.PrettyPrintParam;
import org.opendaylight.restconf.server.mdsal.MdsalDatabindProvider;
import org.opendaylight.restconf.server.mdsal.MdsalRestconfServer;
import org.opendaylight.yangtools.yang.common.ErrorTag;
@Before
public void setup() throws Exception {
- restconf = new JaxRsRestconf(new MdsalRestconfServer(
- new MdsalDatabindProvider(new FixedDOMSchemaService(() -> MODEL_CONTEXT, sourceProvider)), dataBroker,
- rpcService, actionService, mountPointService));
+ restconf = new JaxRsRestconf(
+ new MdsalRestconfServer(new MdsalDatabindProvider(
+ new FixedDOMSchemaService(() -> MODEL_CONTEXT, sourceProvider)), dataBroker, rpcService, actionService,
+ mountPointService),
+ PrettyPrintParam.FALSE);
}
/**
@Test
void testLibraryVersion() {
- final var body = assertFormatableBody(200, ar -> restconf.yangLibraryVersionGET(ar));
+ final var body = assertFormattableBody(200, ar -> restconf.yangLibraryVersionGET(ar));
assertFormat("""
- {"ietf-restconf:yang-library-version":"2019-01-04"}""", body::formatToJSON);
+ {"ietf-restconf:yang-library-version":"2019-01-04"}""", body::formatToJSON, false);
assertFormat("""
<yang-library-version xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf">2019-01-04\
- </yang-library-version>""", body::formatToXML);
+ </yang-library-version>""", body::formatToXML, false);
}
}
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.restconf.api.FormatParameters;
+import org.opendaylight.restconf.api.query.PrettyPrintParam;
import org.opendaylight.restconf.server.api.DatabindContext;
import org.opendaylight.yangtools.yang.common.Decimal64;
import org.opendaylight.yangtools.yang.common.QName;
public abstract class AbstractJukeboxTest {
@FunctionalInterface
- protected interface FormatMethod {
+ public interface FormatMethod {
- void invoke(@NonNull OutputStream out) throws IOException;
+ void invoke(@NonNull FormatParameters format, @NonNull OutputStream out) throws IOException;
}
// container jukebox
return new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
}
- protected static void assertFormat(final String expected, final FormatMethod formatMethod) {
+ public static void assertFormat(final String expected, final FormatMethod formatMethod,
+ final boolean prettyPrint) {
final var baos = new ByteArrayOutputStream();
try {
- formatMethod.invoke(baos);
+ formatMethod.invoke(new FormatParameters(PrettyPrintParam.of(prettyPrint)), baos);
} catch (IOException e) {
throw new AssertionError(e);
}
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
+import org.opendaylight.restconf.api.FormatParameters;
import org.opendaylight.restconf.nb.rfc8040.AbstractInstanceIdentifierTest;
import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
import org.opendaylight.restconf.nb.rfc8040.legacy.WriterParameters;
final NormalizedNodePayload nodePayload = new NormalizedNodePayload(Inference.ofDataTreePath(schemaContext),
ImmutableNodes.newContainerBuilder().withNodeIdentifier(new NodeIdentifier(SchemaContext.NAME)).build(),
- WriterParameters.EMPTY);
+ WriterParameters.EMPTY, FormatParameters.COMPACT);
final ByteArrayOutputStream output = new ByteArrayOutputStream();
final XmlNormalizedNodeBodyWriter xmlWriter = new XmlNormalizedNodeBodyWriter();
.withNodeIdentifier(new NodeIdentifier(
QName.create("bar:module", "2016-09-29", "foo-bar-container")))
.build())
- .build(), WriterParameters.EMPTY);
+ .build(), WriterParameters.EMPTY, FormatParameters.COMPACT);
final ByteArrayOutputStream output = new ByteArrayOutputStream();
final XmlNormalizedNodeBodyWriter xmlWriter = new XmlNormalizedNodeBodyWriter();
import org.junit.Test;
import org.mockito.Mock;
import org.opendaylight.restconf.api.ApiPath;
+import org.opendaylight.restconf.api.QueryParameters;
import org.opendaylight.restconf.api.query.ContentParam;
+import org.opendaylight.restconf.api.query.PrettyPrintParam;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.patch.PatchContext;
import org.opendaylight.restconf.common.patch.PatchEntity;
import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest;
-import org.opendaylight.restconf.server.api.DataYangPatchParams;
import org.opendaylight.restconf.server.api.DatabindContext;
import org.opendaylight.restconf.server.api.PatchStatusContext;
import org.opendaylight.restconf.server.api.PatchStatusEntity;
+import org.opendaylight.restconf.server.api.ServerRequest;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.patch.rev170222.yang.patch.yang.patch.Edit.Operation;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.opendaylight.yangtools.yang.common.ErrorType;
.build();
private static final NodeIdentifier NODE_IDENTIFIER =
new NodeIdentifier(QName.create("ns", "2016-02-28", "container"));
+ private static final ServerRequest REQUEST = ServerRequest.of(QueryParameters.of(), PrettyPrintParam.TRUE);
@Mock
private EffectiveModelContext mockSchemaContext;
*/
@Test
public final void testDeleteData() throws Exception {
- final var future = testDeleteDataStrategy().dataDELETE(ApiPath.empty());
+ final var future = testDeleteDataStrategy().dataDELETE(REQUEST, ApiPath.empty());
assertNotNull(Futures.getDone(future));
}
*/
@Test
public final void testNegativeDeleteData() {
- final var future = testNegativeDeleteDataStrategy().dataDELETE(ApiPath.empty());
+ final var future = testNegativeDeleteDataStrategy().dataDELETE(REQUEST, ApiPath.empty());
final var ex = assertThrows(ExecutionException.class, () -> Futures.getDone(future)).getCause();
assertThat(ex, instanceOf(RestconfDocumentedException.class));
final var errors = ((RestconfDocumentedException) ex).getErrors();
@Test
public final void testDeleteNonexistentData() {
- final var status = deleteNonexistentDataTestStrategy().patchData(DataYangPatchParams.COMPACT,
+ final var status = deleteNonexistentDataTestStrategy().patchData(
new PatchContext("patchD", List.of(new PatchEntity("edit", Operation.Delete, CREATE_AND_DELETE_TARGET))))
.getOrThrow().status();
assertEquals("patchD", status.patchId());
}
private static void patch(final PatchContext patchContext, final RestconfStrategy strategy, final boolean failed) {
- final var patchStatusContext = strategy.patchData(DataYangPatchParams.COMPACT, patchContext).getOrThrow()
- .status();
+ final var patchStatusContext = strategy.patchData(patchContext).getOrThrow().status();
for (var entity : patchStatusContext.editCollection()) {
if (failed) {
assertTrue("Edit " + entity.getEditId() + " failed", entity.isOk());
import org.opendaylight.restconf.server.api.JsonResourceBody;
import org.opendaylight.restconf.server.api.PatchStatusContext;
import org.opendaylight.restconf.server.api.PatchStatusEntity;
-import org.opendaylight.restconf.server.api.QueryParams;
+import org.opendaylight.restconf.server.api.ServerRequest;
import org.opendaylight.yangtools.yang.common.ErrorSeverity;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.opendaylight.yangtools.yang.common.ErrorType;
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
.delete(LogicalDatastoreType.CONFIGURATION, song2Path);
- jukeboxStrategy().delete(new SettableRestconfFuture<>(), songListPath);
+ jukeboxStrategy().delete(new SettableRestconfFuture<>(), null, songListPath);
verify(netconfService).getConfig(songListWildcardPath, songKeyFields);
verify(netconfService).delete(LogicalDatastoreType.CONFIGURATION, song1Path);
verify(netconfService).delete(LogicalDatastoreType.CONFIGURATION, song2Path);
doReturn(immediateFluentFuture(Optional.of(PLAYLIST_WITH_SONGS))).when(spyTx).read(songListPath);
// Inserting new song at 3rd position (aka as last element)
- spyStrategy.dataPUT(ApiPath.parse("example-jukebox:jukebox/playlist=0/song=3"),
- new QueryParams(QueryParameters.of(
- // insert new item after last existing item in list
- InsertParam.AFTER, PointParam.forUriValue("example-jukebox:jukebox/playlist=0/song=2")),
- PrettyPrintParam.TRUE),
+ spyStrategy.dataPUT(ServerRequest.of(QueryParameters.of(
+ // insert new item after last existing item in list
+ InsertParam.AFTER, PointParam.forUriValue("example-jukebox:jukebox/playlist=0/song=2")),
+ PrettyPrintParam.TRUE),
+ ApiPath.parse("example-jukebox:jukebox/playlist=0/song=3"),
new JsonResourceBody(stringInputStream("""
{
"example-jukebox:song" : [
doReturn(immediateFluentFuture(Optional.of(PLAYLIST_WITH_SONGS))).when(spyTx).read(songListPath);
// Inserting new song at 3rd position (aka as last element)
- spyStrategy.dataPOST(ApiPath.parse("example-jukebox:jukebox/playlist=0"),
+ spyStrategy.dataPOST(ServerRequest.of(QueryParameters.of(InsertParam.AFTER,
+ PointParam.forUriValue("example-jukebox:jukebox/playlist=0/song=2")), PrettyPrintParam.FALSE),
+ ApiPath.parse("example-jukebox:jukebox/playlist=0"),
// insert new item after last existing item in list
- new QueryParams(QueryParameters.of(InsertParam.AFTER,
- PointParam.forUriValue("example-jukebox:jukebox/playlist=0/song=2")), PrettyPrintParam.FALSE),
new JsonDataPostBody(stringInputStream("""
{
"example-jukebox:song" : [
import org.opendaylight.restconf.api.query.ContentParam;
import org.opendaylight.restconf.api.query.DepthParam;
import org.opendaylight.restconf.api.query.FieldsParam;
-import org.opendaylight.restconf.api.query.PrettyPrintParam;
import org.opendaylight.restconf.api.query.RestconfQueryParam;
import org.opendaylight.restconf.api.query.WithDefaultsParam;
import org.opendaylight.restconf.nb.rfc8040.Insert;
assertEquals(Set.of(containerChild), fields.get(0));
}
- private static void assertInvalidIAE(final Function<QueryParams, ?> paramsMethod,
+ private static void assertInvalidIAE(final Function<QueryParameters, ?> paramsMethod,
final RestconfQueryParam<?> param) {
assertParamsThrows("Invalid parameter: " + param.paramName(), paramsMethod, param.paramName(),
"odl-test-value");
}
- private static void assertInvalidIAE(final Function<QueryParams, ?> paramsMethod) {
+ private static void assertInvalidIAE(final Function<QueryParameters, ?> paramsMethod) {
assertParamsThrows("Invalid parameter: odl-unknown-param", paramsMethod, "odl-unknown-param", "odl-test-value");
}
private static void assertParamsThrows(final String expectedMessage,
- final Function<QueryParams, ?> paramsMethod, final String name, final String value) {
+ final Function<QueryParameters, ?> paramsMethod, final String name, final String value) {
assertParamsThrows(expectedMessage, paramsMethod, QueryParameters.of(name, value));
}
private static void assertParamsThrows(final String expectedMessage,
- final Function<QueryParams, ?> paramsMethod, final QueryParameters params) {
+ final Function<QueryParameters, ?> paramsMethod, final QueryParameters params) {
final var ex = assertThrows(IllegalArgumentException.class, () -> assertParams(paramsMethod, params));
assertEquals(expectedMessage, ex.getMessage());
}
- private static <T> T assertParams(final Function<QueryParams, T> paramsMethod, final QueryParameters params) {
- return paramsMethod.apply(new QueryParams(params, PrettyPrintParam.FALSE));
+ private static <T> T assertParams(final Function<QueryParameters, T> paramsMethod, final QueryParameters params) {
+ return paramsMethod.apply(params);
}
- private static <T> T assertParams(final Function<QueryParams, T> paramsMethod, final String name,
+ private static <T> T assertParams(final Function<QueryParameters, T> paramsMethod, final String name,
final String value) {
return assertParams(paramsMethod, QueryParameters.of(name, value));
}
import java.io.IOException;
import java.util.List;
import org.junit.Test;
-import org.opendaylight.restconf.api.query.PrettyPrintParam;
import org.opendaylight.restconf.common.errors.RestconfError;
import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest;
import org.opendaylight.restconf.server.api.DatabindContext;
*/
@Test
public void testOutputWithGlobalError() throws IOException {
- final var body = new YangPatchStatusBody(() -> PrettyPrintParam.TRUE,
- new PatchStatusContext(databind, "patch", List.of(statusEntity), false, List.of(error)));
+ final var body = new YangPatchStatusBody(new PatchStatusContext(databind, "patch", List.of(statusEntity), false,
+ List.of(error)));
assertFormat("""
{
]
}
}
- }""", body::formatToJSON);
+ }""", body::formatToJSON, true);
assertFormat("""
<yang-patch-status xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
<patch-id>patch</patch-id>
<error-tag>data-exists</error-tag>
<error-message>Data already exists</error-message>
</errors>
- </yang-patch-status>""", body::formatToXML);
+ </yang-patch-status>""", body::formatToXML, true);
}
/**
*/
@Test
public void testOutputWithoutGlobalError() throws IOException {
- final var body = new YangPatchStatusBody(() -> PrettyPrintParam.TRUE,
- new PatchStatusContext(databind,"patch", List.of(statusEntityError), false, null));
+ final var body = new YangPatchStatusBody(new PatchStatusContext(databind,"patch", List.of(statusEntityError),
+ false, null));
assertFormat("""
{
]
}
}
- }""", body::formatToJSON);
+ }""", body::formatToJSON, true);
assertFormat("""
<yang-patch-status xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
<patch-id>patch</patch-id>
</errors>
</edit>
</edit-status>
- </yang-patch-status>""", body::formatToXML);
+ </yang-patch-status>""", body::formatToXML, true);
}
}