2 * Copyright (c) 2020 PANTHEON.tech, s.r.o. 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.openapi.model.builder;
10 import static javax.ws.rs.core.Response.Status.NO_CONTENT;
11 import static javax.ws.rs.core.Response.Status.OK;
12 import static org.opendaylight.restconf.openapi.impl.DefinitionGenerator.INPUT_SUFFIX;
13 import static org.opendaylight.restconf.openapi.impl.DefinitionGenerator.OUTPUT_SUFFIX;
15 import java.util.AbstractMap.SimpleEntry;
16 import java.util.ArrayList;
17 import java.util.HashMap;
18 import java.util.List;
20 import javax.ws.rs.HttpMethod;
21 import javax.ws.rs.core.MediaType;
22 import javax.ws.rs.core.Response;
23 import org.eclipse.jdt.annotation.NonNull;
24 import org.opendaylight.restconf.openapi.impl.DefinitionNames;
25 import org.opendaylight.restconf.openapi.model.MediaTypeObject;
26 import org.opendaylight.restconf.openapi.model.Operation;
27 import org.opendaylight.restconf.openapi.model.Parameter;
28 import org.opendaylight.restconf.openapi.model.Property;
29 import org.opendaylight.restconf.openapi.model.RequestBody;
30 import org.opendaylight.restconf.openapi.model.ResponseObject;
31 import org.opendaylight.restconf.openapi.model.Schema;
32 import org.opendaylight.restconf.openapi.model.Xml;
33 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.InputSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
36 import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
37 import org.opendaylight.yangtools.yang.model.api.OutputSchemaNode;
39 public final class OperationBuilder {
40 public static final String COMPONENTS_PREFIX = "#/components/schemas/";
41 public static final String INPUT_KEY = "input";
42 public static final String SUMMARY_TEMPLATE = "%s - %s - %s - %s";
43 private static final List<String> MIME_TYPES = List.of(MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON);
44 private static final String OBJECT = "object";
45 private static final String POST_DESCRIPTION = """
48 In example payload, you can see only the first data node child of the resource to be created, following the
49 guidelines of RFC 8040, which allows us to create only one resource in POST request.
52 private OperationBuilder() {
56 public static Operation buildPost(final DataSchemaNode childNode, final String parentName, final String nodeName,
57 final String discriminator, final String moduleName, final @NonNull String deviceName,
58 final String description, final List<Parameter> pathParams) {
59 final var summary = SUMMARY_TEMPLATE.formatted(HttpMethod.POST, deviceName, moduleName, nodeName);
60 final List<String> tags = List.of(deviceName + " " + moduleName);
61 final List<Parameter> parameters = new ArrayList<>(pathParams);
62 final RequestBody requestBody;
63 final List<String> nameElements = new ArrayList<>();
64 if (parentName != null) {
65 nameElements.add(parentName);
67 if (childNode != null && childNode.isConfiguration()) {
68 final String childNodeName = childNode.getQName().getLocalName();
69 nameElements.add(nodeName);
70 nameElements.add(childNodeName + discriminator);
71 final String childDefName = String.join("_", nameElements);
72 requestBody = createRequestBodyParameter(childDefName, childNodeName, childNode instanceof ListSchemaNode,
73 summary, childNodeName);
75 nameElements.add(nodeName + discriminator);
76 final String defName = String.join("_", nameElements);
77 requestBody = createPostDataRequestBodyParameter(defName, nodeName);
79 final Map<String, ResponseObject> responses = Map.of(String.valueOf(Response.Status.CREATED.getStatusCode()),
80 buildResponse(Response.Status.CREATED.getReasonPhrase()));
82 return new Operation.Builder()
84 .parameters(parameters)
85 .requestBody(requestBody)
87 .description(description + POST_DESCRIPTION)
92 public static Operation buildGet(final DataSchemaNode node, final String parentName, final String moduleName,
93 final @NonNull String deviceName, final List<Parameter> pathParams, final boolean isConfig) {
94 final String nodeName = node.getQName().getLocalName();
95 final String defName = parentName + "_" + nodeName;
96 final String description = node.getDescription().orElse("");
97 final String summary = SUMMARY_TEMPLATE.formatted(HttpMethod.GET, deviceName, moduleName,
98 node.getQName().getLocalName());
99 final List<String> tags = List.of(deviceName + " " + moduleName);
100 final List<Parameter> parameters = new ArrayList<>(pathParams);
101 parameters.add(buildQueryParameters(isConfig));
102 final boolean isList = node instanceof ListSchemaNode;
103 final ResponseObject response = createResponse(defName, nodeName, isList,
104 String.valueOf(OK.getStatusCode()), summary);
105 final Map<String, ResponseObject> responses = Map.of(String.valueOf(OK.getStatusCode()),
108 return new Operation.Builder()
110 .parameters(parameters)
111 .responses(responses)
112 .description(description)
117 private static Parameter buildQueryParameters(final boolean isConfig) {
118 final List<String> cases = List.of("config", "nonconfig", "all");
120 return new Parameter.Builder()
124 .schema(new Schema.Builder().type("string").schemaEnum(cases).build())
128 public static Operation buildPut(final DataSchemaNode node, final String parentName, final String moduleName,
129 final @NonNull String deviceName, final List<Parameter> pathParams, final String fullName) {
130 final String nodeName = node.getQName().getLocalName();
131 final String summary = SUMMARY_TEMPLATE.formatted(HttpMethod.PUT, moduleName, deviceName, nodeName);
132 final List<String> tags = List.of(deviceName + " " + moduleName);
133 final List<Parameter> parameters = new ArrayList<>(pathParams);
134 final String defName = parentName + "_" + nodeName;
135 final boolean isList = node instanceof ListSchemaNode;
136 final RequestBody requestBody = createRequestBodyParameter(defName, fullName, isList, summary, nodeName);
138 final var created = new SimpleEntry<>(String.valueOf(Response.Status.CREATED.getStatusCode()),
139 buildResponse(Response.Status.CREATED.getReasonPhrase()));
140 final var updated = new SimpleEntry<>(String.valueOf(NO_CONTENT.getStatusCode()),
141 buildResponse("Updated"));
143 return new Operation.Builder()
145 .parameters(parameters)
146 .requestBody(requestBody)
147 .responses(Map.of(created.getKey(), created.getValue(), updated.getKey(), updated.getValue()))
148 .description(node.getDescription().orElse(""))
153 public static Operation buildPatch(final DataSchemaNode node, final String parentName, final String moduleName,
154 final @NonNull String deviceName, final List<Parameter> pathParams, final String fullName) {
155 final String nodeName = node.getQName().getLocalName();
156 final String summary = SUMMARY_TEMPLATE.formatted(HttpMethod.PATCH, moduleName, deviceName, nodeName);
157 final List<String> tags = List.of(deviceName + " " + moduleName);
158 final List<Parameter> parameters = new ArrayList<>(pathParams);
159 final String defName = parentName + "_" + nodeName;
160 final boolean isList = node instanceof ListSchemaNode;
161 final RequestBody requestBody = createRequestBodyParameter(defName, fullName, isList, summary, nodeName);
163 final SimpleEntry<String, ResponseObject> created = new SimpleEntry<>(String.valueOf(OK.getStatusCode()),
164 buildResponse(OK.getReasonPhrase()));
165 final SimpleEntry<String, ResponseObject> updated = new SimpleEntry<>(String.valueOf(NO_CONTENT
166 .getStatusCode()), buildResponse("Updated"));
168 return new Operation.Builder()
170 .parameters(parameters)
171 .requestBody(requestBody)
172 .responses(Map.of(created.getKey(), created.getValue(), updated.getKey(), updated.getValue()))
173 .description(node.getDescription().orElse(""))
178 public static Operation buildDelete(final DataSchemaNode node, final String moduleName,
179 final @NonNull String deviceName, final List<Parameter> pathParams) {
180 final String summary = SUMMARY_TEMPLATE.formatted(HttpMethod.DELETE, deviceName, moduleName,
181 node.getQName().getLocalName());
182 final List<String> tags = List.of(deviceName + " " + moduleName);
183 final String description = node.getDescription().orElse("");
184 final List<Parameter> parameters = new ArrayList<>(pathParams);
185 final Map<String, ResponseObject> responses = Map.of(String.valueOf(NO_CONTENT.getStatusCode()),
186 buildResponse("Deleted"));
188 return new Operation.Builder()
190 .parameters(parameters)
191 .responses(responses)
192 .description(description)
197 public static Operation buildPostOperation(final OperationDefinition operDef, final String moduleName,
198 final @NonNull String deviceName, final String parentName, final DefinitionNames definitionNames,
199 final List<Parameter> parentPathParameters) {
200 final List<Parameter> parameters = new ArrayList<>(parentPathParameters);
201 final String operationName = operDef.getQName().getLocalName();
202 final String inputName = operationName + INPUT_SUFFIX;
203 final String summary = SUMMARY_TEMPLATE.formatted(HttpMethod.POST, deviceName, moduleName, operationName);
205 final InputSchemaNode input = operDef.getInput();
206 final OutputSchemaNode output = operDef.getOutput();
207 final RequestBody requestBody;
208 if (!input.getChildNodes().isEmpty()) {
209 final String discriminator = definitionNames.getDiscriminator(input);
210 final String clearDefName = parentName + "_" + operationName + INPUT_SUFFIX;
211 final String defName = clearDefName + discriminator;
212 requestBody = createRequestBodyParameter(defName, INPUT_KEY, false, summary, inputName);
214 final Map<String, Property> properties = Map.of(INPUT_KEY, new Property.Builder().type(OBJECT).build());
215 final Schema jsonSchema = new Schema.Builder()
217 .properties(properties)
219 final MediaTypeObject jsonTypeValue = new MediaTypeObject.Builder()
222 final SimpleEntry<String, MediaTypeObject> jsonEntry = new SimpleEntry<>(MediaType.APPLICATION_JSON,
225 final Xml xml = new Xml(INPUT_KEY, input.getQName().getNamespace().toString(), null);
226 final Schema xmlSchema = new Schema.Builder()
230 final MediaTypeObject xmlTypeValue = new MediaTypeObject.Builder()
233 final SimpleEntry<String, MediaTypeObject> xmlEntry = new SimpleEntry<>(MediaType.APPLICATION_XML,
236 requestBody = new RequestBody.Builder()
237 .content(Map.of(jsonEntry.getKey(), jsonEntry.getValue(), xmlEntry.getKey(), xmlEntry.getValue()))
238 .description(inputName)
241 final String description = String.format("RPC %s success", operationName);
243 final Map<String, ResponseObject> responses;
244 if (!output.getChildNodes().isEmpty()) {
245 final String defName = parentName + "_" + operationName + OUTPUT_SUFFIX
246 + definitionNames.getDiscriminator(output);
247 final Schema schema = new Schema.Builder()
248 .ref(COMPONENTS_PREFIX + defName)
250 responses = Map.of(String.valueOf(OK.getStatusCode()), buildResponse(description, schema));
252 responses = Map.of(String.valueOf(NO_CONTENT.getStatusCode()), buildResponse(description));
254 final String desc = operDef.getDescription().orElse("");
255 final List<String> tags = List.of(deviceName + " " + moduleName);
256 return new Operation.Builder()
258 .parameters(parameters)
259 .requestBody(requestBody)
260 .responses(responses)
266 private static RequestBody createPostDataRequestBodyParameter(final String defName, final String name) {
267 final MediaTypeObject value = buildMediaTypeObject(defName);
268 final Map<String, MediaTypeObject> content = Map.of(MediaType.APPLICATION_JSON, value,
269 MediaType.APPLICATION_XML, value);
270 return new RequestBody.Builder()
276 private static RequestBody createRequestBodyParameter(final String defName, final String name,
277 final boolean isList, final String summary, final String description) {
278 final Map<String, MediaTypeObject> content = getStringMediaTypeObjectMap(defName, name, isList, summary);
279 return new RequestBody.Builder()
281 .description(description)
285 private static ResponseObject createResponse(final String defName, final String name,
286 final boolean isList, final String description, final String summary) {
287 final Map<String, MediaTypeObject> content = getStringMediaTypeObjectMap(defName, name, isList, summary);
288 return new ResponseObject.Builder()
290 .description(description)
294 private static Map<String, MediaTypeObject> getStringMediaTypeObjectMap(final String defName, final String name,
295 final boolean isList, final String summary) {
296 final Map<String, Property> properties;
298 properties = Map.of(name, new Property.Builder()
300 .items(new Property.Builder()
302 .ref(COMPONENTS_PREFIX + defName)
306 properties = Map.of(name, new Property.Builder()
308 .ref(COMPONENTS_PREFIX + defName)
311 final MediaTypeObject jsonSchema = new MediaTypeObject.Builder()
312 .schema(new Schema.Builder()
313 .properties(properties)
316 final Map<String, MediaTypeObject> content;
317 if (summary != null && summary.contains(HttpMethod.PATCH)) {
318 content = Map.of("application/yang-data+json", jsonSchema,
319 "application/yang-data+xml", buildMediaTypeObject(defName));
321 content = Map.of(MediaType.APPLICATION_JSON, jsonSchema,
322 MediaType.APPLICATION_XML, buildMediaTypeObject(defName));
327 private static Schema buildRefSchema(final String defName) {
328 return new Schema.Builder()
329 .ref(COMPONENTS_PREFIX + defName)
333 private static MediaTypeObject buildMediaTypeObject(final String defName) {
334 return new MediaTypeObject.Builder()
335 .schema(buildRefSchema(defName))
339 private static ResponseObject buildResponse(final String description) {
340 return new ResponseObject.Builder()
341 .description(description)
345 private static ResponseObject buildResponse(final String description, final Schema schema) {
346 final MediaTypeObject body = new MediaTypeObject.Builder()
349 final Map<String, MediaTypeObject> content = new HashMap<>();
350 for (final String mimeType : MIME_TYPES) {
351 content.put(mimeType, body);
353 return new ResponseObject.Builder()
355 .description(description)