001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.bean.openapi3; 018 019import static org.apache.juneau.commons.utils.AssertionUtils.*; 020import static org.apache.juneau.commons.utils.CollectionUtils.*; 021import static org.apache.juneau.commons.utils.Utils.*; 022import static org.apache.juneau.internal.ConverterUtils.*; 023 024import java.util.*; 025 026import org.apache.juneau.*; 027import org.apache.juneau.commons.collections.*; 028 029/** 030 * Used to aid in serialization, deserialization, and validation. 031 * 032 * <p> 033 * The Discriminator Object is used to aid in serialization, deserialization, and validation. It adds support for 034 * polymorphism by allowing schemas to be discriminated based on the value of a specific property. This is particularly 035 * useful when working with inheritance hierarchies in object-oriented programming. 036 * 037 * <h5 class='section'>OpenAPI Specification:</h5> 038 * <p> 039 * The Discriminator Object is composed of the following fields: 040 * <ul class='spaced-list'> 041 * <li><c>propertyName</c> (string, REQUIRED) - The name of the property in the payload that will hold the discriminator value 042 * <li><c>mapping</c> (map of strings) - An object to hold mappings between payload values and schema names or references 043 * </ul> 044 * 045 * <h5 class='section'>Example:</h5> 046 * <p class='bcode'> 047 * <jc>// Construct using SwaggerBuilder.</jc> 048 * Discriminator <jv>x</jv> = <jsm>discriminator</jsm>() 049 * .setPropertyName(<js>"petType"</js>) 050 * .setMapping(<jsm>map</jsm>(<js>"dog"</js>, <js>"#/components/schemas/Dog"</js>, <js>"cat"</js>, <js>"#/components/schemas/Cat"</js>)); 051 * 052 * <jc>// Serialize using JsonSerializer.</jc> 053 * String <jv>json</jv> = Json.<jsm>from</jsm>(<jv>x</jv>); 054 * 055 * <jc>// Or just use toString() which does the same as above.</jc> 056 * <jv>json</jv> = <jv>x</jv>.toString(); 057 * </p> 058 * <p class='bcode'> 059 * <jc>// Output</jc> 060 * { 061 * <js>"propertyName"</js>: <js>"petType"</js>, 062 * <js>"mapping"</js>: { 063 * <js>"dog"</js>: <js>"#/components/schemas/Dog"</js>, 064 * <js>"cat"</js>: <js>"#/components/schemas/Cat"</js> 065 * } 066 * } 067 * </p> 068 * 069 * <h5 class='section'>See Also:</h5><ul> 070 * <li class='link'><a class="doclink" href="https://spec.openapis.org/oas/v3.0.0#discriminator-object">OpenAPI Specification > Discriminator Object</a> 071 * <li class='link'><a class="doclink" href="https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/">OpenAPI Inheritance and Polymorphism</a> 072 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauBeanOpenApi3">juneau-bean-openapi-v3</a> 073 * </ul> 074 */ 075public class Discriminator extends OpenApiElement { 076 077 private String propertyName; 078 private Map<String,String> mapping = map(); 079 080 /** 081 * Default constructor. 082 */ 083 public Discriminator() {} 084 085 /** 086 * Copy constructor. 087 * 088 * @param copyFrom The object to copy. 089 */ 090 public Discriminator(Discriminator copyFrom) { 091 super(copyFrom); 092 093 this.propertyName = copyFrom.propertyName; 094 this.mapping = copyOf(copyFrom.mapping); 095 } 096 097 /** 098 * Adds one or more values to the <property>mapping</property> property. 099 * 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}