1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.juneau.bean.openapi3;
18
19 import static org.apache.juneau.commons.utils.AssertionUtils.*;
20 import static org.apache.juneau.commons.utils.CollectionUtils.*;
21 import static org.apache.juneau.commons.utils.Utils.*;
22 import static org.apache.juneau.internal.ConverterUtils.*;
23
24 import java.util.*;
25
26 import org.apache.juneau.*;
27 import org.apache.juneau.commons.collections.*;
28
29 /**
30 * Used to aid in serialization, deserialization, and validation.
31 *
32 * <p>
33 * The Discriminator Object is used to aid in serialization, deserialization, and validation. It adds support for
34 * polymorphism by allowing schemas to be discriminated based on the value of a specific property. This is particularly
35 * useful when working with inheritance hierarchies in object-oriented programming.
36 *
37 * <h5 class='section'>OpenAPI Specification:</h5>
38 * <p>
39 * The Discriminator Object is composed of the following fields:
40 * <ul class='spaced-list'>
41 * <li><c>propertyName</c> (string, REQUIRED) - The name of the property in the payload that will hold the discriminator value
42 * <li><c>mapping</c> (map of strings) - An object to hold mappings between payload values and schema names or references
43 * </ul>
44 *
45 * <h5 class='section'>Example:</h5>
46 * <p class='bcode'>
47 * <jc>// Construct using SwaggerBuilder.</jc>
48 * Discriminator <jv>x</jv> = <jsm>discriminator</jsm>()
49 * .setPropertyName(<js>"petType"</js>)
50 * .setMapping(<jsm>map</jsm>(<js>"dog"</js>, <js>"#/components/schemas/Dog"</js>, <js>"cat"</js>, <js>"#/components/schemas/Cat"</js>));
51 *
52 * <jc>// Serialize using JsonSerializer.</jc>
53 * String <jv>json</jv> = Json.<jsm>from</jsm>(<jv>x</jv>);
54 *
55 * <jc>// Or just use toString() which does the same as above.</jc>
56 * <jv>json</jv> = <jv>x</jv>.toString();
57 * </p>
58 * <p class='bcode'>
59 * <jc>// Output</jc>
60 * {
61 * <js>"propertyName"</js>: <js>"petType"</js>,
62 * <js>"mapping"</js>: {
63 * <js>"dog"</js>: <js>"#/components/schemas/Dog"</js>,
64 * <js>"cat"</js>: <js>"#/components/schemas/Cat"</js>
65 * }
66 * }
67 * </p>
68 *
69 * <h5 class='section'>See Also:</h5><ul>
70 * <li class='link'><a class="doclink" href="https://spec.openapis.org/oas/v3.0.0#discriminator-object">OpenAPI Specification > Discriminator Object</a>
71 * <li class='link'><a class="doclink" href="https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/">OpenAPI Inheritance and Polymorphism</a>
72 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauBeanOpenApi3">juneau-bean-openapi-v3</a>
73 * </ul>
74 */
75 public class Discriminator extends OpenApiElement {
76
77 private String propertyName;
78 private Map<String,String> mapping = map();
79
80 /**
81 * Default constructor.
82 */
83 public Discriminator() {}
84
85 /**
86 * Copy constructor.
87 *
88 * @param copyFrom The object to copy.
89 */
90 public Discriminator(Discriminator copyFrom) {
91 super(copyFrom);
92
93 this.propertyName = copyFrom.propertyName;
94 this.mapping = copyOf(copyFrom.mapping);
95 }
96
97 /**
98 * Adds one or more values to the <property>mapping</property> property.
99 *
100 * @param key The key. Must not be <jk>null</jk>.
101 * @param value The value. Must not be <jk>null</jk>.
102 * @return This object
103 */
104 public Discriminator addMapping(String key, String value) {
105 assertArgNotNull("key", key);
106 assertArgNotNull("value", value);
107 mapping.put(key, value);
108 return this;
109 }
110
111 /**
112 * Make a deep copy of this object.
113 *
114 * @return A deep copy of this object.
115 */
116 public Discriminator copy() {
117 return new Discriminator(this);
118 }
119
120 @Override /* Overridden from OpenApiElement */
121 public <T> T get(String property, Class<T> type) {
122 assertArgNotNull("property", property);
123 return switch (property) {
124 case "propertyName" -> toType(getPropertyName(), type);
125 case "mapping" -> toType(getMapping(), type);
126 default -> super.get(property, type);
127 };
128 }
129
130 /**
131 * Bean property getter: <property>mapping</property>.
132 *
133 * <p>
134 * The URL for the target documentation.
135 *
136 * @return The property value, or <jk>null</jk> if it is not set.
137 */
138 public Map<String,String> getMapping() { return nullIfEmpty(mapping); }
139
140 /**
141 * Bean property getter: <property>propertyName</property>.
142 *
143 * <p>
144 * A short description of the target documentation.
145 *
146 * @return The property value, or <jk>null</jk> if it is not set.
147 */
148 public String getPropertyName() { return propertyName; }
149
150 @Override /* Overridden from OpenApiElement */
151 public Set<String> keySet() {
152 // @formatter:off
153 var s = setb(String.class)
154 .addIf(ne(mapping), "mapping")
155 .addIf(nn(propertyName), "propertyName")
156 .build();
157 // @formatter:on
158 return new MultiSet<>(s, super.keySet());
159 }
160
161 @Override /* Overridden from OpenApiElement */
162 public Discriminator set(String property, Object value) {
163 assertArgNotNull("property", property);
164 return switch (property) {
165 case "mapping" -> setMapping(toMapBuilder(value, String.class, String.class).sparse().build());
166 case "propertyName" -> setPropertyName(s(value));
167 default -> {
168 super.set(property, value);
169 yield this;
170 }
171 };
172 }
173
174 /**
175 * Bean property setter: <property>mapping</property>.
176 *
177 * <p>
178 * The URL for the target documentation.
179 *
180 * @param value
181 * The new value for this property.
182 * <br>Property value is required.
183 * <br>URIs defined by {@link UriResolver} can be used for values.
184 * <br>Can be <jk>null</jk> to unset the property.
185 * @return This object
186 */
187 public Discriminator setMapping(Map<String,String> value) {
188 mapping.clear();
189 if (nn(value))
190 mapping.putAll(value);
191 return this;
192 }
193
194 /**
195 * Bean property setter: <property>propertyName</property>.
196 *
197 * <p>
198 * A short description of the target documentation.
199 *
200 * @param value
201 * The new value for this property.
202 * <br>Can be <jk>null</jk> to unset the property.
203 * @return This object
204 */
205 public Discriminator setPropertyName(String value) {
206 propertyName = value;
207 return this;
208 }
209
210 @Override /* Overridden from OpenApiElement */
211 public Discriminator strict() {
212 super.strict();
213 return this;
214 }
215
216 @Override /* Overridden from OpenApiElement */
217 public Discriminator strict(Object value) {
218 super.strict(value);
219 return this;
220 }
221 }