Refactor pretty printing
[netconf.git] / restconf / restconf-nb / src / main / java / org / opendaylight / restconf / server / api / OperationOutputBody.java
1 /*
2  * Copyright (c) 2024 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 package org.opendaylight.restconf.server.api;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.annotations.VisibleForTesting;
13 import com.google.common.base.MoreObjects.ToStringHelper;
14 import java.io.IOException;
15 import java.io.OutputStream;
16 import org.eclipse.jdt.annotation.NonNullByDefault;
17 import org.opendaylight.restconf.nb.rfc8040.jersey.providers.ParameterAwareNormalizedNodeWriter;
18 import org.opendaylight.restconf.server.api.DatabindPath.OperationPath;
19 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
20 import org.opendaylight.yangtools.yang.data.codec.gson.JSONNormalizedNodeStreamWriter;
21 import org.opendaylight.yangtools.yang.data.codec.xml.XMLStreamNormalizedNodeStreamWriter;
22 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
23
24 /**
25  * A {@link FormattableBody} corresponding to a {@code rpc} or {@code action} invocation.
26  */
27 @NonNullByDefault
28 public final class OperationOutputBody extends FormattableBody {
29     private final OperationPath path;
30     private final ContainerNode output;
31
32     public OperationOutputBody(final FormatParameters format, final OperationPath path, final ContainerNode output) {
33         super(format);
34         this.path = requireNonNull(path);
35         this.output = requireNonNull(output);
36         if (output.isEmpty()) {
37             throw new IllegalArgumentException("output may not be empty");
38         }
39     }
40
41     @VisibleForTesting
42     public ContainerNode output() {
43         return output;
44     }
45
46     @Override
47     void formatToJSON(final OutputStream out, final FormatParameters format) throws IOException {
48         final var stack = prepareStack();
49
50         // RpcDefinition/ActionDefinition is not supported as initial codec in JSONStreamWriter, so we need to emit
51         // initial output declaration
52         try (var jsonWriter = createJsonWriter(out, format)) {
53             final var module = stack.currentModule();
54             jsonWriter.beginObject().name(module.argument().getLocalName() + ":output").beginObject();
55
56             final var nnWriter = ParameterAwareNormalizedNodeWriter.forStreamWriter(
57                 JSONNormalizedNodeStreamWriter.createNestedWriter(path.databind().jsonCodecs(), stack.toInference(),
58                     module.namespace().argument(), jsonWriter), null, null);
59             for (var child : output.body()) {
60                 nnWriter.write(child);
61             }
62             nnWriter.flush();
63
64             jsonWriter.endObject().endObject();
65         }
66     }
67
68     @Override
69     void formatToXML(final OutputStream out, final FormatParameters format) throws IOException {
70         final var stack = prepareStack();
71
72         // RpcDefinition/ActionDefinition is not supported as initial codec in XMLStreamWriter, so we need to emit
73         // initial output declaration.
74         final var xmlWriter = createXmlWriter(out, format);
75         final var nnWriter = ParameterAwareNormalizedNodeWriter.forStreamWriter(
76             XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, stack.toInference()), null, null);
77
78         writeElements(xmlWriter, nnWriter, output);
79         nnWriter.flush();
80     }
81
82     @Override
83     ToStringHelper addToStringAttributes(final ToStringHelper helper) {
84         return super.addToStringAttributes(helper.add("path", path).add("output", output.prettyTree()));
85     }
86
87     private SchemaInferenceStack prepareStack() {
88         final var stack = path.inference().toSchemaInferenceStack();
89         stack.enterSchemaTree(path.outputStatement().argument());
90         return stack;
91     }
92 }