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.marshall.oapi; 018 019import java.util.*; 020import java.util.concurrent.*; 021 022import org.apache.juneau.commons.*; 023import org.apache.juneau.commons.bean.*; 024import org.apache.juneau.commons.collections.*; 025import org.apache.juneau.commons.httppart.*; 026import org.apache.juneau.marshall.*; 027import org.apache.juneau.marshall.uon.*; 028 029/** 030 * Serializes POJOs to values suitable for transmission as HTTP headers, query/form-data parameters, and path variables. 031 * 032 * <h5 class='section'>Notes:</h5><ul> 033 * <li class='note'>This class is thread safe and reusable. 034 * </ul> 035 * 036 * <h5 class='section'>See Also:</h5><ul> 037 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/OpenApiSupport">OpenApi</a> 038 039 * </ul> 040 */ 041@SuppressWarnings({ 042 "java:S110", // Inheritance depth acceptable for OpenApiSerializer hierarchy 043 "java:S9149" // Per-format static factories intentionally shadow the parent's. 044}) 045public class OpenApiSerializer extends UonSerializer implements OpenApiMetaProvider { 046 047 /** 048 * Builder class. 049 */ 050 public static class Builder extends UonSerializer.Builder<Builder> { 051 052 private static final Cache<HashKey,OpenApiSerializer> CACHE = Cache.of(HashKey.class, OpenApiSerializer.class).build(); 053 054 HttpPartFormat format; 055 HttpPartCollectionFormat collectionFormat; 056 057 /** 058 * Constructor, default settings. 059 */ 060 protected Builder() { 061 produces("text/openapi"); 062 format = HttpPartFormat.NO_FORMAT; 063 collectionFormat = HttpPartCollectionFormat.NO_COLLECTION_FORMAT; 064 } 065 066 /** 067 * Copy constructor. 068 * 069 * @param copyFrom The builder to copy from. 070 */ 071 protected Builder(Builder copyFrom) { 072 super(copyFrom); 073 format = copyFrom.format; 074 collectionFormat = copyFrom.collectionFormat; 075 } 076 077 /** 078 * Copy constructor. 079 * 080 * @param copyFrom The bean to copy from. 081 */ 082 protected Builder(OpenApiSerializer copyFrom) { 083 super(copyFrom); 084 format = copyFrom.format; 085 collectionFormat = copyFrom.collectionFormat; 086 } 087 088 @Override /* Overridden from Context.Builder<?> */ 089 public OpenApiSerializer build() { 090 return cache(CACHE).build(OpenApiSerializer.class); 091 } 092 093 /** 094 * <i><l>OpenApiCommon</l> configuration property: </i> Default collection format for HTTP parts. 095 * 096 * <p> 097 * Specifies the collection format to use for HTTP parts when not otherwise specified via {@link Schema#collectionFormat()}. 098 * 099 * <h5 class='section'>Example:</h5> 100 * <p class='bjava'> 101 * <jc>// Create a serializer using CSV for collections.</jc> 102 * OpenApiSerializer <jv>serializer1</jv> = OpenApiSerializer 103 * .<jsm>create</jsm>() 104 * .collectionFormat(<jsf>CSV</jsf>) 105 * .build(); 106 * 107 * <jc>// Create a serializer using UON for collections.</jc> 108 * OpenApiSerializer <jv>serializer2</jv> = OpenApiSerializer 109 * .<jsm>create</jsm>() 110 * .collectionFormat(<jsf>UON</jsf>) 111 * .build(); 112 * 113 * <jc>// An arbitrary data structure.</jc> 114 * JsonList <jv>list</jv> = JsonList.<jsm>of</jsm>( 115 * <js>"foo"</js>, 116 * <js>"bar"</js>, 117 * JsonMap.<jsm>of</jsm>( 118 * <js>"baz"</js>, JsonList.<jsm>of</jsm>(<js>"qux"</js>,<js>"true"</js>,<js>"123"</js>) 119 * ) 120 * ); 121 * 122 * <jc>// Produces: "foo=bar,baz=qux\,true\,123"</jc> 123 * String <jv>value1</jv> = <jv>serializer1</jv>.write(<jv>list</jv>) 124 * 125 * <jc>// Produces: "(foo=bar,baz=@(qux,'true','123'))"</jc> 126 * String <jv>value2</jv> = <jv>serializer2</jv>.write(<jv>list</jv>) 127 * </p> 128 * 129 * <ul class='values javatree'> 130 * <li class='jc'>{@link HttpPartFormat} 131 * <ul> 132 * <li class='jf'>{@link HttpPartCollectionFormat#CSV CSV} - (default) Comma-separated values (e.g. <js>"foo,bar"</js>). 133 * <li class='jf'>{@link HttpPartCollectionFormat#SSV SSV} - Space-separated values (e.g. <js>"foo bar"</js>). 134 * <li class='jf'>{@link HttpPartCollectionFormat#TSV TSV} - Tab-separated values (e.g. <js>"foo\tbar"</js>). 135 * <li class='jf'>{@link HttpPartCollectionFormat#PIPES PIPES} - Pipe-separated values (e.g. <js>"foo|bar"</js>). 136 * <li class='jf'>{@link HttpPartCollectionFormat#MULTI MULTI} - Corresponds to multiple parameter instances instead of multiple values for a single instance (e.g. <js>"foo=bar&foo=baz"</js>). 137 * <li class='jf'>{@link HttpPartCollectionFormat#UONC UONC} - UON collection notation (e.g. <js>"@(foo,bar)"</js>). 138 * </ul> 139 * </ul> 140 * 141 * @param value The new value for this property. 142 * @return This object. 143 */ 144 public Builder collectionFormat(HttpPartCollectionFormat value) { 145 collectionFormat = value; 146 return this; 147 } 148 149 @Override /* Overridden from Context.Builder<?> */ 150 public Builder copy() { 151 return new Builder(this); 152 } 153 154 /** 155 * <i><l>OpenApiCommon</l> configuration property: </i> Default format for HTTP parts. 156 * 157 * <p> 158 * Specifies the format to use for HTTP parts when not otherwise specified via {@link Schema#format()}. 159 * 160 * <h5 class='section'>Example:</h5> 161 * <p class='bjava'> 162 * <jc>// Create a plain-text serializer.</jc> 163 * OpenApiSerializer <jv>serializer1</jv> = OpenApiSerializer 164 * .<jsm>create</jsm>() 165 * .build(); 166 * 167 * <jc>// Create a UON serializer.</jc> 168 * OpenApiSerializer <jv>serializer2</jv> = OpenApiSerializer 169 * .<jsm>create</jsm>() 170 * .format(<jsf>UON</jsf>) 171 * .build(); 172 * 173 * String <jv>string</jv> = <js>"foo bar"</js>; 174 * 175 * <jc>// Produces: "foo bar"</jc> 176 * String <jv>value1</jv> = <jv>serializer1</jv>.write(<jv>string</jv>); 177 * 178 * <jc>// Produces: "'foo bar'"</jc> 179 * String <jv>value2</jv> = <jv>serializer2</jv>.write(<jv>string</jv>); 180 * </p> 181 * 182 * <ul class='values javatree'> 183 * <li class='jc'>{@link HttpPartFormat} 184 * <ul> 185 * <li class='jf'>{@link HttpPartFormat#UON UON} - UON notation (e.g. <js>"'foo bar'"</js>). 186 * <li class='jf'>{@link HttpPartFormat#INT32 INT32} - Signed 32 bits. 187 * <li class='jf'>{@link HttpPartFormat#INT64 INT64} - Signed 64 bits. 188 * <li class='jf'>{@link HttpPartFormat#FLOAT FLOAT} - 32-bit floating point number. 189 * <li class='jf'>{@link HttpPartFormat#DOUBLE DOUBLE} - 64-bit floating point number. 190 * <li class='jf'>{@link HttpPartFormat#BYTE BYTE} - BASE-64 encoded characters. 191 * <li class='jf'>{@link HttpPartFormat#BINARY BINARY} - Hexadecimal encoded octets (e.g. <js>"00FF"</js>). 192 * <li class='jf'>{@link HttpPartFormat#BINARY_SPACED BINARY_SPACED} - Spaced-separated hexadecimal encoded octets (e.g. <js>"00 FF"</js>). 193 * <li class='jf'>{@link HttpPartFormat#DATE DATE} - An <a href='http://xml2rfc.ietf.org/public/rfc/html/rfc3339.html#anchor14'>RFC3339 full-date</a>. 194 * <li class='jf'>{@link HttpPartFormat#DATE_TIME DATE_TIME} - An <a href='http://xml2rfc.ietf.org/public/rfc/html/rfc3339.html#anchor14'>RFC3339 date-time</a>. 195 * <li class='jf'>{@link HttpPartFormat#PASSWORD PASSWORD} - Used to hint UIs the input needs to be obscured. 196 * <li class='jf'>{@link HttpPartFormat#NO_FORMAT NO_FORMAT} - (default) Not specified. 197 * </ul> 198 * </ul> 199 * 200 * @param value The new value for this property. 201 * @return This object. 202 */ 203 public Builder format(HttpPartFormat value) { 204 format = value; 205 return this; 206 } 207 208 @Override /* Overridden from Context.Builder<?> */ 209 public HashKey hashKey() { 210 // @formatter:off 211 return HashKey.of( 212 super.hashKey(), 213 format, 214 collectionFormat 215 ); 216 // @formatter:on 217 } 218 219 220 } 221 222 /** Reusable instance of {@link OpenApiSerializer}, all default settings. */ 223 public static final OpenApiSerializer DEFAULT = new OpenApiSerializer(create()); 224 225 /** 226 * Creates a new builder for this object. 227 * 228 * @return A new builder. 229 */ 230 public static Builder create() { 231 return new Builder(); 232 } 233 234 final HttpPartFormat format; 235 final HttpPartCollectionFormat collectionFormat; 236 237 private final Map<ClassMeta<?>,OpenApiClassMeta> openApiClassMetas = new ConcurrentHashMap<>(); 238 private final Map<BeanPropertyMeta,OpenApiBeanPropertyMeta> openApiBeanPropertyMetas = new ConcurrentHashMap<>(); 239 240 /** 241 * Constructor. 242 * 243 * @param builder 244 * The builder for this object. 245 */ 246 public OpenApiSerializer(Builder builder) { 247 super(builder.encoding(false)); 248 format = builder.format; 249 collectionFormat = builder.collectionFormat; 250 } 251 252 @Override /* Overridden from Context */ 253 public Builder copy() { 254 return new Builder(this); 255 } 256 257 @Override /* Overridden from Context */ 258 public OpenApiSerializerSession.Builder createSession() { 259 return OpenApiSerializerSession.create(this); 260 } 261 262 @Override /* Overridden from OpenApiMetaProvider */ 263 public OpenApiBeanPropertyMeta getOpenApiBeanPropertyMeta(BeanPropertyMeta bpm) { 264 if (bpm == null) 265 return OpenApiBeanPropertyMeta.DEFAULT; 266 return openApiBeanPropertyMetas.computeIfAbsent(bpm, k -> new OpenApiBeanPropertyMeta(k.getDelegateFor(), this)); 267 } 268 269 @Override /* Overridden from OpenApiMetaProvider */ 270 public OpenApiClassMeta getOpenApiClassMeta(ClassMeta<?> cm) { 271 return openApiClassMetas.computeIfAbsent(cm, k -> new OpenApiClassMeta(k, this)); 272 } 273 274 @Override /* Overridden from HttpPartSerializer */ 275 public OpenApiSerializerSession getPartSession() { return OpenApiSerializerSession.create(this).build(); } 276 277 @Override /* Overridden from Context */ 278 public OpenApiSerializerSession getSession() { return createSession().build(); } 279 280 /** 281 * Returns the default collection format to use when not otherwise specified via {@link Schema#collectionFormat()} 282 * 283 * @return The default collection format to use when not otherwise specified via {@link Schema#collectionFormat()} 284 */ 285 protected final HttpPartCollectionFormat getCollectionFormat() { return collectionFormat; } 286 287 /** 288 * Returns the default format to use when not otherwise specified via {@link Schema#format()} 289 * 290 * @return The default format to use when not otherwise specified via {@link Schema#format()} 291 */ 292 protected final HttpPartFormat getFormat() { return format; } 293}