Remove _XML and _POST containers from OpenApi
[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.JsonNode;
16 import com.fasterxml.jackson.databind.node.ArrayNode;
17 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
18 import com.fasterxml.jackson.databind.node.ObjectNode;
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.restconf.openapi.impl.DefinitionNames;
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 PARAMETERS_KEY = "parameters";
41     public static final String PROPERTIES_KEY = "properties";
42     public static final String REF_KEY = "$ref";
43     public static final String REQUEST_BODY_KEY = "requestBody";
44     public static final String RESPONSES_KEY = "responses";
45     public static final String SCHEMA_KEY = "schema";
46     public static final String SUMMARY_KEY = "summary";
47     public static final String SUMMARY_SEPARATOR = " - ";
48     public static final String TAGS_KEY = "tags";
49     public static final String TOP = "_TOP";
50     public static final String XML_KEY = "xml";
51     private static final String CONTENT = "content";
52     private static final ArrayNode CONSUMES_PUT_POST;
53     private static final String ENUM_KEY = "enum";
54     private static final List<String> MIME_TYPES = List.of(MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON);
55     private static final String OBJECT = "object";
56     private static final String REQUIRED_KEY = "required";
57     private static final String STRING = "string";
58     private static final String TYPE_KEY = "type";
59     private static final String QUERY = "query";
60
61     static {
62         CONSUMES_PUT_POST = JsonNodeFactory.instance.arrayNode();
63         for (final String mimeType : MIME_TYPES) {
64             CONSUMES_PUT_POST.add(mimeType);
65         }
66     }
67
68     private OperationBuilder() {
69
70     }
71
72     public static ObjectNode buildPost(final String parentName, final String nodeName, final String discriminator,
73             final String moduleName, final Optional<String> deviceName, final String description,
74             final ArrayNode pathParams) {
75         final ObjectNode value = JsonNodeFactory.instance.objectNode();
76         value.put(DESCRIPTION_KEY, description);
77         value.put(SUMMARY_KEY, buildSummaryValue(HttpMethod.POST, moduleName, deviceName, nodeName));
78         value.set(TAGS_KEY, buildTagsValue(deviceName, moduleName));
79         final ArrayNode parameters = JsonNodeFactory.instance.arrayNode().addAll(pathParams);
80         final ObjectNode ref = JsonNodeFactory.instance.objectNode();
81         final String cleanDefName = parentName + CONFIG + "_" + nodeName;
82         final String defName = cleanDefName + discriminator;
83         final String xmlDefName = cleanDefName + discriminator;
84         ref.put(REF_KEY, COMPONENTS_PREFIX + defName);
85         insertRequestBodyParameter(value, defName, xmlDefName, nodeName + CONFIG);
86         value.set(PARAMETERS_KEY, parameters);
87
88         final ObjectNode responses = JsonNodeFactory.instance.objectNode();
89         responses.set(String.valueOf(Response.Status.CREATED.getStatusCode()),
90                 buildResponse(Response.Status.CREATED.getReasonPhrase(), Optional.empty()));
91
92         value.set(RESPONSES_KEY, responses);
93         return value;
94     }
95
96     public static ObjectNode buildGet(final DataSchemaNode node, final String moduleName,
97             final Optional<String> deviceName, final ArrayNode pathParams, final String defName,
98             final boolean isConfig) {
99         final ObjectNode value = JsonNodeFactory.instance.objectNode();
100         value.put(DESCRIPTION_KEY, node.getDescription().orElse(""));
101         value.put(SUMMARY_KEY, buildSummaryValue(HttpMethod.GET, moduleName, deviceName,
102                 node.getQName().getLocalName()));
103         value.set(TAGS_KEY, buildTagsValue(deviceName, moduleName));
104         final ArrayNode parameters = JsonNodeFactory.instance.arrayNode().addAll(pathParams);
105
106         addQueryParameters(parameters, isConfig);
107
108         value.set(PARAMETERS_KEY, parameters);
109
110         final ObjectNode responses = JsonNodeFactory.instance.objectNode();
111         final ObjectNode schema = JsonNodeFactory.instance.objectNode();
112         schema.put(REF_KEY, COMPONENTS_PREFIX + defName);
113         responses.set(String.valueOf(Response.Status.OK.getStatusCode()),
114                 buildResponse(Response.Status.OK.getReasonPhrase(), Optional.of(schema)));
115
116         value.set(RESPONSES_KEY, responses);
117         return value;
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 ObjectNode 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 ObjectNode value = JsonNodeFactory.instance.objectNode();
143         value.put(DESCRIPTION_KEY, description);
144         value.put(SUMMARY_KEY, buildSummaryValue(HttpMethod.PUT, moduleName, deviceName, nodeName));
145         value.set(TAGS_KEY, buildTagsValue(deviceName, moduleName));
146         final ArrayNode parameters = JsonNodeFactory.instance.arrayNode().addAll(pathParams);
147         final String defName = parentName + CONFIG + "_" + nodeName + TOP;
148         final String xmlDefName = parentName + CONFIG + "_" + nodeName;
149         insertRequestBodyParameter(value, defName, xmlDefName, nodeName + CONFIG);
150         value.set(PARAMETERS_KEY, parameters);
151
152         final ObjectNode responses = JsonNodeFactory.instance.objectNode();
153         responses.set(String.valueOf(Response.Status.CREATED.getStatusCode()),
154                 buildResponse(Response.Status.CREATED.getReasonPhrase(), Optional.empty()));
155         responses.set(String.valueOf(Response.Status.NO_CONTENT.getStatusCode()),
156                 buildResponse("Updated", Optional.empty()));
157
158         value.set(RESPONSES_KEY, responses);
159         return value;
160     }
161
162     public static ObjectNode buildPatch(final String parentName, final String nodeName, final String moduleName,
163             final Optional<String> deviceName, final String description, final ArrayNode pathParams) {
164         final ObjectNode value = JsonNodeFactory.instance.objectNode();
165         value.put(DESCRIPTION_KEY, description);
166         value.put(SUMMARY_KEY, buildSummaryValue(HttpMethod.PATCH, moduleName, deviceName, nodeName));
167         value.set(TAGS_KEY, buildTagsValue(deviceName, moduleName));
168         final ArrayNode parameters = JsonNodeFactory.instance.arrayNode().addAll(pathParams);
169         final String defName = parentName + CONFIG + "_" + nodeName + TOP;
170         final String xmlDefName = parentName + CONFIG + "_" + nodeName;
171         insertRequestBodyParameter(value, defName, xmlDefName, nodeName + CONFIG);
172         value.set(PARAMETERS_KEY, parameters);
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         value.set(RESPONSES_KEY, responses);
181         return value;
182     }
183
184     public static ObjectNode buildDelete(final DataSchemaNode node, final String moduleName,
185             final Optional<String> deviceName, final ArrayNode pathParams) {
186         final ObjectNode value = JsonNodeFactory.instance.objectNode();
187         value.put(SUMMARY_KEY, buildSummaryValue(HttpMethod.DELETE, moduleName, deviceName,
188                 node.getQName().getLocalName()));
189         value.set(TAGS_KEY, buildTagsValue(deviceName, moduleName));
190         value.put(DESCRIPTION_KEY, node.getDescription().orElse(""));
191         final ArrayNode parameters = JsonNodeFactory.instance.arrayNode().addAll(pathParams);
192         value.set(PARAMETERS_KEY, parameters);
193
194         final ObjectNode responses = JsonNodeFactory.instance.objectNode();
195         responses.set(String.valueOf(Response.Status.NO_CONTENT.getStatusCode()),
196                 buildResponse("Deleted", Optional.empty()));
197
198         value.set(RESPONSES_KEY, responses);
199         return value;
200     }
201
202     public static ObjectNode buildPostOperation(final OperationDefinition operDef, final String moduleName,
203             final Optional<String> deviceName, final String parentName, final DefinitionNames definitionNames) {
204         final ObjectNode postOperation = JsonNodeFactory.instance.objectNode();
205         final ArrayNode parameters = JsonNodeFactory.instance.arrayNode();
206         final String operName = operDef.getQName().getLocalName();
207         final String inputName = operName + INPUT_SUFFIX;
208
209         final InputSchemaNode input = operDef.getInput();
210         final OutputSchemaNode output = operDef.getOutput();
211         if (!input.getChildNodes().isEmpty()) {
212             final String discriminator = definitionNames.getDiscriminator(input);
213             final String clearDefName = parentName + "_" + operName + INPUT_SUFFIX;
214             final String defName = clearDefName + discriminator;
215             final String defTopName = clearDefName + TOP + discriminator;
216             insertRequestBodyParameter(postOperation, defTopName, defName, inputName);
217         } else {
218             final ObjectNode payload = JsonNodeFactory.instance.objectNode();
219             final ObjectNode jsonSchema = JsonNodeFactory.instance.objectNode();
220             final ObjectNode properties = JsonNodeFactory.instance.objectNode();
221             final ObjectNode inputSchema = JsonNodeFactory.instance.objectNode();
222             inputSchema.put(TYPE_KEY, OBJECT);
223             properties.set(INPUT_KEY, inputSchema);
224             jsonSchema.put(TYPE_KEY, OBJECT);
225             jsonSchema.set(PROPERTIES_KEY, properties);
226             final ObjectNode content = JsonNodeFactory.instance.objectNode();
227             final ObjectNode jsonTypeValue = JsonNodeFactory.instance.objectNode();
228             jsonTypeValue.set(SCHEMA_KEY, jsonSchema);
229             content.set(MediaType.APPLICATION_JSON, jsonTypeValue);
230
231             final ObjectNode xmlSchema = JsonNodeFactory.instance.objectNode();
232             xmlSchema.put(TYPE_KEY, OBJECT);
233             final ObjectNode xml = JsonNodeFactory.instance.objectNode();
234             xml.put(NAME_KEY, INPUT);
235             xmlSchema.set(XML_KEY, xml);
236             final ObjectNode xmlTypeValue = JsonNodeFactory.instance.objectNode();
237             xmlTypeValue.set(SCHEMA_KEY, xmlSchema);
238             content.set(MediaType.APPLICATION_XML, xmlTypeValue);
239
240             payload.set(CONTENT_KEY, content);
241             payload.put(DESCRIPTION_KEY, inputName);
242             postOperation.set(REQUEST_BODY_KEY, payload);
243         }
244         postOperation.set(PARAMETERS_KEY, parameters);
245         final ObjectNode responses = JsonNodeFactory.instance.objectNode();
246         final String description = String.format("RPC %s success", operName);
247
248         if (!output.getChildNodes().isEmpty()) {
249             final ObjectNode schema = JsonNodeFactory.instance.objectNode();
250             final String defName = parentName + "_" + operName + OUTPUT_SUFFIX + TOP
251                     + definitionNames.getDiscriminator(output);
252             schema.put(REF_KEY, COMPONENTS_PREFIX + defName);
253             responses.set(String.valueOf(Response.Status.OK.getStatusCode()), buildResponse(description,
254                     Optional.of(schema)));
255         } else {
256             responses.set(String.valueOf(Response.Status.NO_CONTENT.getStatusCode()), buildResponse(description,
257                     Optional.empty()));
258         }
259         postOperation.set(RESPONSES_KEY, responses);
260         postOperation.put(DESCRIPTION_KEY, operDef.getDescription().orElse(""));
261         postOperation.put(SUMMARY_KEY, buildSummaryValue(HttpMethod.POST, moduleName, deviceName, operName));
262         postOperation.set(TAGS_KEY, buildTagsValue(deviceName, moduleName));
263         return postOperation;
264     }
265
266     private static void insertRequestBodyParameter(final ObjectNode operation, final String defName,
267             final String xmlDefName, final String name) {
268         final ObjectNode payload = JsonNodeFactory.instance.objectNode();
269         final ObjectNode content = JsonNodeFactory.instance.objectNode();
270         final JsonNode node = operation.get(SUMMARY_KEY);
271         if (node != null && node.asText().contains(HttpMethod.PATCH)) {
272             content.set("application/yang-data+json", buildMimeTypeValue(defName));
273             content.set("application/yang-data+xml", buildMimeTypeValue(xmlDefName));
274         } else {
275             content.set(MediaType.APPLICATION_JSON, buildMimeTypeValue(defName));
276             content.set(MediaType.APPLICATION_XML, buildMimeTypeValue(xmlDefName));
277         }
278         payload.set(CONTENT_KEY, content);
279         payload.put(DESCRIPTION_KEY, name);
280         operation.set(REQUEST_BODY_KEY, payload);
281     }
282
283     private static ObjectNode buildRefSchema(final String defName) {
284         final ObjectNode schema = JsonNodeFactory.instance.objectNode();
285         schema.put(REF_KEY, COMPONENTS_PREFIX + defName);
286         return schema;
287     }
288
289     private static ObjectNode buildMimeTypeValue(final String defName) {
290         final ObjectNode mimeTypeValue = JsonNodeFactory.instance.objectNode();
291         mimeTypeValue.set(SCHEMA_KEY, buildRefSchema(defName));
292         return mimeTypeValue;
293     }
294
295     public static ObjectNode buildResponse(final String description, final Optional<ObjectNode> schema) {
296         final ObjectNode response = JsonNodeFactory.instance.objectNode();
297
298         if (schema.isPresent()) {
299             final ObjectNode schemaValue = schema.orElseThrow();
300             final ObjectNode content = JsonNodeFactory.instance.objectNode();
301             final ObjectNode body = JsonNodeFactory.instance.objectNode();
302             for (final String mimeType : MIME_TYPES) {
303                 content.set(mimeType, body);
304             }
305             body.set(SCHEMA_KEY, schemaValue);
306             response.set(CONTENT_KEY, content);
307         }
308         response.put(DESCRIPTION_KEY, description);
309         return response;
310     }
311
312     private static String buildSummaryValue(final String httpMethod, final String moduleName,
313             final Optional<String> deviceName, final String nodeName) {
314         return httpMethod + SUMMARY_SEPARATOR + deviceName.map(s -> s + SUMMARY_SEPARATOR).orElse("")
315                 + moduleName + SUMMARY_SEPARATOR + nodeName;
316     }
317
318     public static ArrayNode buildTagsValue(final Optional<String> deviceName, final String moduleName) {
319         final ArrayNode tagsValue = JsonNodeFactory.instance.arrayNode();
320         tagsValue.add(deviceName.map(s -> "mounted " + s).orElse("controller") + " " + moduleName);
321         return tagsValue;
322     }
323
324     public static ObjectNode getTypeParentNode(final ObjectNode parameter) {
325         final ObjectNode schema = JsonNodeFactory.instance.objectNode();
326         parameter.set(SCHEMA_KEY, schema);
327         return schema;
328     }
329 }