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 static org.apache.juneau.commons.httppart.HttpPartCollectionFormat.*; 020import static org.apache.juneau.commons.httppart.HttpPartDataType.*; 021import static org.apache.juneau.commons.httppart.HttpPartFormat.*; 022import static org.apache.juneau.commons.utils.ObjectUtils.*; 023import static org.apache.juneau.commons.utils.Shorts.*; 024import static org.apache.juneau.commons.utils.StringUtils.*; 025 026import java.io.*; 027import java.lang.reflect.*; 028import java.util.*; 029 030import org.apache.juneau.commons.httppart.*; 031import org.apache.juneau.commons.reflect.*; 032import org.apache.juneau.commons.time.*; 033import org.apache.juneau.commons.utils.*; 034import org.apache.juneau.marshall.*; 035import org.apache.juneau.marshall.collections.*; 036import org.apache.juneau.marshall.httppart.*; 037import org.apache.juneau.marshall.parser.*; 038import org.apache.juneau.marshall.swap.spi.*; 039import org.apache.juneau.marshall.uon.*; 040import org.apache.juneau.marshall.utils.*; 041 042/** 043 * Session object that lives for the duration of a single use of {@link OpenApiParser}. 044 * 045 * <h5 class='section'>Notes:</h5><ul> 046 * <li class='warn'>This class is not thread safe and is typically discarded after one use. 047 * </ul> 048 * 049 * <h5 class='section'>See Also:</h5><ul> 050 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/OpenApiSupport">OpenApi</a> 051 052 * </ul> 053 */ 054@SuppressWarnings({ 055 "java:S110", // Inheritance depth acceptable for OpenApiParserSession hierarchy 056 "unchecked" // (T) casts of parsed part values and (Map<String,Object>) cast of type.newInstance() in the OBJECT part path follow the requested ClassMeta 057}) 058public class OpenApiParserSession extends UonParserSession { 059 060 /** 061 * Builder class. 062 */ 063 public static class Builder extends UonParserSession.Builder<Builder> { 064 065 private final OpenApiParser ctx; 066 067 /** 068 * Constructor 069 * 070 * @param ctx The context creating this session. 071 */ 072 protected Builder(OpenApiParser ctx) { 073 super(ctx); 074 this.ctx = ctx; 075 } 076 077 @Override 078 public OpenApiParserSession build() { 079 return new OpenApiParserSession(this); 080 } 081 082 } 083 084 // Cache these for faster lookup 085 private static final MarshallingContext BC = MarshallingContext.DEFAULT; 086 private static final ClassMeta<Long> CM_Long = BC.getClassMeta(Long.class); 087 private static final ClassMeta<Integer> CM_Integer = BC.getClassMeta(Integer.class); 088 private static final ClassMeta<Double> CM_Double = BC.getClassMeta(Double.class); 089 private static final ClassMeta<Float> CM_Float = BC.getClassMeta(Float.class); 090 private static final ClassMeta<Boolean> CM_Boolean = BC.getClassMeta(Boolean.class); 091 private static final ClassMeta<JsonList> CM_JsonList = BC.getClassMeta(JsonList.class); 092 093 private static final ClassMeta<JsonMap> CM_JsonMap = BC.getClassMeta(JsonMap.class); 094 095 private static final HttpPartSchema DEFAULT_SCHEMA = HttpPartSchema.DEFAULT; 096 097 /** 098 * Creates a new builder for this object. 099 * 100 * @param ctx The context creating this session. 101 * @return A new builder. 102 */ 103 public static Builder create(OpenApiParser ctx) { 104 return new Builder(ctx); 105 } 106 107 private final OpenApiParser ctx; 108 109 /** 110 * Constructor. 111 * 112 * @param builder The builder for this object. 113 */ 114 protected OpenApiParserSession(Builder builder) { 115 super(builder); 116 ctx = builder.ctx; 117 } 118 119 @Override /* Overridden from HttpPartParser */ 120 public <T> T read(HttpPartType partType, HttpPartSchema schema, String in, ClassMeta<T> type) throws ParseException, SchemaValidationException { 121 if (partType == null) 122 partType = HttpPartType.OTHER; 123 124 var isOptional = type.isOptional(); 125 126 while (nn(type) && type.isOptional()) 127 type = (ClassMeta<T>)type.getElementType(); 128 129 if (type == null) 130 type = (ClassMeta<T>)object(); 131 132 schema = coalesce(schema, getSchema(), DEFAULT_SCHEMA); 133 134 T t = readInner(partType, schema, in, type); 135 if (t == null && type.isPrimitive()) 136 t = type.getPrimitiveDefault(); 137 schema.validateOutput(t); 138 139 if (isOptional) 140 t = (T)o(t); 141 142 return t; 143 } 144 145 @SuppressWarnings({ 146 "java:S3776", // Cognitive complexity acceptable for this specific logic 147 "java:S6541" // Single-threaded session contexts do not require synchronization 148 }) 149 private <T> T readInner(HttpPartType partType, HttpPartSchema schema, String in, ClassMeta<T> type) throws SchemaValidationException, ParseException { 150 schema.validateInput(in); 151 if (in == null || eq(in, "null")) { 152 if (schema.getDefault() == null) 153 return null; 154 in = schema.getDefault(); 155 } else { 156 157 var swap = (ObjectSwap<T,Object>)type.getSwap(this); 158 var builder = (BuilderSwap<T,Object>)type.getBuilderSwap(this); 159 ClassMeta<?> sType = null; 160 if (nn(builder)) 161 sType = builder.getBuilderClassMeta(this); 162 else if (nn(swap)) 163 sType = swap.getSwapClassMeta(this); 164 else 165 sType = type; 166 167 if (sType.isOptional()) 168 return (T)o(readInner(partType, schema, in, sType.getElementType())); 169 170 var t = schema.getType(sType); 171 if (partType == null) 172 partType = HttpPartType.OTHER; 173 174 var f = schema.getFormat(sType); 175 if (f == HttpPartFormat.NO_FORMAT) 176 f = ctx.getFormat(); 177 178 if (t == STRING) { 179 if (sType.isObject()) { 180 if (f == BYTE) 181 return toType(base64Decode(in), type); 182 if (f == DATE || f == DATE_TIME) { 183 var in2 = in; 184 return toType(o(in).filter(x1 -> ! isBlank(x1)).map(x -> GranularZonedDateTime.of(in2).getZonedDateTime()).map(GregorianCalendar::from).orElse(null), type); 185 } 186 if (f == BINARY) 187 return toType(fromHex(in), type); 188 if (f == BINARY_SPACED) 189 return toType(fromSpacedHex(in), type); 190 if (f == HttpPartFormat.UON) 191 return super.read(partType, schema, in, type); 192 return toType(in, type); 193 } 194 if (f == BYTE) 195 return toType(base64Decode(in), type); 196 if (f == DATE || f == DATE_TIME) { 197 if (type.isDateOrCalendarOrTemporal()) 198 return toType(Iso8601Utils.parse(in, type, getTimeZone()), type); 199 return toType(in, type); 200 } 201 if (f == BINARY) 202 return toType(fromHex(in), type); 203 if (f == BINARY_SPACED) 204 return toType(fromSpacedHex(in), type); 205 if (f == HttpPartFormat.UON) 206 return super.read(partType, schema, in, type); 207 return toType(in, type); 208 209 } else if (t == BOOLEAN) { 210 if (type.isObject()) 211 type = (ClassMeta<T>)CM_Boolean; 212 if (type.isBoolean()) 213 return super.read(partType, schema, in, type); 214 return toType(super.read(partType, schema, in, CM_Boolean), type); 215 216 } else if (t == INTEGER) { 217 if (type.isObject()) { 218 if (f == INT64) 219 type = (ClassMeta<T>)CM_Long; 220 else 221 type = (ClassMeta<T>)CM_Integer; 222 } 223 if (type.isNumber()) 224 return super.read(partType, schema, in, type); 225 return toType(super.read(partType, schema, in, CM_Integer), type); 226 227 } else if (t == NUMBER) { 228 if (type.isObject()) { 229 if (f == DOUBLE) 230 type = (ClassMeta<T>)CM_Double; 231 else 232 type = (ClassMeta<T>)CM_Float; 233 } 234 if (type.isNumber()) 235 return super.read(partType, schema, in, type); 236 return toType(super.read(partType, schema, in, CM_Double), type); 237 238 } else if (t == ARRAY) { 239 240 var cf = schema.getCollectionFormat(); 241 if (cf == HttpPartCollectionFormat.NO_COLLECTION_FORMAT) 242 cf = ctx.getCollectionFormat(); 243 244 if (cf == HttpPartCollectionFormat.UONC) 245 return super.read(partType, schema, in, type); 246 247 if (type.isObject()) 248 type = (ClassMeta<T>)CM_JsonList; 249 250 var spt = getClassMeta(schema.getParsedType()); 251 var eType = type.isObject() ? string() : type.getElementType(); 252 if (eType == null) 253 eType = spt.getElementType(); 254 if (eType == null) 255 eType = string(); 256 257 var ss = new String[] {}; 258 259 if (cf == MULTI) 260 ss = a(in); 261 else if (cf == CSV) 262 ss = StringUtils.splita(in, ','); 263 else if (cf == PIPES) 264 ss = StringUtils.splita(in, '|'); 265 else if (cf == SSV) 266 ss = StringUtils.splitQuoted(in); 267 else if (cf == TSV) 268 ss = StringUtils.splita(in, '\t'); 269 else if (cf == HttpPartCollectionFormat.UONC) 270 return super.read(partType, null, in, type); 271 else if (cf == NO_COLLECTION_FORMAT) { 272 if (firstNonWhitespaceChar(in) == '@' && lastNonWhitespaceChar(in) == ')') 273 return super.read(partType, null, in, type); 274 ss = StringUtils.splita(in, ','); 275 } 276 277 var items = schema.getItems(); 278 if (items == null) 279 items = HttpPartSchema.DEFAULT; 280 var o = Array.newInstance(eType.inner(), ss.length); 281 for (var i = 0; i < ss.length; i++) 282 Array.set(o, i, read(partType, items, ss[i], eType)); 283 if (type.hasMutaterFrom(spt) || spt.hasMutaterTo(type)) 284 return toType(toType(o, spt), type); 285 return toType(o, type); 286 287 } else if (t == OBJECT) { 288 289 var cf = schema.getCollectionFormat(); 290 if (cf == HttpPartCollectionFormat.NO_COLLECTION_FORMAT) 291 cf = ctx.getCollectionFormat(); 292 293 if (cf == HttpPartCollectionFormat.UONC) 294 return super.read(partType, schema, in, type); 295 296 if (type.isObject()) 297 type = (ClassMeta<T>)CM_JsonMap; 298 299 if (! type.isMapOrBean()) 300 throw new ParseException("Invalid type %s for part type OBJECT.", type); 301 302 var ss = new String[] {}; 303 304 if (cf == MULTI) 305 ss = a(in); 306 else if (cf == CSV) 307 ss = StringUtils.splita(in, ','); 308 else if (cf == PIPES) 309 ss = StringUtils.splita(in, '|'); 310 else if (cf == SSV) 311 ss = StringUtils.splitQuoted(in); 312 else if (cf == TSV) 313 ss = StringUtils.splita(in, '\t'); 314 else if (cf == HttpPartCollectionFormat.UONC) 315 return super.read(partType, null, in, type); 316 else if (cf == NO_COLLECTION_FORMAT) { 317 if (firstNonWhitespaceChar(in) == '@' && lastNonWhitespaceChar(in) == ')') 318 return super.read(partType, null, in, type); 319 ss = StringUtils.splita(in, ','); 320 } 321 322 if (type.isBean()) { 323 var m = ctx.getMarshallingContext().newBeanMap(type.inner()); 324 for (var s : ss) { 325 var kv = StringUtils.splita(s, '=', 2); 326 if (kv.length != 2) 327 throw new ParseException("Invalid input %s for part type OBJECT.", in); 328 var key = kv[0]; 329 var value = kv[1]; 330 var bpm = m.getPropertyMeta(key); 331 if (bpm == null && ! isIgnoreUnknownBeanProperties()) 332 throw new ParseException("Invalid input %s for part type OBJECT. Cannot find property %s", in, key); 333 m.put(key, read(partType, schema.getProperty(key), value, ((ClassMeta<T>)(bpm == null ? object() : bpm.getBeanInfo())))); 334 } 335 return checkRequired(m).getBean(); 336 } 337 338 var eType = type.isObject() ? string() : type.getValueType(); 339 if (eType == null) 340 eType = getClassMeta(schema.getParsedType()).getValueType(); 341 if (eType == null) 342 eType = string(); 343 344 try { 345 var m = (Map<String,Object>)type.newInstance(); 346 if (m == null) 347 m = JsonMap.create(); 348 349 for (var s : ss) { 350 var kv = StringUtils.splita(s, '=', 2); 351 if (kv.length != 2) 352 throw new ParseException("Invalid input %s for part type OBJECT.", in); 353 var key = kv[0]; 354 var value = kv[1]; 355 m.put(key, read(partType, schema.getProperty(key), value, eType)); 356 } 357 return (T)m; 358 } catch (ExecutableException e) { 359 throw new ParseException(e); 360 } 361 362 } else if (t == FILE) { 363 throw new ParseException("File part not supported."); 364 365 } else if (t == NO_TYPE) { 366 // This should never be returned by HttpPartSchema.getType(ClassMeta). 367 throw new ParseException("Invalid type."); 368 } 369 } 370 371 return super.read(partType, schema, in, type); 372 } 373 374 private <T> T toType(Object in, ClassMeta<T> type) throws ParseException { 375 try { 376 return convertToType(in, type); 377 } catch (InvalidDataConversionException e) { 378 throw new ParseException(e.getMessage()); 379 } 380 } 381 382 @Override /* Overridden from ParserSession */ 383 protected <T> T doRead(ParserPipe pipe, ClassMeta<T> type) throws IOException, ParseException, ExecutableException { 384 return readInner(null, HttpPartSchema.DEFAULT, pipe.asString(), type); 385 } 386}