blob: 064683d19a823b3cd812ca7c0b7d31b2c6f1c8f7 [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.HttpPartCollectionFormat.*;
import static org.apache.juneau.httppart.HttpPartDataType.*;
import static org.apache.juneau.httppart.HttpPartFormat.*;
import java.io.*;
import java.lang.reflect.*;
import java.util.*;
import org.apache.juneau.*;
import org.apache.juneau.collections.*;
import org.apache.juneau.httppart.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.transform.*;
import org.apache.juneau.transforms.*;
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<OList> CM_OList = BC.getClassMeta(OList.class);
private static final ClassMeta<OMap> CM_OMap = BC.getClassMeta(OMap.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 {
if (partType == null)
partType = HttpPartType.OTHER;
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;
}
@Override /* ParserSession */
protected <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws IOException, ParseException, ExecutableException {
return parseInner(null, HttpPartSchema.DEFAULT, pipe.asString(), type);
}
@SuppressWarnings({ "unchecked" })
private<T> T parseInner(HttpPartType partType, HttpPartSchema schema, String in, ClassMeta<T> type) throws SchemaValidationException, ParseException {
schema.validateInput(in);
if (in == null || "null".equals(in)) {
if (schema.getDefault() == null)
return null;
in = schema.getDefault();
} else {
PojoSwap<T,Object> swap = (PojoSwap<T,Object>)type.getSwap(this);
BuilderSwap<T,Object> builder = (BuilderSwap<T,Object>)type.getBuilderSwap(this);
ClassMeta<?> sType = null;
if (builder != null)
sType = builder.getBuilderClassMeta(this);
else if (swap != null)
sType = swap.getSwapClassMeta(this);
else
sType = type;
if (sType.isOptional())
return (T)Optional.ofNullable(parseInner(partType, schema, in, sType.getElementType()));
HttpPartDataType t = schema.getType(sType);
if (partType == null)
partType = HttpPartType.OTHER;
HttpPartFormat f = schema.getFormat(sType);
if (f == HttpPartFormat.NO_FORMAT)
f = ctx.getFormat();
if (t == STRING) {
if (sType.isObject()) {
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 == HttpPartFormat.UON)
return super.parse(partType, schema, in, type);
return toType(in, type);
}
if (f == BYTE)
return toType(base64Decode(in), type);
if (f == DATE) {
try {
if (type.isCalendar())
return toType(TemporalCalendarSwap.IsoDate.DEFAULT.unswap(this, in, type), type);
if (type.isDate())
return toType(TemporalDateSwap.IsoDate.DEFAULT.unswap(this, in, type), type);
if (type.isTemporal())
return toType(TemporalSwap.IsoDate.DEFAULT.unswap(this, in, type), type);
return toType(in, type);
} catch (Exception e) {
throw new ParseException(e);
}
}
if (f == DATE_TIME) {
try {
if (type.isCalendar())
return toType(TemporalCalendarSwap.IsoDateTime.DEFAULT.unswap(this, in, type), type);
if (type.isDate())
return toType(TemporalDateSwap.IsoDateTime.DEFAULT.unswap(this, in, type), type);
if (type.isTemporal())
return toType(TemporalSwap.IsoDateTime.DEFAULT.unswap(this, in, type), type);
return toType(in, type);
} catch (Exception e) {
throw new ParseException(e);
}
}
if (f == BINARY)
return toType(fromHex(in), type);
if (f == BINARY_SPACED)
return toType(fromSpacedHex(in), type);
if (f == HttpPartFormat.UON)
return super.parse(partType, schema, in, type);
return toType(in, 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_Double), type);
} else if (t == ARRAY) {
HttpPartCollectionFormat cf = schema.getCollectionFormat();
if (cf == HttpPartCollectionFormat.NO_COLLECTION_FORMAT)
cf = ctx.getCollectionFormat();
if (cf == HttpPartCollectionFormat.UONC)
return super.parse(partType, schema, in, type);
if (type.isObject())
type = (ClassMeta<T>)CM_OList;
ClassMeta<?> eType = type.isObject() ? string() : type.getElementType();
if (eType == null)
eType = schema.getParsedType().getElementType();
if (eType == null)
eType = string();
String[] ss = {};
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 == HttpPartCollectionFormat.UONC)
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 = Array.newInstance(eType.getInnerClass(), ss.length);
for (int i = 0; i < ss.length; i++)
Array.set(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 == OBJECT) {
HttpPartCollectionFormat cf = schema.getCollectionFormat();
if (cf == HttpPartCollectionFormat.NO_COLLECTION_FORMAT)
cf = ctx.getCollectionFormat();
if (cf == HttpPartCollectionFormat.UONC)
return super.parse(partType, schema, in, type);
if (type.isObject())
type = (ClassMeta<T>)CM_OMap;
if (! type.isMapOrBean())
throw new ParseException("Invalid type {0} for part type OBJECT.", type);
String[] ss = {};
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 == HttpPartCollectionFormat.UONC)
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, ',');
}
if (type.isBean()) {
BeanMap<T> m = ctx.createBeanSession().newBeanMap(type.getInnerClass());
for (String s : ss) {
String[] kv = split(s, '=', 2);
if (kv.length != 2)
throw new ParseException("Invalid input {0} for part type OBJECT.", in);
String key = kv[0], value = kv[1];
BeanPropertyMeta bpm = m.getPropertyMeta(key);
if (bpm == null && ! isIgnoreUnknownBeanProperties())
throw new ParseException("Invalid input {0} for part type OBJECT. Cannot find property {1}", in, key);
m.put(key, parse(partType, schema.getProperty(key), value, bpm == null ? object() : bpm.getClassMeta()));
}
return m.getBean();
}
ClassMeta<?> eType = type.isObject() ? string() : type.getValueType();
if (eType == null)
eType = schema.getParsedType().getValueType();
if (eType == null)
eType = string();
try {
Map<String,Object> m = (Map<String,Object>)type.newInstance();
if (m == null)
m = OMap.create();
for (String s : ss) {
String[] kv = split(s, '=', 2);
if (kv.length != 2)
throw new ParseException("Invalid input {0} for part type OBJECT.", in);
String key = kv[0], value = kv[1];
m.put(key, parse(partType, schema.getProperty(key), value, eType));
}
return (T)m;
} catch (ExecutableException e) {
throw new ParseException(e);
}
} 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 OMap toMap() {
return super.toMap()
.a("OpenApiParserSession", new DefaultFilteringOMap()
);
}
}