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