blob: 3fc479741bfc17f624f1f40f31a1cd5034c05211 [file] [log] [blame]
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance *
// * with the License. You may obtain a copy of the License at *
// * *
// * http://www.apache.org/licenses/LICENSE-2.0 *
// * *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the *
// * specific language governing permissions and limitations under the License. *
// ***************************************************************************************************************************
package org.apache.juneau.oapi;
import static org.apache.juneau.internal.StringUtils.*;
import static org.apache.juneau.httppart.HttpPartSchema.Type.*;
import static org.apache.juneau.httppart.HttpPartSchema.Format.*;
import static org.apache.juneau.httppart.HttpPartSchema.CollectionFormat.*;
import java.util.*;
import org.apache.juneau.*;
import org.apache.juneau.httppart.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.uon.*;
/**
* Session object that lives for the duration of a single use of {@link OpenApiParser}.
*
* <p>
* This class is NOT thread safe.
* It is typically discarded after one-time use although it can be reused within the same thread.
*/
public class OpenApiParserSession extends UonParserSession {
// Cache these for faster lookup
private static final BeanContext BC = BeanContext.DEFAULT;
private static final ClassMeta<Long> CM_Long = BC.getClassMeta(Long.class);
private static final ClassMeta<Integer> CM_Integer = BC.getClassMeta(Integer.class);
private static final ClassMeta<Double> CM_Double = BC.getClassMeta(Double.class);
private static final ClassMeta<Float> CM_Float = BC.getClassMeta(Float.class);
private static final ClassMeta<Boolean> CM_Boolean = BC.getClassMeta(Boolean.class);
private static final ClassMeta<ObjectList> CM_ObjectList = BC.getClassMeta(ObjectList.class);
private static final ClassMeta<ObjectMap> CM_ObjectMap = BC.getClassMeta(ObjectMap.class);
private static final HttpPartSchema DEFAULT_SCHEMA = HttpPartSchema.DEFAULT;
//-------------------------------------------------------------------------------------------------------------------
// Instance
//-------------------------------------------------------------------------------------------------------------------
private final OpenApiParser ctx;
/**
* Create a new session using properties specified in the context.
*
* @param ctx
* The context creating this session object.
* The context contains all the configuration settings for this object.
* @param args
* Runtime session arguments.
*/
protected OpenApiParserSession(OpenApiParser ctx, ParserSessionArgs args) {
super(ctx, args);
this.ctx = ctx;
}
@SuppressWarnings("unchecked")
@Override /* HttpPartParser */
public <T> T parse(HttpPartType partType, HttpPartSchema schema, String in, ClassMeta<T> type) throws ParseException, SchemaValidationException {
boolean isOptional = type.isOptional();
while (type != null && type.isOptional())
type = (ClassMeta<T>)type.getElementType();
if (type == null)
type = (ClassMeta<T>)object();
schema = ObjectUtils.firstNonNull(schema, getSchema(), DEFAULT_SCHEMA);
T t = parseInner(partType, schema, in, type);
if (t == null && type.isPrimitive())
t = type.getPrimitiveDefault();
schema.validateOutput(t, ctx);
if (isOptional)
t = (T)Optional.ofNullable(t);
return t;
}
@SuppressWarnings({ "unchecked" })
private<T> T parseInner(HttpPartType partType, HttpPartSchema schema, String in, ClassMeta<T> type) throws SchemaValidationException, ParseException {
schema.validateInput(in);
if (in == null) {
if (schema.getDefault() == null)
return null;
in = schema.getDefault();
} else {
HttpPartSchema.Type t = schema.getType(type);
HttpPartSchema.Format f = schema.getFormat(type);
if (t == STRING) {
if (type.isObject()) {
if (f == BYTE)
return (T)base64Decode(in);
if (f == DATE || f == DATE_TIME)
return (T)parseIsoCalendar(in);
if (f == BINARY)
return (T)fromHex(in);
if (f == BINARY_SPACED)
return (T)fromSpacedHex(in);
if (f == HttpPartSchema.Format.UON)
return super.parse(partType, schema, in, type);
return (T)in;
}
if (f == BYTE)
return toType(base64Decode(in), type);
if (f == DATE || f == DATE_TIME)
return toType(parseIsoCalendar(in), type);
if (f == BINARY)
return toType(fromHex(in), type);
if (f == BINARY_SPACED)
return toType(fromSpacedHex(in), type);
if (f == HttpPartSchema.Format.UON)
return super.parse(partType, schema, in, type);
return toType(in, type);
} else if (t == ARRAY) {
if (type.isObject())
type = (ClassMeta<T>)CM_ObjectList;
ClassMeta<?> eType = type.isObject() ? string() : type.getElementType();
if (eType == null)
eType = schema.getParsedType().getElementType();
HttpPartSchema.CollectionFormat cf = schema.getCollectionFormat();
String[] ss = new String[0];
if (cf == MULTI)
ss = new String[]{in};
else if (cf == CSV)
ss = split(in, ',');
else if (cf == PIPES)
ss = split(in, '|');
else if (cf == SSV)
ss = splitQuoted(in);
else if (cf == TSV)
ss = split(in, '\t');
else if (cf == HttpPartSchema.CollectionFormat.UON)
return super.parse(partType, null, in, type);
else if (cf == NO_COLLECTION_FORMAT) {
if (firstNonWhitespaceChar(in) == '@' && lastNonWhitespaceChar(in) == ')')
return super.parse(partType, null, in, type);
ss = split(in, ',');
}
HttpPartSchema items = schema.getItems();
if (items == null)
items = HttpPartSchema.DEFAULT;
Object[] o = new Object[ss.length];
for (int i = 0; i < ss.length; i++)
o[i] = parse(partType, items, ss[i], eType);
if (type.hasMutaterFrom(schema.getParsedType()) || schema.getParsedType().hasMutaterTo(type))
return toType(toType(o, schema.getParsedType()), type);
return toType(o, type);
} else if (t == BOOLEAN) {
if (type.isObject())
type = (ClassMeta<T>)CM_Boolean;
if (type.isBoolean())
return super.parse(partType, schema, in, type);
return toType(super.parse(partType, schema, in, CM_Boolean), type);
} else if (t == INTEGER) {
if (type.isObject()) {
if (f == INT64)
type = (ClassMeta<T>)CM_Long;
else
type = (ClassMeta<T>)CM_Integer;
}
if (type.isNumber())
return super.parse(partType, schema, in, type);
return toType(super.parse(partType, schema, in, CM_Integer), type);
} else if (t == NUMBER) {
if (type.isObject()) {
if (f == DOUBLE)
type = (ClassMeta<T>)CM_Double;
else
type = (ClassMeta<T>)CM_Float;
}
if (type.isNumber())
return super.parse(partType, schema, in, type);
return toType(super.parse(partType, schema, in, CM_Integer), type);
} else if (t == OBJECT) {
if (type.isObject())
type = (ClassMeta<T>)CM_ObjectMap;
if (schema.hasProperties() && type.isMapOrBean()) {
try {
if (type.isBean()) {
BeanMap<T> m = BC.createBeanSession().newBeanMap(type.getInnerClass());
for (Map.Entry<String,Object> e : parse(partType, DEFAULT_SCHEMA, in, CM_ObjectMap).entrySet()) {
String key = e.getKey();
BeanPropertyMeta bpm = m.getPropertyMeta(key);
m.put(key, parse(partType, schema.getProperty(key), stringify(e.getValue()), bpm == null ? object() : bpm.getClassMeta()));
}
return m.getBean();
}
Map<String,Object> m = (Map<String,Object>)type.newInstance();
for (Map.Entry<String,Object> e : parse(partType, DEFAULT_SCHEMA, in, CM_ObjectMap).entrySet()) {
String key = e.getKey();
m.put(key, parse(partType, schema.getProperty(key), stringify(e.getValue()), object()));
}
return (T)m;
} catch (Exception e1) {
throw new ParseException(e1, "Could not instantiate type ''{0}''.", type);
}
}
return super.parse(partType, schema, in, type);
} else if (t == FILE) {
throw new ParseException("File part not supported.");
} else if (t == NO_TYPE) {
// This should never be returned by HttpPartSchema.getType(ClassMeta).
throw new ParseException("Invalid type.");
}
}
return super.parse(partType, schema, in, type);
}
private <T> T toType(Object in, ClassMeta<T> type) throws ParseException {
try {
return convertToType(in, type);
} catch (InvalidDataConversionException e) {
throw new ParseException(e.getMessage());
}
}
//-----------------------------------------------------------------------------------------------------------------
// Other methods
//-----------------------------------------------------------------------------------------------------------------
@Override /* Session */
public ObjectMap toMap() {
return super.toMap()
.append("OpenApiParserSession", new DefaultFilteringObjectMap()
);
}
}