d5ee84fd2f2a2230fd09c30809c0d746ace102eb
[netconf.git] / restconf / sal-rest-docgen / src / main / java / org / opendaylight / netconf / sal / rest / doc / model / builder / OperationBuilder.java
1 /*
2  * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
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
7  */
8
9 package org.opendaylight.netconf.sal.rest.doc.model.builder;
10
11 import static org.opendaylight.netconf.sal.rest.doc.impl.DefinitionGenerator.INPUT;
12 import static org.opendaylight.netconf.sal.rest.doc.impl.DefinitionGenerator.INPUT_SUFFIX;
13 import static org.opendaylight.netconf.sal.rest.doc.impl.DefinitionGenerator.OUTPUT_SUFFIX;
14
15 import com.fasterxml.jackson.databind.node.ArrayNode;
16 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
17 import com.fasterxml.jackson.databind.node.ObjectNode;
18 import com.google.common.collect.ImmutableList;
19 import java.util.List;
20 import java.util.Optional;
21 import javax.ws.rs.HttpMethod;
22 import javax.ws.rs.core.MediaType;
23 import javax.ws.rs.core.Response;
24 import org.opendaylight.netconf.sal.rest.doc.impl.ApiDocServiceImpl;
25 import org.opendaylight.netconf.sal.rest.doc.impl.ApiDocServiceImpl.OAversion;
26 import org.opendaylight.netconf.sal.rest.doc.impl.ApiDocServiceImpl.URIType;
27 import org.opendaylight.netconf.sal.rest.doc.impl.DefinitionNames;
28 import org.opendaylight.netconf.sal.rest.doc.util.JsonUtil;
29 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
30 import org.opendaylight.yangtools.yang.model.api.InputSchemaNode;
31 import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
32 import org.opendaylight.yangtools.yang.model.api.OutputSchemaNode;
33
34 public final class OperationBuilder {
35     public static final String BODY = "body";
36     public static final String CONFIG = "_config";
37     public static final String CONFIG_QUERY_PARAM = "config";
38     public static final String CONSUMES_KEY = "consumes";
39     public static final String CONTENT_KEY = "content";
40     public static final String COMPONENTS_PREFIX = "#/components/schemas/";
41     public static final String DEFINITIONS_PREFIX = "#/definitions/";
42     public static final String DESCRIPTION_KEY = "description";
43     public static final String IN_KEY = "in";
44     public static final String INPUT_KEY = "input";
45     public static final String NAME_KEY = "name";
46     public static final String NONCONFIG_QUERY_PARAM = "nonconfig";
47     public static final String PARAMETERS_KEY = "parameters";
48     public static final String POST_SUFFIX = "_post";
49     public static final String PROPERTIES_KEY = "properties";
50     public static final String REF_KEY = "$ref";
51     public static final String REQUEST_BODY_KEY = "requestBody";
52     public static final String RESPONSES_KEY = "responses";
53     public static final String SCHEMA_KEY = "schema";
54     public static final String SUMMARY_KEY = "summary";
55     public static final String SUMMARY_SEPARATOR = " - ";
56     public static final String TAGS_KEY = "tags";
57     public static final String TOP = "_TOP";
58     public static final String XML_KEY = "xml";
59     public static final String XML_SUFFIX = "_xml";
60     private static final String CONTENT = "content";
61     private static final ArrayNode CONSUMES_PUT_POST;
62     private static final String ENUM_KEY = "enum";
63     private static final List<String> MIME_TYPES = ImmutableList.of(MediaType.APPLICATION_XML,
64             MediaType.APPLICATION_JSON);
65     private static final String OBJECT = "object";
66     private static final String REQUIRED_KEY = "required";
67     private static final String STRING = "string";
68     private static final String TYPE_KEY = "type";
69     private static final String QUERY = "query";
70
71     static {
72         CONSUMES_PUT_POST = JsonNodeFactory.instance.arrayNode();
73         for (final String mimeType : MIME_TYPES) {
74             CONSUMES_PUT_POST.add(mimeType);
75         }
76     }
77
78     private OperationBuilder() {
79
80     }
81
82     public static ObjectNode buildPost(final String parentName, final String nodeName, final String discriminator,
83                                        final String moduleName, final Optional<String> deviceName,
84                                        final String description, final ArrayNode pathParams,
85                                        final OAversion oaversion) {
86         final ObjectNode value = JsonNodeFactory.instance.objectNode();
87         value.put(DESCRIPTION_KEY, description);
88         value.put(SUMMARY_KEY, buildSummaryValue(HttpMethod.POST, moduleName, deviceName, nodeName));
89         value.set(TAGS_KEY, buildTagsValue(deviceName, moduleName));
90         final ArrayNode parameters = JsonUtil.copy(pathParams);
91         final ObjectNode ref = JsonNodeFactory.instance.objectNode();
92         final String cleanDefName = parentName + CONFIG + "_" + nodeName + POST_SUFFIX;
93         final String defName = cleanDefName + discriminator;
94         final String xmlDefName = cleanDefName + XML_SUFFIX + discriminator;
95         ref.put(REF_KEY, getAppropriateModelPrefix(oaversion) + defName);
96         insertRequestBodyParameter(parameters, value, defName, xmlDefName, nodeName + CONFIG, oaversion);
97         value.set(PARAMETERS_KEY, parameters);
98
99         final ObjectNode responses = JsonNodeFactory.instance.objectNode();
100         responses.set(String.valueOf(Response.Status.CREATED.getStatusCode()),
101                 buildResponse(Response.Status.CREATED.getReasonPhrase(), Optional.empty(), oaversion));
102
103         value.set(RESPONSES_KEY, responses);
104         setConsumesIfNeeded(value, oaversion);
105         return value;
106     }
107
108     public static ObjectNode buildGet(final DataSchemaNode node, final String moduleName,
109                                       final Optional<String> deviceName, final ArrayNode pathParams,
110                                       final String defName, final boolean isConfig,
111                                       final URIType uriType, final OAversion oaversion) {
112         final ObjectNode value = JsonNodeFactory.instance.objectNode();
113         value.put(DESCRIPTION_KEY, node.getDescription().orElse(""));
114         value.put(SUMMARY_KEY, buildSummaryValue(HttpMethod.GET, moduleName, deviceName,
115                 node.getQName().getLocalName()));
116         value.set(TAGS_KEY, buildTagsValue(deviceName, moduleName));
117         final ArrayNode parameters = JsonUtil.copy(pathParams);
118
119         addQueryParameters(parameters, isConfig, uriType, oaversion);
120
121         value.set(PARAMETERS_KEY, parameters);
122
123         final ObjectNode responses = JsonNodeFactory.instance.objectNode();
124         final ObjectNode schema = JsonNodeFactory.instance.objectNode();
125         schema.put(REF_KEY, getAppropriateModelPrefix(oaversion) + defName);
126         responses.set(String.valueOf(Response.Status.OK.getStatusCode()),
127                 buildResponse(Response.Status.OK.getReasonPhrase(), Optional.of(schema), oaversion));
128
129         value.set(RESPONSES_KEY, responses);
130         return value;
131     }
132
133     private static void addQueryParameters(final ArrayNode parameters, final boolean isConfig,
134                                            final ApiDocServiceImpl.URIType uriType, final OAversion oaversion) {
135         if (uriType.equals(ApiDocServiceImpl.URIType.RFC8040)) {
136             final ObjectNode contentParam = JsonNodeFactory.instance.objectNode();
137             final ArrayNode cases = JsonNodeFactory.instance.arrayNode();
138             cases.add(NONCONFIG_QUERY_PARAM);
139             if (isConfig) {
140                 cases.add(CONFIG_QUERY_PARAM);
141             } else {
142                 contentParam.put(REQUIRED_KEY, true);
143             }
144             contentParam.put(IN_KEY, QUERY);
145             contentParam.put(NAME_KEY, CONTENT);
146
147             final ObjectNode typeParent = getTypeParentNode(contentParam, oaversion);
148             typeParent.put(TYPE_KEY, STRING);
149             typeParent.set(ENUM_KEY, cases);
150
151             parameters.add(contentParam);
152         }
153     }
154
155     public static ObjectNode buildPut(final String parentName, final String nodeName, final String discriminator,
156                                       final String moduleName, final Optional<String> deviceName,
157                                       final String description, final ArrayNode pathParams,
158                                       final OAversion oaversion) {
159         final ObjectNode value = JsonNodeFactory.instance.objectNode();
160         value.put(DESCRIPTION_KEY, description);
161         value.put(SUMMARY_KEY, buildSummaryValue(HttpMethod.PUT, moduleName, deviceName, nodeName));
162         value.set(TAGS_KEY, buildTagsValue(deviceName, moduleName));
163         final ArrayNode parameters = JsonUtil.copy(pathParams);
164         final String defName = parentName + CONFIG + "_" + nodeName + TOP;
165         final String xmlDefName = parentName + CONFIG + "_" + nodeName;
166         insertRequestBodyParameter(parameters, value, defName, xmlDefName, nodeName + CONFIG, oaversion);
167         value.set(PARAMETERS_KEY, parameters);
168
169         final ObjectNode responses = JsonNodeFactory.instance.objectNode();
170         responses.set(String.valueOf(Response.Status.CREATED.getStatusCode()),
171                 buildResponse(Response.Status.CREATED.getReasonPhrase(), Optional.empty(), oaversion));
172         responses.set(String.valueOf(Response.Status.NO_CONTENT.getStatusCode()),
173                 buildResponse("Updated", Optional.empty(), oaversion));
174
175         value.set(RESPONSES_KEY, responses);
176         setConsumesIfNeeded(value, oaversion);
177         return value;
178     }
179
180     public static ObjectNode buildDelete(final DataSchemaNode node, final String moduleName,
181                                          final Optional<String> deviceName, final ArrayNode pathParams,
182                                          final OAversion oaversion) {
183         final ObjectNode value = JsonNodeFactory.instance.objectNode();
184         value.put(SUMMARY_KEY, buildSummaryValue(HttpMethod.DELETE, moduleName, deviceName,
185                 node.getQName().getLocalName()));
186         value.set(TAGS_KEY, buildTagsValue(deviceName, moduleName));
187         value.put(DESCRIPTION_KEY, node.getDescription().orElse(""));
188         final ArrayNode parameters = JsonUtil.copy(pathParams);
189         value.set(PARAMETERS_KEY, parameters);
190
191         final ObjectNode responses = JsonNodeFactory.instance.objectNode();
192         responses.set(String.valueOf(Response.Status.NO_CONTENT.getStatusCode()),
193                 buildResponse("Deleted", Optional.empty(), oaversion));
194
195         value.set(RESPONSES_KEY, responses);
196         return value;
197     }
198
199     public static ObjectNode buildPostOperation(final OperationDefinition operDef, final String moduleName,
200                                                 final Optional<String> deviceName, final String parentName,
201                                                 final DefinitionNames definitionNames, final OAversion oaversion) {
202         final ObjectNode postOperation = JsonNodeFactory.instance.objectNode();
203         final ArrayNode parameters = JsonNodeFactory.instance.arrayNode();
204         final String operName = operDef.getQName().getLocalName();
205         final String inputName = operName + INPUT_SUFFIX;
206
207         final InputSchemaNode input = operDef.getInput();
208         final OutputSchemaNode output = operDef.getOutput();
209         if (!input.getChildNodes().isEmpty()) {
210             final String discriminator = definitionNames.getDiscriminator(input);
211             final String clearDefName = parentName + "_" + operName + INPUT_SUFFIX;
212             final String defName = clearDefName + discriminator;
213             final String defTopName = clearDefName + TOP + discriminator;
214             insertRequestBodyParameter(parameters, postOperation, defTopName, defName, inputName, oaversion);
215         } else {
216             final ObjectNode payload = JsonNodeFactory.instance.objectNode();
217             final ObjectNode jsonSchema = JsonNodeFactory.instance.objectNode();
218             final ObjectNode properties = JsonNodeFactory.instance.objectNode();
219             final ObjectNode inputSchema = JsonNodeFactory.instance.objectNode();
220             inputSchema.put(TYPE_KEY, OBJECT);
221             properties.set(INPUT_KEY, inputSchema);
222             jsonSchema.put(TYPE_KEY, OBJECT);
223             jsonSchema.set(PROPERTIES_KEY, properties);
224             if (oaversion.equals(OAversion.V3_0)) {
225                 final ObjectNode content = JsonNodeFactory.instance.objectNode();
226                 final ObjectNode jsonTypeValue = JsonNodeFactory.instance.objectNode();
227                 jsonTypeValue.set(SCHEMA_KEY, jsonSchema);
228                 content.set(MediaType.APPLICATION_JSON, jsonTypeValue);
229
230                 final ObjectNode xmlSchema = JsonNodeFactory.instance.objectNode();
231                 xmlSchema.put(TYPE_KEY, OBJECT);
232                 final ObjectNode xml = JsonNodeFactory.instance.objectNode();
233                 xml.put(NAME_KEY, INPUT);
234                 xmlSchema.set(XML_KEY, xml);
235                 final ObjectNode xmlTypeValue = JsonNodeFactory.instance.objectNode();
236                 xmlTypeValue.set(SCHEMA_KEY, xmlSchema);
237                 content.set(MediaType.APPLICATION_XML, xmlTypeValue);
238
239                 payload.set(CONTENT_KEY, content);
240                 payload.put(DESCRIPTION_KEY, inputName);
241                 postOperation.set(REQUEST_BODY_KEY, payload);
242             } else {
243                 payload.put(IN_KEY, BODY);
244                 payload.put(NAME_KEY, inputName);
245                 payload.set(SCHEMA_KEY, jsonSchema);
246                 parameters.add(payload);
247             }
248
249         }
250         setConsumesIfNeeded(postOperation, oaversion);
251         postOperation.set(PARAMETERS_KEY, parameters);
252         final ObjectNode responses = JsonNodeFactory.instance.objectNode();
253         final String description = String.format("RPC %s success", operName);
254
255         if (!output.getChildNodes().isEmpty()) {
256             final ObjectNode schema = JsonNodeFactory.instance.objectNode();
257             final String defName = parentName + "_" + operName + OUTPUT_SUFFIX + TOP
258                     + definitionNames.getDiscriminator(output);
259             schema.put(REF_KEY, getAppropriateModelPrefix(oaversion) + defName);
260             responses.set(String.valueOf(Response.Status.OK.getStatusCode()), buildResponse(description,
261                     Optional.of(schema), oaversion));
262         } else {
263             responses.set(String.valueOf(Response.Status.NO_CONTENT.getStatusCode()), buildResponse(description,
264                     Optional.empty(), oaversion));
265         }
266         postOperation.set(RESPONSES_KEY, responses);
267         postOperation.put(DESCRIPTION_KEY, operDef.getDescription().orElse(""));
268         postOperation.put(SUMMARY_KEY, buildSummaryValue(HttpMethod.POST, moduleName, deviceName, operName));
269         postOperation.set(TAGS_KEY, buildTagsValue(deviceName, moduleName));
270         return postOperation;
271     }
272
273     private static void insertRequestBodyParameter(final ArrayNode parameters, final ObjectNode operation,
274                                                    final String defName, final String xmlDefName,
275                                                    final String name, final OAversion oaversion) {
276         final ObjectNode payload = JsonNodeFactory.instance.objectNode();
277         if (oaversion.equals(OAversion.V3_0)) {
278             final ObjectNode content = JsonNodeFactory.instance.objectNode();
279             content.set(MediaType.APPLICATION_JSON, buildMimeTypeValue(defName));
280             content.set(MediaType.APPLICATION_XML, buildMimeTypeValue(xmlDefName));
281             payload.set(CONTENT_KEY, content);
282             payload.put(DESCRIPTION_KEY, name);
283             operation.set(REQUEST_BODY_KEY, payload);
284         } else {
285             payload.put(IN_KEY, BODY);
286             payload.put(NAME_KEY, name);
287             payload.set(SCHEMA_KEY, buildRefSchema(defName, OAversion.V2_0));
288             parameters.add(payload);
289         }
290     }
291
292     private static ObjectNode buildRefSchema(final String defName, final OAversion oaversion) {
293         final ObjectNode schema = JsonNodeFactory.instance.objectNode();
294         schema.put(REF_KEY, getAppropriateModelPrefix(oaversion) + defName);
295         return schema;
296     }
297
298     private static ObjectNode buildMimeTypeValue(final String defName) {
299         final ObjectNode mimeTypeValue = JsonNodeFactory.instance.objectNode();
300         mimeTypeValue.set(SCHEMA_KEY, buildRefSchema(defName, OAversion.V3_0));
301         return mimeTypeValue;
302     }
303
304     public static ObjectNode buildResponse(final String description, final Optional<ObjectNode> schema,
305                                            final OAversion oaversion) {
306         final ObjectNode response = JsonNodeFactory.instance.objectNode();
307
308         if (schema.isPresent()) {
309             final ObjectNode schemaValue = schema.get();
310             if (oaversion.equals(OAversion.V3_0)) {
311                 final ObjectNode content = JsonNodeFactory.instance.objectNode();
312                 final ObjectNode body = JsonNodeFactory.instance.objectNode();
313                 for (final String mimeType : MIME_TYPES) {
314                     content.set(mimeType, body);
315                 }
316                 body.set(SCHEMA_KEY, schemaValue);
317                 response.set(CONTENT_KEY, content);
318             } else {
319                 response.set(SCHEMA_KEY, schemaValue);
320             }
321         }
322         response.put(DESCRIPTION_KEY, description);
323         return response;
324     }
325
326     private static void setConsumesIfNeeded(final ObjectNode operation, final OAversion oaversion) {
327         if (oaversion.equals(OAversion.V2_0)) {
328             operation.set(CONSUMES_KEY, CONSUMES_PUT_POST);
329         }
330     }
331
332     private static String buildSummaryValue(final String httpMethod, final String moduleName,
333                                             final Optional<String> deviceName, final String nodeName) {
334         return httpMethod + SUMMARY_SEPARATOR + deviceName.map(s -> s + SUMMARY_SEPARATOR).orElse("")
335                 + moduleName + SUMMARY_SEPARATOR + nodeName;
336     }
337
338     public static ArrayNode buildTagsValue(final Optional<String> deviceName, final String moduleName) {
339         final ArrayNode tagsValue = JsonNodeFactory.instance.arrayNode();
340         tagsValue.add(deviceName.map(s -> "mounted " + s).orElse("controller") + " " + moduleName);
341         return tagsValue;
342     }
343
344     public static String getAppropriateModelPrefix(final OAversion oaversion) {
345         if (oaversion.equals(OAversion.V3_0)) {
346             return COMPONENTS_PREFIX;
347         }
348         return DEFINITIONS_PREFIX;
349     }
350
351     public static ObjectNode getTypeParentNode(final ObjectNode parameter, final OAversion oaversion) {
352         if (oaversion.equals(OAversion.V3_0)) {
353             final ObjectNode schema = JsonNodeFactory.instance.objectNode();
354             parameter.set(SCHEMA_KEY, schema);
355             return schema;
356         }
357         return parameter;
358     }
359 }