2 * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.restconf.nb.rfc8040.rests.services.impl;
10 import static java.util.Objects.requireNonNull;
12 import java.io.IOException;
13 import java.io.InputStream;
14 import java.util.Optional;
15 import javax.ws.rs.Consumes;
16 import javax.ws.rs.Encoded;
17 import javax.ws.rs.POST;
18 import javax.ws.rs.Path;
19 import javax.ws.rs.PathParam;
20 import javax.ws.rs.Produces;
21 import javax.ws.rs.container.AsyncResponse;
22 import javax.ws.rs.container.Suspended;
23 import javax.ws.rs.core.Context;
24 import javax.ws.rs.core.MediaType;
25 import javax.ws.rs.core.Response;
26 import javax.ws.rs.core.UriInfo;
27 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
28 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
29 import org.opendaylight.restconf.common.errors.RestconfFuture;
30 import org.opendaylight.restconf.nb.rfc8040.MediaTypes;
31 import org.opendaylight.restconf.nb.rfc8040.databind.DatabindContext;
32 import org.opendaylight.restconf.nb.rfc8040.databind.DatabindProvider;
33 import org.opendaylight.restconf.nb.rfc8040.databind.JsonOperationInputBody;
34 import org.opendaylight.restconf.nb.rfc8040.databind.OperationInputBody;
35 import org.opendaylight.restconf.nb.rfc8040.databind.XmlOperationInputBody;
36 import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
37 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
38 import org.opendaylight.restconf.nb.rfc8040.streams.ListenersBroker;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.device.notification.rev221106.SubscribeDeviceNotification;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.remote.rev140114.CreateDataChangeEventSubscription;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.remote.rev140114.CreateNotificationStream;
42 import org.opendaylight.yangtools.yang.common.ErrorTag;
43 import org.opendaylight.yangtools.yang.common.ErrorType;
44 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
49 * An operation resource represents a protocol operation defined with the YANG {@code rpc} statement. It is invoked
50 * using a POST method on the operation resource.
53 public final class RestconfInvokeOperationsServiceImpl {
54 private static final Logger LOG = LoggerFactory.getLogger(RestconfInvokeOperationsServiceImpl.class);
56 private final DatabindProvider databindProvider;
57 private final MdsalRestconfServer server;
58 @Deprecated(forRemoval = true)
59 private final DOMMountPointService mountPointService;
60 private final ListenersBroker listenersBroker;
62 public RestconfInvokeOperationsServiceImpl(final DatabindProvider databindProvider,
63 final MdsalRestconfServer server, final DOMMountPointService mountPointService,
64 final ListenersBroker listenersBroker) {
65 this.databindProvider = requireNonNull(databindProvider);
66 this.server = requireNonNull(server);
67 this.mountPointService = requireNonNull(mountPointService);
68 this.listenersBroker = requireNonNull(listenersBroker);
72 * Invoke RPC operation.
74 * @param identifier module name and rpc identifier string for the desired operation
75 * @param body the body of the operation
76 * @param uriInfo URI info
77 * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
80 // FIXME: identifier is just a *single* QName
81 @Path("/operations/{identifier:.+}")
83 MediaTypes.APPLICATION_YANG_DATA_XML,
84 MediaType.APPLICATION_XML,
88 MediaTypes.APPLICATION_YANG_DATA_JSON,
89 MediaTypes.APPLICATION_YANG_DATA_XML,
90 MediaType.APPLICATION_JSON,
91 MediaType.APPLICATION_XML,
94 public void invokeRpcXML(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
95 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
96 try (var xmlBody = new XmlOperationInputBody(body)) {
97 invokeRpc(identifier, uriInfo, ar, xmlBody);
102 * Invoke RPC operation.
104 * @param identifier module name and rpc identifier string for the desired operation
105 * @param body the body of the operation
106 * @param uriInfo URI info
107 * @param ar {@link AsyncResponse} which needs to be completed with a {@link NormalizedNodePayload} output
110 // FIXME: identifier is just a *single* QName
111 @Path("/operations/{identifier:.+}")
113 MediaTypes.APPLICATION_YANG_DATA_JSON,
114 MediaType.APPLICATION_JSON,
117 MediaTypes.APPLICATION_YANG_DATA_JSON,
118 MediaTypes.APPLICATION_YANG_DATA_XML,
119 MediaType.APPLICATION_JSON,
120 MediaType.APPLICATION_XML,
123 public void invokeRpcJSON(@Encoded @PathParam("identifier") final String identifier, final InputStream body,
124 @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
125 try (var jsonBody = new JsonOperationInputBody(body)) {
126 invokeRpc(identifier, uriInfo, ar, jsonBody);
130 private void invokeRpc(final String identifier, final UriInfo uriInfo, final AsyncResponse ar,
131 final OperationInputBody body) {
132 final var databind = databindProvider.currentContext();
133 final var reqPath = server.bindRequestPath(databind, identifier);
135 final ContainerNode input;
137 input = body.toContainerNode(reqPath.inference());
138 } catch (IOException e) {
139 LOG.debug("Error reading input", e);
140 throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL,
141 ErrorTag.MALFORMED_MESSAGE, e);
144 hackInvokeRpc(databind, reqPath, uriInfo, input).addCallback(new JaxRsRestconfCallback<>(ar) {
146 Response transform(final Optional<ContainerNode> result) {
148 .filter(output -> !output.isEmpty())
149 .map(output -> Response.ok().entity(new NormalizedNodePayload(reqPath.inference(), output)).build())
150 .orElseGet(() -> Response.noContent().build());
155 private RestconfFuture<Optional<ContainerNode>> hackInvokeRpc(final DatabindContext localDatabind,
156 final InstanceIdentifierContext reqPath, final UriInfo uriInfo, final ContainerNode input) {
158 final var type = reqPath.getSchemaNode().getQName();
159 final var mountPoint = reqPath.getMountPoint();
160 if (mountPoint == null) {
161 // Hacked-up integration of streams
162 if (CreateDataChangeEventSubscription.QNAME.equals(type)) {
163 return listenersBroker.createDataChangeNotifiStream(input, localDatabind.modelContext());
164 } else if (CreateNotificationStream.QNAME.equals(type)) {
165 return listenersBroker.createNotificationStream(input, localDatabind.modelContext());
166 } else if (SubscribeDeviceNotification.QNAME.equals(type)) {
167 return listenersBroker.createDeviceNotificationStream(input,
168 listenersBroker.prepareUriByStreamName(uriInfo, "").toString(), mountPointService);
172 return server.getRestconfStrategy(reqPath.getSchemaContext(), mountPoint).invokeRpc(type, input);