15501eea7ba291f8f0f8978a10b7e19cb2ee38d1
[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.OAversion;
25 import org.opendaylight.netconf.sal.rest.doc.impl.DefinitionNames;
26 import org.opendaylight.netconf.sal.rest.doc.util.JsonUtil;
27 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
28 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
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.ListSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
33 import org.opendaylight.yangtools.yang.model.api.OutputSchemaNode;
34
35 public final class OperationBuilder {
36     public static final String BODY = "body";
37     public static final String CONFIG = "_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 PARAMETERS_KEY = "parameters";
47     public static final String POST_SUFFIX = "_post";
48     public static final String PROPERTIES_KEY = "properties";
49     public static final String REF_KEY = "$ref";
50     public static final String REQUEST_BODY_KEY = "requestBody";
51     public static final String RESPONSES_KEY = "responses";
52     public static final String SCHEMA_KEY = "schema";
53     public static final String SUMMARY_KEY = "summary";
54     public static final String SUMMARY_SEPARATOR = " - ";
55     public static final String TAGS_KEY = "tags";
56     public static final String TOP = "_TOP";
57     public static final String XML_KEY = "xml";
58     public static final String XML_SUFFIX = "_xml";
59     private static final ArrayNode CONSUMES_PUT_POST;
60     private static final String ENUM_KEY = "enum";
61     private static final List<String> MIME_TYPES = ImmutableList.of(MediaType.APPLICATION_XML,
62             MediaType.APPLICATION_JSON);
63     private static final String OBJECT = "object";
64     private static final String REQUIRED_KEY = "required";
65     private static final String TYPE_KEY = "type";
66
67     static {
68         CONSUMES_PUT_POST = JsonNodeFactory.instance.arrayNode();
69         for (final String mimeType : MIME_TYPES) {
70             CONSUMES_PUT_POST.add(mimeType);
71         }
72     }
73
74     private OperationBuilder() {
75
76     }
77
78     public static ObjectNode buildPost(final DataSchemaNode node, final String parentName, final String nodeName,
79                                        final String discriminator, final String moduleName,
80                                        final Optional<String> deviceName, final String description,
81                                        final ArrayNode pathParams, final OAversion oaversion) {
82         final ObjectNode value = JsonNodeFactory.instance.objectNode();
83         value.put(DESCRIPTION_KEY, description);
84         value.put(SUMMARY_KEY, buildSummaryValue(HttpMethod.POST, moduleName, deviceName, nodeName));
85         value.set(TAGS_KEY, buildTagsValue(deviceName, moduleName));
86         final ArrayNode parameters = JsonUtil.copy(pathParams);
87         final ObjectNode ref = JsonNodeFactory.instance.objectNode();
88         final String cleanDefName = parentName + CONFIG + "_" + nodeName + POST_SUFFIX;
89         final String defName = cleanDefName + discriminator;
90         final String xmlDefName = cleanDefName + XML_SUFFIX + discriminator;
91         ref.put(REF_KEY, getAppropriateModelPrefix(oaversion) + defName);
92         final DataSchemaNode childNode = getListOrContainerChildNode(Optional.ofNullable(node));
93         if (childNode != null && childNode.isConfiguration()) {
94             final String childNodeName = childNode.getQName().getLocalName();
95             final String cleanChildDefName = parentName + "_" + nodeName + CONFIG + "_" + childNodeName + POST_SUFFIX;
96             final String childDefName = cleanChildDefName + discriminator;
97             final String childXmlDefName = cleanChildDefName + XML_SUFFIX + discriminator;
98             insertPostRequestBodyParameter(childNode, parameters, value, childDefName, childXmlDefName, childNodeName,
99                 oaversion);
100         } else {
101             insertRequestBodyParameter(parameters, value, defName, xmlDefName, nodeName + CONFIG, oaversion);
102         }
103         value.set(PARAMETERS_KEY, parameters);
104
105         final ObjectNode responses = JsonNodeFactory.instance.objectNode();
106         responses.set(String.valueOf(Response.Status.CREATED.getStatusCode()),
107                 buildResponse(Response.Status.CREATED.getReasonPhrase(), Optional.empty(), oaversion));
108
109         value.set(RESPONSES_KEY, responses);
110         setConsumesIfNeeded(value, oaversion);
111         return value;
112     }
113
114     public static ObjectNode buildGet(final DataSchemaNode node, final String moduleName,
115                                       final Optional<String> deviceName, final ArrayNode pathParams,
116                                       final String defName, final String defNameTop, final boolean isConfig,
117                                       final OAversion oaversion) {
118         final ObjectNode value = JsonNodeFactory.instance.objectNode();
119         value.put(DESCRIPTION_KEY, node.getDescription().orElse(""));
120         value.put(SUMMARY_KEY, buildSummaryValue(HttpMethod.GET, moduleName, deviceName,
121                 node.getQName().getLocalName()));
122         value.set(TAGS_KEY, buildTagsValue(deviceName, moduleName));
123         final ArrayNode parameters = JsonUtil.copy(pathParams);
124         parameters.add(buildQueryParameters(isConfig, oaversion));
125
126         value.set(PARAMETERS_KEY, parameters);
127
128         final ObjectNode responses = JsonNodeFactory.instance.objectNode();
129         final ObjectNode schema = JsonNodeFactory.instance.objectNode();
130         final ObjectNode xmlSchema = JsonNodeFactory.instance.objectNode();
131         schema.put(REF_KEY, getAppropriateModelPrefix(oaversion) + defNameTop);
132         xmlSchema.put(REF_KEY, getAppropriateModelPrefix(oaversion) + defName);
133         responses.set(String.valueOf(Response.Status.OK.getStatusCode()),
134             buildResponse(Response.Status.OK.getReasonPhrase(), schema, xmlSchema, oaversion));
135
136         value.set(RESPONSES_KEY, responses);
137         return value;
138     }
139
140     private static ObjectNode buildQueryParameters(final boolean isConfig, final OAversion oaversion) {
141         final ObjectNode contentParam = JsonNodeFactory.instance.objectNode();
142         final ArrayNode cases = JsonNodeFactory.instance.arrayNode();
143         if (isConfig) {
144             cases.add("config");
145             cases.add("nonconfig");
146             cases.add("all");
147         } else {
148             cases.add("nonconfig");
149             contentParam.put(REQUIRED_KEY, true);
150         }
151         contentParam.put(IN_KEY, "query");
152         contentParam.put(NAME_KEY, "content");
153
154         final ObjectNode typeParent = getTypeParentNode(contentParam, oaversion);
155         typeParent.put(TYPE_KEY, "string");
156         typeParent.set(ENUM_KEY, cases);
157
158         return contentParam;
159     }
160
161     public static ObjectNode buildPut(final String parentName, final String nodeName, final String discriminator,
162                                       final String moduleName, final Optional<String> deviceName,
163                                       final String description, final ArrayNode pathParams,
164                                       final OAversion oaversion) {
165         final ObjectNode value = JsonNodeFactory.instance.objectNode();
166         value.put(DESCRIPTION_KEY, description);
167         value.put(SUMMARY_KEY, buildSummaryValue(HttpMethod.PUT, moduleName, deviceName, nodeName));
168         value.set(TAGS_KEY, buildTagsValue(deviceName, moduleName));
169         final ArrayNode parameters = JsonUtil.copy(pathParams);
170         final String defName = parentName + CONFIG + "_" + nodeName + TOP;
171         final String xmlDefName = parentName + CONFIG + "_" + nodeName;
172         insertRequestBodyParameter(parameters, value, defName, xmlDefName, nodeName + CONFIG, oaversion);
173         value.set(PARAMETERS_KEY, parameters);
174
175         final ObjectNode responses = JsonNodeFactory.instance.objectNode();
176         responses.set(String.valueOf(Response.Status.CREATED.getStatusCode()),
177                 buildResponse(Response.Status.CREATED.getReasonPhrase(), Optional.empty(), oaversion));
178         responses.set(String.valueOf(Response.Status.NO_CONTENT.getStatusCode()),
179                 buildResponse("Updated", Optional.empty(), oaversion));
180
181         value.set(RESPONSES_KEY, responses);
182         setConsumesIfNeeded(value, oaversion);
183         return value;
184     }
185
186     public static ObjectNode buildDelete(final DataSchemaNode node, final String moduleName,
187                                          final Optional<String> deviceName, final ArrayNode pathParams,
188                                          final OAversion oaversion) {
189         final ObjectNode value = JsonNodeFactory.instance.objectNode();
190         value.put(SUMMARY_KEY, buildSummaryValue(HttpMethod.DELETE, moduleName, deviceName,
191                 node.getQName().getLocalName()));
192         value.set(TAGS_KEY, buildTagsValue(deviceName, moduleName));
193         value.put(DESCRIPTION_KEY, node.getDescription().orElse(""));
194         final ArrayNode parameters = JsonUtil.copy(pathParams);
195         value.set(PARAMETERS_KEY, parameters);
196
197         final ObjectNode responses = JsonNodeFactory.instance.objectNode();
198         responses.set(String.valueOf(Response.Status.NO_CONTENT.getStatusCode()),
199                 buildResponse("Deleted", Optional.empty(), oaversion));
200
201         value.set(RESPONSES_KEY, responses);
202         return value;
203     }
204
205     public static ObjectNode buildPostOperation(final OperationDefinition operDef, final String moduleName,
206                                                 final Optional<String> deviceName, final String parentName,
207                                                 final DefinitionNames definitionNames, final OAversion oaversion,
208                                                 final ArrayNode parentPathParameters) {
209         final ObjectNode postOperation = JsonNodeFactory.instance.objectNode();
210         final ArrayNode parameters = JsonNodeFactory.instance.arrayNode().addAll(parentPathParameters);
211         final String operName = operDef.getQName().getLocalName();
212         final String inputName = operName + INPUT_SUFFIX;
213
214         final InputSchemaNode input = operDef.getInput();
215         final OutputSchemaNode output = operDef.getOutput();
216         if (!input.getChildNodes().isEmpty()) {
217             final String discriminator = definitionNames.getDiscriminator(input);
218             final String clearDefName = parentName + "_" + operName + INPUT_SUFFIX;
219             final String defName = clearDefName + discriminator;
220             final String defTopName = clearDefName + TOP + discriminator;
221             insertRequestBodyParameter(parameters, postOperation, defTopName, defName, inputName, oaversion);
222         } else {
223             final ObjectNode payload = JsonNodeFactory.instance.objectNode();
224             final ObjectNode jsonSchema = JsonNodeFactory.instance.objectNode();
225             final ObjectNode properties = JsonNodeFactory.instance.objectNode();
226             final ObjectNode inputSchema = JsonNodeFactory.instance.objectNode();
227             inputSchema.put(TYPE_KEY, OBJECT);
228             properties.set(INPUT_KEY, inputSchema);
229             jsonSchema.put(TYPE_KEY, OBJECT);
230             jsonSchema.set(PROPERTIES_KEY, properties);
231             if (oaversion.equals(OAversion.V3_0)) {
232                 final ObjectNode content = JsonNodeFactory.instance.objectNode();
233                 final ObjectNode jsonTypeValue = JsonNodeFactory.instance.objectNode();
234                 jsonTypeValue.set(SCHEMA_KEY, jsonSchema);
235                 content.set(MediaType.APPLICATION_JSON, jsonTypeValue);
236
237                 final ObjectNode xmlSchema = JsonNodeFactory.instance.objectNode();
238                 xmlSchema.put(TYPE_KEY, OBJECT);
239                 final ObjectNode xml = JsonNodeFactory.instance.objectNode();
240                 xml.put(NAME_KEY, INPUT);
241                 xmlSchema.set(XML_KEY, xml);
242                 final ObjectNode xmlTypeValue = JsonNodeFactory.instance.objectNode();
243                 xmlTypeValue.set(SCHEMA_KEY, xmlSchema);
244                 content.set(MediaType.APPLICATION_XML, xmlTypeValue);
245
246                 payload.set(CONTENT_KEY, content);
247                 payload.put(DESCRIPTION_KEY, inputName);
248                 postOperation.set(REQUEST_BODY_KEY, payload);
249             } else {
250                 payload.put(IN_KEY, BODY);
251                 payload.put(NAME_KEY, inputName);
252                 payload.set(SCHEMA_KEY, jsonSchema);
253                 parameters.add(payload);
254             }
255
256         }
257         setConsumesIfNeeded(postOperation, oaversion);
258         postOperation.set(PARAMETERS_KEY, parameters);
259         final ObjectNode responses = JsonNodeFactory.instance.objectNode();
260         final String description = String.format("RPC %s success", operName);
261
262         if (!output.getChildNodes().isEmpty()) {
263             final ObjectNode schema = JsonNodeFactory.instance.objectNode();
264             final String defName = parentName + "_" + operName + OUTPUT_SUFFIX + TOP
265                     + definitionNames.getDiscriminator(output);
266             schema.put(REF_KEY, getAppropriateModelPrefix(oaversion) + defName);
267             responses.set(String.valueOf(Response.Status.OK.getStatusCode()), buildResponse(description,
268                     Optional.of(schema), oaversion));
269         } else {
270             responses.set(String.valueOf(Response.Status.NO_CONTENT.getStatusCode()), buildResponse(description,
271                     Optional.empty(), oaversion));
272         }
273         postOperation.set(RESPONSES_KEY, responses);
274         postOperation.put(DESCRIPTION_KEY, operDef.getDescription().orElse(""));
275         postOperation.put(SUMMARY_KEY, buildSummaryValue(HttpMethod.POST, moduleName, deviceName, operName));
276         postOperation.set(TAGS_KEY, buildTagsValue(deviceName, moduleName));
277         return postOperation;
278     }
279
280     private static void insertRequestBodyParameter(final ArrayNode parameters, final ObjectNode operation,
281                                                    final String defName, final String xmlDefName,
282                                                    final String name, final OAversion oaversion) {
283         final ObjectNode payload = JsonNodeFactory.instance.objectNode();
284         if (oaversion.equals(OAversion.V3_0)) {
285             final ObjectNode content = JsonNodeFactory.instance.objectNode();
286             content.set(MediaType.APPLICATION_JSON, buildMimeTypeValue(defName));
287             content.set(MediaType.APPLICATION_XML, buildMimeTypeValue(xmlDefName));
288             payload.set(CONTENT_KEY, content);
289             payload.put(DESCRIPTION_KEY, name);
290             operation.set(REQUEST_BODY_KEY, payload);
291         } else {
292             payload.put(IN_KEY, BODY);
293             payload.put(NAME_KEY, name);
294             payload.set(SCHEMA_KEY, buildRefSchema(defName, OAversion.V2_0));
295             parameters.add(payload);
296         }
297     }
298
299     private static void insertPostRequestBodyParameter(final DataSchemaNode childNode, final ArrayNode parameters,
300             final ObjectNode operation, final String defName, final String xmlDefName, final String name,
301             final OAversion oaversion) {
302         final ObjectNode payload = JsonNodeFactory.instance.objectNode();
303         if (oaversion.equals(OAversion.V3_0)) {
304             final ObjectNode content = JsonNodeFactory.instance.objectNode();
305             final ObjectNode properties = JsonNodeFactory.instance.objectNode();
306             if (childNode instanceof ListSchemaNode) {
307                 final ObjectNode list = JsonNodeFactory.instance.objectNode();
308                 final ObjectNode listValue = JsonNodeFactory.instance.objectNode();
309                 listValue.put(TYPE_KEY, "array");
310                 listValue.set("items", buildRefSchema(defName, oaversion));
311                 list.set(name, listValue);
312                 properties.set(PROPERTIES_KEY, list);
313             } else {
314                 final ObjectNode container = JsonNodeFactory.instance.objectNode();
315                 container.set(name, buildRefSchema(defName, oaversion));
316                 properties.set(PROPERTIES_KEY, container);
317             }
318             final ObjectNode jsonSchema = JsonNodeFactory.instance.objectNode();
319             jsonSchema.set(SCHEMA_KEY, properties);
320             content.set(MediaType.APPLICATION_JSON, jsonSchema);
321             content.set(MediaType.APPLICATION_XML, buildMimeTypeValue(xmlDefName));
322             payload.set(CONTENT_KEY, content);
323             payload.put(DESCRIPTION_KEY, name);
324             operation.set(REQUEST_BODY_KEY, payload);
325         } else {
326             payload.put(IN_KEY, BODY);
327             payload.put(NAME_KEY, name);
328             payload.set(SCHEMA_KEY, buildRefSchema(defName, OAversion.V2_0));
329             parameters.add(payload);
330         }
331     }
332
333     private static ObjectNode buildRefSchema(final String defName, final OAversion oaversion) {
334         final ObjectNode schema = JsonNodeFactory.instance.objectNode();
335         schema.put(REF_KEY, getAppropriateModelPrefix(oaversion) + defName);
336         return schema;
337     }
338
339     private static ObjectNode buildMimeTypeValue(final String defName) {
340         final ObjectNode mimeTypeValue = JsonNodeFactory.instance.objectNode();
341         mimeTypeValue.set(SCHEMA_KEY, buildRefSchema(defName, OAversion.V3_0));
342         return mimeTypeValue;
343     }
344
345     public static ObjectNode buildResponse(final String description, final ObjectNode schema,
346             final ObjectNode xmlSchema, final OAversion oaversion) {
347         final ObjectNode response = JsonNodeFactory.instance.objectNode();
348
349         if (oaversion.equals(OAversion.V3_0)) {
350             final ObjectNode content = JsonNodeFactory.instance.objectNode();
351             final ObjectNode body = JsonNodeFactory.instance.objectNode();
352             final ObjectNode xmlBody = JsonNodeFactory.instance.objectNode();
353
354             body.set(SCHEMA_KEY, schema);
355             xmlBody.set(SCHEMA_KEY, xmlSchema);
356             content.set(MediaType.APPLICATION_JSON, body);
357             content.set(MediaType.APPLICATION_XML, xmlBody);
358
359             response.set(CONTENT_KEY, content);
360         } else {
361             response.set(SCHEMA_KEY, schema);
362         }
363         response.put(DESCRIPTION_KEY, description);
364         return response;
365     }
366
367     public static ObjectNode buildResponse(final String description, final Optional<ObjectNode> schema,
368                                            final OAversion oaversion) {
369         final ObjectNode response = JsonNodeFactory.instance.objectNode();
370
371         if (schema.isPresent()) {
372             final ObjectNode schemaValue = schema.get();
373             if (oaversion.equals(OAversion.V3_0)) {
374                 final ObjectNode content = JsonNodeFactory.instance.objectNode();
375                 final ObjectNode body = JsonNodeFactory.instance.objectNode();
376                 for (final String mimeType : MIME_TYPES) {
377                     content.set(mimeType, body);
378                 }
379                 body.set(SCHEMA_KEY, schemaValue);
380                 response.set(CONTENT_KEY, content);
381             } else {
382                 response.set(SCHEMA_KEY, schemaValue);
383             }
384         }
385         response.put(DESCRIPTION_KEY, description);
386         return response;
387     }
388
389     private static void setConsumesIfNeeded(final ObjectNode operation, final OAversion oaversion) {
390         if (oaversion.equals(OAversion.V2_0)) {
391             operation.set(CONSUMES_KEY, CONSUMES_PUT_POST);
392         }
393     }
394
395     private static String buildSummaryValue(final String httpMethod, final String moduleName,
396                                             final Optional<String> deviceName, final String nodeName) {
397         return httpMethod + SUMMARY_SEPARATOR + deviceName.map(s -> s + SUMMARY_SEPARATOR).orElse("")
398                 + moduleName + SUMMARY_SEPARATOR + nodeName;
399     }
400
401     public static ArrayNode buildTagsValue(final Optional<String> deviceName, final String moduleName) {
402         final ArrayNode tagsValue = JsonNodeFactory.instance.arrayNode();
403         tagsValue.add(deviceName.map(s -> "mounted " + s).orElse("controller") + " " + moduleName);
404         return tagsValue;
405     }
406
407     public static String getAppropriateModelPrefix(final OAversion oaversion) {
408         if (oaversion.equals(OAversion.V3_0)) {
409             return COMPONENTS_PREFIX;
410         }
411         return DEFINITIONS_PREFIX;
412     }
413
414     public static ObjectNode getTypeParentNode(final ObjectNode parameter, final OAversion oaversion) {
415         if (oaversion.equals(OAversion.V3_0)) {
416             final ObjectNode schema = JsonNodeFactory.instance.objectNode();
417             parameter.set(SCHEMA_KEY, schema);
418             return schema;
419         }
420         return parameter;
421     }
422
423     private static DataSchemaNode getListOrContainerChildNode(final Optional<DataSchemaNode> node) {
424         return node.flatMap(schemaNode -> ((DataNodeContainer) schemaNode).getChildNodes().stream()
425             .filter(n -> n instanceof ListSchemaNode || n instanceof ContainerSchemaNode)
426             .findFirst()).orElse(null);
427     }
428 }