blob: 7b611f2235139d06edefc4aa6928e414b9813d51 [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.httppart;
import static java.util.Collections.*;
import static org.apache.juneau.httppart.HttpPartDataType.*;
import static org.apache.juneau.httppart.HttpPartFormat.*;
import static org.apache.juneau.internal.StringUtils.*;
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.math.*;
import java.util.*;
import java.util.concurrent.atomic.*;
import java.util.regex.*;
import org.apache.juneau.*;
import org.apache.juneau.collections.*;
import org.apache.juneau.http.annotation.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.reflect.*;
/**
* Represents an OpenAPI schema definition.
*
* <p>
* The schema definition can be applied to any HTTP parts such as bodies, headers, query/form parameters, and URL path parts.
* <br>The API is generic enough to apply to any path part although some attributes may only applicable for certain parts.
*
* <p>
* Schema objects are created via builders instantiated through the {@link #create()} method.
*
* <p>
* This class is thread safe and reusable.
*
* <ul class='seealso'>
* <li class='link'>{@doc OpenApiDetails}
* </ul>
*/
public class HttpPartSchema {
//-------------------------------------------------------------------------------------------------------------------
// Predefined instances
//-------------------------------------------------------------------------------------------------------------------
/** Reusable instance of this object, all default settings. */
public static final HttpPartSchema DEFAULT = HttpPartSchema.create().allowEmptyValue(true).build();
/** Boolean type */
public static final HttpPartSchema T_BOOLEAN = HttpPartSchema.tBoolean().build();
/** File type */
public static final HttpPartSchema T_FILE = HttpPartSchema.tFile().build();
/** Integer type */
public static final HttpPartSchema T_INTEGER = HttpPartSchema.tInteger().build();
/** Int32 type */
public static final HttpPartSchema T_INT32 = HttpPartSchema.tInt32().build();
/** Int64 type */
public static final HttpPartSchema T_INT64 = HttpPartSchema.tInt64().build();
/** No type */
public static final HttpPartSchema T_NONE = HttpPartSchema.tNone().build();
/** Number type */
public static final HttpPartSchema T_NUMBER = HttpPartSchema.tNumber().build();
/** Float type */
public static final HttpPartSchema T_FLOAT = HttpPartSchema.tFloat().build();
/** Double type */
public static final HttpPartSchema T_DOUBLE = HttpPartSchema.tDouble().build();
/** String type */
public static final HttpPartSchema T_STRING = HttpPartSchema.tString().build();
/** Byte type */
public static final HttpPartSchema T_BYTE = HttpPartSchema.tByte().build();
/** Binary type */
public static final HttpPartSchema T_BINARY = HttpPartSchema.tBinary().build();
/** Spaced binary type */
public static final HttpPartSchema T_BINARY_SPACED = HttpPartSchema.tBinarySpaced().build();
/** Date type */
public static final HttpPartSchema T_DATE = HttpPartSchema.tDate().build();
/** Date-time type */
public static final HttpPartSchema T_DATETIME = HttpPartSchema.tDateTime().build();
/** UON-formated simple type */
public static final HttpPartSchema T_UON = HttpPartSchema.tUon().build();
/** Array type */
public static final HttpPartSchema T_ARRAY = HttpPartSchema.tArray().build();
/** Comma-delimited array type */
public static final HttpPartSchema T_ARRAY_CSV = HttpPartSchema.tArrayCsv().build();
/** Pipe-delimited array type */
public static final HttpPartSchema T_ARRAY_PIPES = HttpPartSchema.tArrayPipes().build();
/** Space-delimited array type */
public static final HttpPartSchema T_ARRAY_SSV = HttpPartSchema.tArraySsv().build();
/** Tab-delimited array type */
public static final HttpPartSchema T_ARRAY_TSV = HttpPartSchema.tArrayTsv().build();
/** UON-formatted array type */
public static final HttpPartSchema T_ARRAY_UON = HttpPartSchema.tArrayUon().build();
/** Multi-part array type */
public static final HttpPartSchema T_ARRAY_MULTI = HttpPartSchema.tArrayMulti().build();
/** Object type */
public static final HttpPartSchema T_OBJECT = HttpPartSchema.tObject().build();
/** Comma-delimited object type */
public static final HttpPartSchema T_OBJECT_CSV = HttpPartSchema.tObjectCsv().build();
/** Pipe-delimited object type */
public static final HttpPartSchema T_OBJECT_PIPES = HttpPartSchema.tObjectPipes().build();
/** Space-delimited object type */
public static final HttpPartSchema T_OBJECT_SSV = HttpPartSchema.tObjectSsv().build();
/** Tab-delimited object type */
public static final HttpPartSchema T_OBJECT_TSV = HttpPartSchema.tObjectTsv().build();
/** UON-formated object type */
public static final HttpPartSchema T_OBJECT_UON = HttpPartSchema.tObjectUon().build();
final String name;
final Set<Integer> codes;
final String _default;
final Set<String> _enum;
final Map<String,HttpPartSchema> properties;
final boolean allowEmptyValue, exclusiveMaximum, exclusiveMinimum, required, uniqueItems, skipIfEmpty;
final HttpPartCollectionFormat collectionFormat;
final HttpPartDataType type;
final HttpPartFormat format;
final Pattern pattern;
final HttpPartSchema items, additionalProperties;
final Number maximum, minimum, multipleOf;
final Long maxLength, minLength, maxItems, minItems, maxProperties, minProperties;
final Class<? extends HttpPartParser> parser;
final Class<? extends HttpPartSerializer> serializer;
final ClassMeta<?> parsedType;
/**
* Instantiates a new builder for this object.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder create() {
return new HttpPartSchemaBuilder();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>BOOLEAN</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tBoolean() {
return create().tBoolean();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>FILE</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tFile() {
return create().tFile();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>INTEGER</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tInteger() {
return create().tInteger();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>INTEGER</jsf>).format(HttpPartFormat.<jsf>INT32</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tInt32() {
return create().tInteger().fInt32();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>INTEGER</jsf>).format(HttpPartFormat.<jsf>INT64</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tInt64() {
return create().tInteger().fInt64();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>NONE</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tNone() {
return create().tNone();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>NUMBER</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tNumber() {
return create().tNumber();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>NUMBER</jsf>).format(HttpPartFormat.<jsf>FLOAT</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tFloat() {
return create().tNumber().fFloat();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>NUMBER</jsf>).format(HttpPartFormat.<jsf>DOUBLE</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tDouble() {
return create().tNumber().fDouble();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tString() {
return create().tString();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>BYTE</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tByte() {
return create().tString().fByte();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>BINARY</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tBinary() {
return create().tString().fBinary();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>BINARY_SPACED</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tBinarySpaced() {
return create().tString().fBinarySpaced();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>DATE</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tDate() {
return create().tString().fDate();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>DATE_TIME</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tDateTime() {
return create().tString().fDateTime();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>UON</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tUon() {
return create().tString().fUon();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tArray() {
return create().tArray();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).items(items)</c>.
*
* @param items The schema of the array items.
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tArray(HttpPartSchemaBuilder items) {
return create().tArray().items(items);
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>CSV</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tArrayCsv() {
return create().tArray().cfCsv();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>CSV</jsf>).items(items)</c>.
*
* @param items The schema of the array items.
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tArrayCsv(HttpPartSchemaBuilder items) {
return create().tArray().cfCsv().items(items);
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>PIPES</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tArrayPipes() {
return create().tArray().cfPipes();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>PIPES</jsf>).items(items)</c>.
*
* @param items The schema of the array items.
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tArrayPipes(HttpPartSchemaBuilder items) {
return create().tArray().cfPipes().items(items);
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>SSV</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tArraySsv() {
return create().tArray().cfSsv();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>SSV</jsf>).items(items)</c>.
*
* @param items The schema of the array items.
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tArraySsv(HttpPartSchemaBuilder items) {
return create().tArray().cfSsv().items(items);
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>TSV</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tArrayTsv() {
return create().tArray().cfTsv();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>TSV</jsf>).items(items)</c>.
*
* @param items The schema of the array items.
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tArrayTsv(HttpPartSchemaBuilder items) {
return create().tArray().cfTsv().items(items);
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>UONC</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tArrayUon() {
return create().tArray().cfUon();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>UONC</jsf>).items(items)</c>.
*
* @param items The schema of the array items.
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tArrayUon(HttpPartSchemaBuilder items) {
return create().tArray().cfUon().items(items);
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>MULTI</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tArrayMulti() {
return create().tArray().cfMulti();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>MULTI</jsf>).items(items)</c>.
*
* @param items The schema of the array items.
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tArrayMulti(HttpPartSchemaBuilder items) {
return create().tArray().cfMulti().items(items);
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>OBJECT</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tObject() {
return create().tObject();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>OBJECT</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>CSV</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tObjectCsv() {
return create().tObject().cfCsv();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>OBJECT</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>PIPES</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tObjectPipes() {
return create().tObject().cfPipes();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>OBJECT</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>SSV</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tObjectSsv() {
return create().tObject().cfSsv();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>OBJECT</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>TSV</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tObjectTsv() {
return create().tObject().cfTsv();
}
/**
* Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>OBJECT</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>UON</jsf>)</c>.
*
* @return A new builder for this object.
*/
public static HttpPartSchemaBuilder tObjectUon() {
return create().tObject().cfUon();
}
/**
* Finds the schema information for the specified method parameter.
*
* <p>
* This method will gather all the schema information from the annotations at the following locations:
* <ul>
* <li>The method parameter.
* <li>The method parameter class.
* <li>The method parameter parent classes and interfaces.
* </ul>
*
* @param c
* The annotation to look for.
* <br>Valid values:
* <ul>
* <li>{@link Body}
* <li>{@link Header}
* <li>{@link Query}
* <li>{@link FormData}
* <li>{@link Path}
* <li>{@link Response}
* <li>{@link ResponseHeader}
* <li>{@link ResponseBody}
* <li>{@link HasQuery}
* <li>{@link HasFormData}
* </ul>
* @param mpi The Java method parameter.
* @return The schema information about the parameter.
*/
public static HttpPartSchema create(Class<? extends Annotation> c, ParamInfo mpi) {
return create().apply(c, mpi).build();
}
/**
* Finds the schema information for the specified method return.
*
* <p>
* This method will gather all the schema information from the annotations at the following locations:
* <ul>
* <li>The method.
* <li>The method return class.
* <li>The method return parent classes and interfaces.
* </ul>
*
* @param c
* The annotation to look for.
* <br>Valid values:
* <ul>
* <li>{@link Body}
* <li>{@link Header}
* <li>{@link Query}
* <li>{@link FormData}
* <li>{@link Path}
* <li>{@link Response}
* <li>{@link ResponseHeader}
* <li>{@link HasQuery}
* <li>{@link HasFormData}
* </ul>
* @param m
* The Java method with the return type being checked.
* @return The schema information about the parameter.
*/
public static HttpPartSchema create(Class<? extends Annotation> c, Method m) {
return create().apply(c, m).build();
}
/**
* Finds the schema information for the specified class.
*
* <p>
* This method will gather all the schema information from the annotations on the class and all parent classes/interfaces.
*
* @param c
* The annotation to look for.
* <br>Valid values:
* <ul>
* <li>{@link Body}
* <li>{@link Header}
* <li>{@link Query}
* <li>{@link FormData}
* <li>{@link Path}
* <li>{@link Response}
* <li>{@link ResponseHeader}
* <li>{@link HasQuery}
* <li>{@link HasFormData}
* </ul>
* @param t
* The class containing the parameter.
* @return The schema information about the parameter.
*/
public static HttpPartSchema create(Class<? extends Annotation> c, java.lang.reflect.Type t) {
return create().apply(c, t).build();
}
/**
* Shortcut for calling <c>create().type(type);</c>
*
* @param type The schema type value.
* @return A new builder.
*/
public static HttpPartSchemaBuilder create(String type) {
return create().type(type);
}
/**
* Shortcut for calling <c>create().type(type).format(format);</c>
*
* @param type The schema type value.
* @param format The schema format value.
* @return A new builder.
*/
public static HttpPartSchemaBuilder create(String type, String format) {
return create().type(type).format(format);
}
/**
* Finds the schema information on the specified annotation.
*
* @param a
* The annotation to find the schema information on..
* @return The schema information found on the annotation.
*/
public static HttpPartSchema create(Annotation a) {
return create().apply(a).build();
}
/**
* Finds the schema information on the specified annotation.
*
* @param a
* The annotation to find the schema information on..
* @param defaultName The default part name if not specified on the annotation.
* @return The schema information found on the annotation.
*/
public static HttpPartSchema create(Annotation a, String defaultName) {
return create().name(defaultName).apply(a).build();
}
HttpPartSchema(HttpPartSchemaBuilder b) {
this.name = b.name;
this.codes = copy(b.codes);
this._default = b._default;
this._enum = copy(b._enum);
this.properties = build(b.properties, b.noValidate);
this.allowEmptyValue = resolve(b.allowEmptyValue);
this.exclusiveMaximum = resolve(b.exclusiveMaximum);
this.exclusiveMinimum = resolve(b.exclusiveMinimum);
this.required = resolve(b.required);
this.uniqueItems = resolve(b.uniqueItems);
this.skipIfEmpty = resolve(b.skipIfEmpty);
this.collectionFormat = b.collectionFormat;
this.type = b.type;
this.format = b.format;
this.pattern = b.pattern;
this.items = build(b.items, b.noValidate);
this.additionalProperties = build(b.additionalProperties, b.noValidate);
this.maximum = b.maximum;
this.minimum = b.minimum;
this.multipleOf = b.multipleOf;
this.maxItems = b.maxItems;
this.maxLength = b.maxLength;
this.maxProperties = b.maxProperties;
this.minItems = b.minItems;
this.minLength = b.minLength;
this.minProperties = b.minProperties;
this.parser = b.parser;
this.serializer = b.serializer;
// Calculate parse type
Class<?> parsedType = Object.class;
if (type == ARRAY) {
if (items != null)
parsedType = Array.newInstance(items.parsedType.getInnerClass(), 0).getClass();
} else if (type == BOOLEAN) {
parsedType = Boolean.class;
} else if (type == INTEGER) {
if (format == INT64)
parsedType = Long.class;
else
parsedType = Integer.class;
} else if (type == NUMBER) {
if (format == DOUBLE)
parsedType = Double.class;
else
parsedType = Float.class;
} else if (type == STRING) {
if (format == BYTE || format == BINARY || format == BINARY_SPACED)
parsedType = byte[].class;
else if (format == DATE || format == DATE_TIME)
parsedType = Calendar.class;
else
parsedType = String.class;
}
this.parsedType = BeanContext.DEFAULT.getClassMeta(parsedType);
if (b.noValidate)
return;
// Validation.
List<String> errors = new ArrayList<>();
AList<String> notAllowed = AList.create();
boolean invalidFormat = false;
switch (type) {
case STRING: {
notAllowed.appendIf(properties != null, "properties");
notAllowed.appendIf(additionalProperties != null, "additionalProperties");
notAllowed.appendIf(exclusiveMaximum, "exclusiveMaximum");
notAllowed.appendIf(exclusiveMinimum, "exclusiveMinimum");
notAllowed.appendIf(uniqueItems, "uniqueItems");
notAllowed.appendIf(collectionFormat != HttpPartCollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat");
notAllowed.appendIf(items != null, "items");
notAllowed.appendIf(maximum != null, "maximum");
notAllowed.appendIf(minimum != null, "minimum");
notAllowed.appendIf(multipleOf != null, "multipleOf");
notAllowed.appendIf(maxItems != null, "maxItems");
notAllowed.appendIf(minItems != null, "minItems");
notAllowed.appendIf(minProperties != null, "minProperties");
invalidFormat = ! format.isOneOf(HttpPartFormat.BYTE, HttpPartFormat.BINARY, HttpPartFormat.BINARY_SPACED, HttpPartFormat.DATE, HttpPartFormat.DATE_TIME, HttpPartFormat.PASSWORD, HttpPartFormat.UON, HttpPartFormat.NO_FORMAT);
break;
}
case ARRAY: {
notAllowed.appendIf(properties != null, "properties");
notAllowed.appendIf(additionalProperties != null, "additionalProperties");
notAllowed.appendIf(exclusiveMaximum, "exclusiveMaximum");
notAllowed.appendIf(exclusiveMinimum, "exclusiveMinimum");
notAllowed.appendIf(pattern != null, "pattern");
notAllowed.appendIf(maximum != null, "maximum");
notAllowed.appendIf(minimum != null, "minimum");
notAllowed.appendIf(multipleOf != null, "multipleOf");
notAllowed.appendIf(maxLength != null, "maxLength");
notAllowed.appendIf(minLength != null, "minLength");
notAllowed.appendIf(maxProperties != null, "maxProperties");
notAllowed.appendIf(minProperties != null, "minProperties");
invalidFormat = ! format.isOneOf(HttpPartFormat.NO_FORMAT, HttpPartFormat.UON);
break;
}
case BOOLEAN: {
notAllowed.appendIf(! _enum.isEmpty(), "_enum");
notAllowed.appendIf(properties != null, "properties");
notAllowed.appendIf(additionalProperties != null, "additionalProperties");
notAllowed.appendIf(exclusiveMaximum, "exclusiveMaximum");
notAllowed.appendIf(exclusiveMinimum, "exclusiveMinimum");
notAllowed.appendIf(uniqueItems, "uniqueItems");
notAllowed.appendIf(collectionFormat != HttpPartCollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat");
notAllowed.appendIf(pattern != null, "pattern");
notAllowed.appendIf(items != null, "items");
notAllowed.appendIf(maximum != null, "maximum");
notAllowed.appendIf(minimum != null, "minimum");
notAllowed.appendIf(multipleOf != null, "multipleOf");
notAllowed.appendIf(maxItems != null, "maxItems");
notAllowed.appendIf(maxLength != null, "maxLength");
notAllowed.appendIf(maxProperties != null, "maxProperties");
notAllowed.appendIf(minItems != null, "minItems");
notAllowed.appendIf(minLength != null, "minLength");
notAllowed.appendIf(minProperties != null, "minProperties");
invalidFormat = ! format.isOneOf(HttpPartFormat.NO_FORMAT, HttpPartFormat.UON);
break;
}
case FILE: {
break;
}
case INTEGER: {
notAllowed.appendIf(properties != null, "properties");
notAllowed.appendIf(additionalProperties != null, "additionalProperties");
notAllowed.appendIf(uniqueItems, "uniqueItems");
notAllowed.appendIf(collectionFormat != HttpPartCollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat");
notAllowed.appendIf(pattern != null, "pattern");
notAllowed.appendIf(items != null, "items");
notAllowed.appendIf(maxItems != null, "maxItems");
notAllowed.appendIf(maxLength != null, "maxLength");
notAllowed.appendIf(maxProperties != null, "maxProperties");
notAllowed.appendIf(minItems != null, "minItems");
notAllowed.appendIf(minLength != null, "minLength");
notAllowed.appendIf(minProperties != null, "minProperties");
invalidFormat = ! format.isOneOf(HttpPartFormat.NO_FORMAT, HttpPartFormat.UON, HttpPartFormat.INT32, HttpPartFormat.INT64);
break;
}
case NUMBER: {
notAllowed.appendIf(properties != null, "properties");
notAllowed.appendIf(additionalProperties != null, "additionalProperties");
notAllowed.appendIf(uniqueItems, "uniqueItems");
notAllowed.appendIf(collectionFormat != HttpPartCollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat");
notAllowed.appendIf(pattern != null, "pattern");
notAllowed.appendIf(items != null, "items");
notAllowed.appendIf(maxItems != null, "maxItems");
notAllowed.appendIf(maxLength != null, "maxLength");
notAllowed.appendIf(maxProperties != null, "maxProperties");
notAllowed.appendIf(minItems != null, "minItems");
notAllowed.appendIf(minLength != null, "minLength");
notAllowed.appendIf(minProperties != null, "minProperties");
invalidFormat = ! format.isOneOf(HttpPartFormat.NO_FORMAT, HttpPartFormat.UON, HttpPartFormat.FLOAT, HttpPartFormat.DOUBLE);
break;
}
case OBJECT: {
notAllowed.appendIf(exclusiveMaximum, "exclusiveMaximum");
notAllowed.appendIf(exclusiveMinimum, "exclusiveMinimum");
notAllowed.appendIf(uniqueItems, "uniqueItems");
notAllowed.appendIf(pattern != null, "pattern");
notAllowed.appendIf(items != null, "items");
notAllowed.appendIf(maximum != null, "maximum");
notAllowed.appendIf(minimum != null, "minimum");
notAllowed.appendIf(multipleOf != null, "multipleOf");
notAllowed.appendIf(maxItems != null, "maxItems");
notAllowed.appendIf(maxLength != null, "maxLength");
notAllowed.appendIf(minItems != null, "minItems");
notAllowed.appendIf(minLength != null, "minLength");
invalidFormat = ! format.isOneOf(HttpPartFormat.NO_FORMAT);
break;
}
default:
break;
}
if (! notAllowed.isEmpty())
errors.add("Attributes not allow for type='"+type+"': " + StringUtils.join(notAllowed, ","));
if (invalidFormat)
errors.add("Invalid format for type='"+type+"': '"+format+"'");
if (exclusiveMaximum && maximum == null)
errors.add("Cannot specify exclusiveMaximum with maximum.");
if (exclusiveMinimum && minimum == null)
errors.add("Cannot specify exclusiveMinimum with minimum.");
if (required && _default != null)
errors.add("Cannot specify a default value on a required value.");
if (minLength != null && maxLength != null && maxLength < minLength)
errors.add("maxLength cannot be less than minLength.");
if (minimum != null && maximum != null && maximum.doubleValue() < minimum.doubleValue())
errors.add("maximum cannot be less than minimum.");
if (minItems != null && maxItems != null && maxItems < minItems)
errors.add("maxItems cannot be less than minItems.");
if (minProperties != null && maxProperties != null && maxProperties < minProperties)
errors.add("maxProperties cannot be less than minProperties.");
if (minLength != null && minLength < 0)
errors.add("minLength cannot be less than zero.");
if (maxLength != null && maxLength < 0)
errors.add("maxLength cannot be less than zero.");
if (minItems != null && minItems < 0)
errors.add("minItems cannot be less than zero.");
if (maxItems != null && maxItems < 0)
errors.add("maxItems cannot be less than zero.");
if (minProperties != null && minProperties < 0)
errors.add("minProperties cannot be less than zero.");
if (maxProperties != null && maxProperties < 0)
errors.add("maxProperties cannot be less than zero.");
if (type == ARRAY && items != null && items.getType() == OBJECT && (format != UON && format != HttpPartFormat.NO_FORMAT))
errors.add("Cannot define an array of objects unless array format is 'uon'.");
if (! errors.isEmpty())
throw new ContextRuntimeException("Schema specification errors: \n\t" + join(errors, "\n\t"), new Object[0]);
}
/**
* Returns the default parsed type for this schema.
*
* @return The default parsed type for this schema. Never <jk>null</jk>.
*/
public ClassMeta<?> getParsedType() {
return parsedType;
}
/**
* Returns the name of the object described by this schema, for example the query or form parameter name.
*
* @return The name, or <jk>null</jk> if not specified.
* @see HttpPartSchemaBuilder#name(String)
*/
public String getName() {
return name;
}
/**
* Returns the HTTP status code or codes defined on a schema.
*
* @return
* The list of HTTP status codes.
* <br>Never <jk>null</jk>.
* @see HttpPartSchemaBuilder#code(int)
* @see HttpPartSchemaBuilder#codes(int[])
*/
public Set<Integer> getCodes() {
return codes;
}
/**
* Returns the HTTP status code or codes defined on a schema.
*
* @param def The default value if there are no codes defined.
* @return
* The list of HTTP status codes.
* <br>A singleton set containing the default value if the set is empty.
* <br>Never <jk>null</jk>.
* @see HttpPartSchemaBuilder#code(int)
* @see HttpPartSchemaBuilder#codes(int[])
*/
public Set<Integer> getCodes(Integer def) {
return codes.isEmpty() ? Collections.singleton(def) : codes;
}
/**
* Returns the first HTTP status code on a schema.
*
* @param def The default value if there are no codes defined.
* @return
* The list of HTTP status codes.
* <br>A singleton set containing the default value if the set is empty.
* <br>Never <jk>null</jk>.
* @see HttpPartSchemaBuilder#code(int)
* @see HttpPartSchemaBuilder#codes(int[])
*/
public Integer getCode(Integer def) {
return codes.isEmpty() ? def : codes.iterator().next();
}
/**
* Returns the <c>type</c> field of this schema.
*
* @return The <c>type</c> field of this schema, or <jk>null</jk> if not specified.
* @see HttpPartSchemaBuilder#type(String)
*/
public HttpPartDataType getType() {
return type;
}
/**
* Returns the <c>type</c> field of this schema.
*
* @param cm
* The class meta of the object.
* <br>Used to auto-detect the type if the type was not specified.
* @return The format field of this schema, or <jk>null</jk> if not specified.
* @see HttpPartSchemaBuilder#format(String)
*/
public HttpPartDataType getType(ClassMeta<?> cm) {
if (type != HttpPartDataType.NO_TYPE)
return type;
if (cm.isTemporal() || cm.isDateOrCalendar())
return HttpPartDataType.STRING;
if (cm.isNumber()) {
if (cm.isDecimal())
return HttpPartDataType.NUMBER;
return HttpPartDataType.INTEGER;
}
if (cm.isBoolean())
return HttpPartDataType.BOOLEAN;
if (cm.isMapOrBean())
return HttpPartDataType.OBJECT;
if (cm.isCollectionOrArray())
return HttpPartDataType.ARRAY;
return HttpPartDataType.STRING;
}
/**
* Returns the <c>default</c> field of this schema.
*
* @return The default value for this schema, or <jk>null</jk> if not specified.
* @see HttpPartSchemaBuilder#_default(String)
*/
public String getDefault() {
return _default;
}
/**
* Returns the <c>collectionFormat</c> field of this schema.
*
* @return The <c>collectionFormat</c> field of this schema, or <jk>null</jk> if not specified.
* @see HttpPartSchemaBuilder#collectionFormat(String)
*/
public HttpPartCollectionFormat getCollectionFormat() {
return collectionFormat;
}
/**
* Returns the <c>format</c> field of this schema.
*
* @see HttpPartSchemaBuilder#format(String)
* @return The <c>format</c> field of this schema, or <jk>null</jk> if not specified.
*/
public HttpPartFormat getFormat() {
return format;
}
/**
* Returns the <c>format</c> field of this schema.
*
* @param cm
* The class meta of the object.
* <br>Used to auto-detect the format if the format was not specified.
* @return The <c>format</c> field of this schema, or <jk>null</jk> if not specified.
* @see HttpPartSchemaBuilder#format(String)
*/
public HttpPartFormat getFormat(ClassMeta<?> cm) {
if (format != HttpPartFormat.NO_FORMAT)
return format;
if (cm.isNumber()) {
if (cm.isDecimal()) {
if (cm.isDouble())
return HttpPartFormat.DOUBLE;
return HttpPartFormat.FLOAT;
}
if (cm.isLong())
return HttpPartFormat.INT64;
return HttpPartFormat.INT32;
}
return format;
}
/**
* Returns the <c>maximum</c> field of this schema.
*
* @return The schema for child items of the object represented by this schema, or <jk>null</jk> if not defined.
* @see HttpPartSchemaBuilder#items(HttpPartSchemaBuilder)
*/
public HttpPartSchema getItems() {
return items;
}
/**
* Returns the <c>maximum</c> field of this schema.
*
* @return The <c>maximum</c> field of this schema, or <jk>null</jk> if not specified.
* @see HttpPartSchemaBuilder#maximum(Number)
*/
public Number getMaximum() {
return maximum;
}
/**
* Returns the <c>minimum</c> field of this schema.
*
* @return The <c>minimum</c> field of this schema, or <jk>null</jk> if not specified.
* @see HttpPartSchemaBuilder#minimum(Number)
*/
public Number getMinimum() {
return minimum;
}
/**
* Returns the <c>xxx</c> field of this schema.
*
* @return The <c>xxx</c> field of this schema, or <jk>null</jk> if not specified.
* @see HttpPartSchemaBuilder#multipleOf(Number)
*/
public Number getMultipleOf() {
return multipleOf;
}
/**
* Returns the <c>xxx</c> field of this schema.
*
* @return The <c>xxx</c> field of this schema, or <jk>null</jk> if not specified.
* @see HttpPartSchemaBuilder#pattern(String)
*/
public Pattern getPattern() {
return pattern;
}
/**
* Returns the <c>xxx</c> field of this schema.
*
* @return The <c>xxx</c> field of this schema, or <jk>null</jk> if not specified.
* @see HttpPartSchemaBuilder#maxLength(Long)
*/
public Long getMaxLength() {
return maxLength;
}
/**
* Returns the <c>xxx</c> field of this schema.
*
* @return The <c>xxx</c> field of this schema, or <jk>null</jk> if not specified.
* @see HttpPartSchemaBuilder#minLength(Long)
*/
public Long getMinLength() {
return minLength;
}
/**
* Returns the <c>xxx</c> field of this schema.
*
* @return The <c>xxx</c> field of this schema, or <jk>null</jk> if not specified.
* @see HttpPartSchemaBuilder#maxItems(Long)
*/
public Long getMaxItems() {
return maxItems;
}
/**
* Returns the <c>xxx</c> field of this schema.
*
* @return The <c>xxx</c> field of this schema, or <jk>null</jk> if not specified.
* @see HttpPartSchemaBuilder#minItems(Long)
*/
public Long getMinItems() {
return minItems;
}
/**
* Returns the <c>xxx</c> field of this schema.
*
* @return The <c>xxx</c> field of this schema, or <jk>null</jk> if not specified.
* @see HttpPartSchemaBuilder#maxProperties(Long)
*/
public Long getMaxProperties() {
return maxProperties;
}
/**
* Returns the <c>xxx</c> field of this schema.
*
* @return The <c>xxx</c> field of this schema, or <jk>null</jk> if not specified.
* @see HttpPartSchemaBuilder#minProperties(Long)
*/
public Long getMinProperties() {
return minProperties;
}
/**
* Returns the <c>exclusiveMaximum</c> field of this schema.
*
* @return The <c>exclusiveMaximum</c> field of this schema.
* @see HttpPartSchemaBuilder#exclusiveMaximum(Boolean)
*/
public boolean isExclusiveMaximum() {
return exclusiveMaximum;
}
/**
* Returns the <c>exclusiveMinimum</c> field of this schema.
*
* @return The <c>exclusiveMinimum</c> field of this schema.
* @see HttpPartSchemaBuilder#exclusiveMinimum(Boolean)
*/
public boolean isExclusiveMinimum() {
return exclusiveMinimum;
}
/**
* Returns the <c>uniqueItems</c> field of this schema.
*
* @return The <c>uniqueItems</c> field of this schema.
* @see HttpPartSchemaBuilder#uniqueItems(Boolean)
*/
public boolean isUniqueItems() {
return uniqueItems;
}
/**
* Returns the <c>required</c> field of this schema.
*
* @return The <c>required</c> field of this schema.
* @see HttpPartSchemaBuilder#required(Boolean)
*/
public boolean isRequired() {
return required;
}
/**
* Returns the <c>skipIfEmpty</c> field of this schema.
*
* @return The <c>skipIfEmpty</c> field of this schema.
* @see HttpPartSchemaBuilder#skipIfEmpty(Boolean)
*/
public boolean isSkipIfEmpty() {
return skipIfEmpty;
}
/**
* Returns the <c>allowEmptyValue</c> field of this schema.
*
* @return The <c>skipIfEmpty</c> field of this schema.
* @see HttpPartSchemaBuilder#skipIfEmpty(Boolean)
*/
public boolean isAllowEmptyValue() {
return allowEmptyValue;
}
/**
* Returns the <c>enum</c> field of this schema.
*
* @return The <c>enum</c> field of this schema, or <jk>null</jk> if not specified.
* @see HttpPartSchemaBuilder#_enum(Set)
*/
public Set<String> getEnum() {
return _enum;
}
/**
* Returns the <c>parser</c> field of this schema.
*
* @return The <c>parser</c> field of this schema, or <jk>null</jk> if not specified.
* @see HttpPartSchemaBuilder#parser(Class)
*/
public Class<? extends HttpPartParser> getParser() {
return parser;
}
/**
* Returns the <c>serializer</c> field of this schema.
*
* @return The <c>serializer</c> field of this schema, or <jk>null</jk> if not specified.
* @see HttpPartSchemaBuilder#serializer(Class)
*/
public Class<? extends HttpPartSerializer> getSerializer() {
return serializer;
}
/**
* Throws a {@link ParseException} if the specified pre-parsed input does not validate against this schema.
*
* @param in The input.
* @return The same object passed in.
* @throws SchemaValidationException if the specified pre-parsed input does not validate against this schema.
*/
public String validateInput(String in) throws SchemaValidationException {
if (! isValidRequired(in))
throw new SchemaValidationException("No value specified.");
if (in != null) {
if (! isValidAllowEmpty(in))
throw new SchemaValidationException("Empty value not allowed.");
if (! isValidPattern(in))
throw new SchemaValidationException("Value does not match expected pattern. Must match pattern: {0}", pattern.pattern());
if (! isValidEnum(in))
throw new SchemaValidationException("Value does not match one of the expected values. Must be one of the following: {0}", _enum);
if (! isValidMaxLength(in))
throw new SchemaValidationException("Maximum length of value exceeded.");
if (! isValidMinLength(in))
throw new SchemaValidationException("Minimum length of value not met.");
}
return in;
}
/**
* Throws a {@link ParseException} if the specified parsed output does not validate against this schema.
*
* @param o The parsed output.
* @param bc The bean context used to detect POJO types.
* @return The same object passed in.
* @throws SchemaValidationException if the specified parsed output does not validate against this schema.
*/
@SuppressWarnings("rawtypes")
public <T> T validateOutput(T o, BeanContext bc) throws SchemaValidationException {
if (o == null) {
if (! isValidRequired(o))
throw new SchemaValidationException("Required value not provided.");
return o;
}
ClassMeta<?> cm = bc.getClassMetaForObject(o);
switch (getType(cm)) {
case ARRAY: {
if (cm.isArray()) {
if (! isValidMinItems(o))
throw new SchemaValidationException("Minimum number of items not met.");
if (! isValidMaxItems(o))
throw new SchemaValidationException("Maximum number of items exceeded.");
if (! isValidUniqueItems(o))
throw new SchemaValidationException("Duplicate items not allowed.");
HttpPartSchema items = getItems();
if (items != null)
for (int i = 0; i < Array.getLength(o); i++)
items.validateOutput(Array.get(o, i), bc);
} else if (cm.isCollection()) {
Collection<?> c = (Collection<?>)o;
if (! isValidMinItems(c))
throw new SchemaValidationException("Minimum number of items not met.");
if (! isValidMaxItems(c))
throw new SchemaValidationException("Maximum number of items exceeded.");
if (! isValidUniqueItems(c))
throw new SchemaValidationException("Duplicate items not allowed.");
HttpPartSchema items = getItems();
if (items != null)
for (Object o2 : c)
items.validateOutput(o2, bc);
}
break;
}
case INTEGER: {
if (cm.isNumber()) {
Number n = (Number)o;
if (! isValidMinimum(n))
throw new SchemaValidationException("Minimum value not met.");
if (! isValidMaximum(n))
throw new SchemaValidationException("Maximum value exceeded.");
if (! isValidMultipleOf(n))
throw new SchemaValidationException("Multiple-of not met.");
}
break;
}
case NUMBER: {
if (cm.isNumber()) {
Number n = (Number)o;
if (! isValidMinimum(n))
throw new SchemaValidationException("Minimum value not met.");
if (! isValidMaximum(n))
throw new SchemaValidationException("Maximum value exceeded.");
if (! isValidMultipleOf(n))
throw new SchemaValidationException("Multiple-of not met.");
}
break;
}
case OBJECT: {
if (cm.isMapOrBean()) {
Map<?,?> m = cm.isMap() ? (Map<?,?>)o : bc.createSession().toBeanMap(o);
if (! isValidMinProperties(m))
throw new SchemaValidationException("Minimum number of properties not met.");
if (! isValidMaxProperties(m))
throw new SchemaValidationException("Maximum number of properties exceeded.");
for (Map.Entry e : m.entrySet()) {
String key = e.getKey().toString();
HttpPartSchema s2 = getProperty(key);
if (s2 != null)
s2.validateOutput(e.getValue(), bc);
}
} else if (cm.isBean()) {
}
break;
}
case BOOLEAN:
case FILE:
case STRING:
case NO_TYPE:
break;
}
return o;
}
//-----------------------------------------------------------------------------------------------------------------
// Helper methods.
//-----------------------------------------------------------------------------------------------------------------
private boolean isValidRequired(Object x) {
return x != null || ! required;
}
private boolean isValidMinProperties(Map<?,?> x) {
return minProperties == null || x.size() >= minProperties;
}
private boolean isValidMaxProperties(Map<?,?> x) {
return maxProperties == null || x.size() <= maxProperties;
}
private boolean isValidMinimum(Number x) {
if (x instanceof Integer || x instanceof AtomicInteger)
return minimum == null || x.intValue() > minimum.intValue() || (x.intValue() == minimum.intValue() && (! exclusiveMinimum));
if (x instanceof Short || x instanceof Byte)
return minimum == null || x.shortValue() > minimum.shortValue() || (x.intValue() == minimum.shortValue() && (! exclusiveMinimum));
if (x instanceof Long || x instanceof AtomicLong || x instanceof BigInteger)
return minimum == null || x.longValue() > minimum.longValue() || (x.intValue() == minimum.longValue() && (! exclusiveMinimum));
if (x instanceof Float)
return minimum == null || x.floatValue() > minimum.floatValue() || (x.floatValue() == minimum.floatValue() && (! exclusiveMinimum));
if (x instanceof Double || x instanceof BigDecimal)
return minimum == null || x.doubleValue() > minimum.doubleValue() || (x.doubleValue() == minimum.doubleValue() && (! exclusiveMinimum));
return true;
}
private boolean isValidMaximum(Number x) {
if (x instanceof Integer || x instanceof AtomicInteger)
return maximum == null || x.intValue() < maximum.intValue() || (x.intValue() == maximum.intValue() && (! exclusiveMaximum));
if (x instanceof Short || x instanceof Byte)
return maximum == null || x.shortValue() < maximum.shortValue() || (x.intValue() == maximum.shortValue() && (! exclusiveMaximum));
if (x instanceof Long || x instanceof AtomicLong || x instanceof BigInteger)
return maximum == null || x.longValue() < maximum.longValue() || (x.intValue() == maximum.longValue() && (! exclusiveMaximum));
if (x instanceof Float)
return maximum == null || x.floatValue() < maximum.floatValue() || (x.floatValue() == maximum.floatValue() && (! exclusiveMaximum));
if (x instanceof Double || x instanceof BigDecimal)
return maximum == null || x.doubleValue() < maximum.doubleValue() || (x.doubleValue() == maximum.doubleValue() && (! exclusiveMaximum));
return true;
}
private boolean isValidMultipleOf(Number x) {
if (x instanceof Integer || x instanceof AtomicInteger)
return multipleOf == null || x.intValue() % multipleOf.intValue() == 0;
if (x instanceof Short || x instanceof Byte)
return multipleOf == null || x.shortValue() % multipleOf.shortValue() == 0;
if (x instanceof Long || x instanceof AtomicLong || x instanceof BigInteger)
return multipleOf == null || x.longValue() % multipleOf.longValue() == 0;
if (x instanceof Float)
return multipleOf == null || x.floatValue() % multipleOf.floatValue() == 0;
if (x instanceof Double || x instanceof BigDecimal)
return multipleOf == null || x.doubleValue() % multipleOf.doubleValue() == 0;
return true;
}
private boolean isValidAllowEmpty(String x) {
return allowEmptyValue || isNotEmpty(x);
}
private boolean isValidPattern(String x) {
return pattern == null || pattern.matcher(x).matches();
}
private boolean isValidEnum(String x) {
return _enum.isEmpty() || _enum.contains(x);
}
private boolean isValidMinLength(String x) {
return minLength == null || x.length() >= minLength;
}
private boolean isValidMaxLength(String x) {
return maxLength == null || x.length() <= maxLength;
}
private boolean isValidMinItems(Object x) {
return minItems == null || Array.getLength(x) >= minItems;
}
private boolean isValidMaxItems(Object x) {
return maxItems == null || Array.getLength(x) <= maxItems;
}
private boolean isValidUniqueItems(Object x) {
if (uniqueItems) {
Set<Object> s = new HashSet<>();
for (int i = 0; i < Array.getLength(x); i++) {
Object o = Array.get(x, i);
if (! s.add(o))
return false;
}
}
return true;
}
private boolean isValidMinItems(Collection<?> x) {
return minItems == null || x.size() >= minItems;
}
private boolean isValidMaxItems(Collection<?> x) {
return maxItems == null || x.size() <= maxItems;
}
private boolean isValidUniqueItems(Collection<?> x) {
if (uniqueItems && ! (x instanceof Set)) {
Set<Object> s = new HashSet<>();
for (Object o : x)
if (! s.add(o))
return false;
}
return true;
}
/**
* Returns the schema information for the specified property.
*
* @param name The property name.
* @return The schema information for the specified property, or <jk>null</jk> if properties are not defined on this schema.
*/
public HttpPartSchema getProperty(String name) {
if (properties != null) {
HttpPartSchema schema = properties.get(name);
if (schema != null)
return schema;
}
return additionalProperties;
}
/**
* Returns <jk>true</jk> if this schema has properties associated with it.
*
* @return <jk>true</jk> if this schema has properties associated with it.
*/
public boolean hasProperties() {
return properties != null || additionalProperties != null;
}
private static <T> Set<T> copy(Set<T> in) {
return in == null ? Collections.emptySet() : unmodifiableSet(new LinkedHashSet<>(in));
}
private static Map<String,HttpPartSchema> build(Map<String,Object> in, boolean noValidate) {
if (in == null)
return null;
Map<String,HttpPartSchema> m = new LinkedHashMap<>();
for (Map.Entry<String,Object> e : in.entrySet()) {
Object v = e.getValue();
m.put(e.getKey(), build(v, noValidate));
}
return unmodifiableMap(m);
}
private static HttpPartSchema build(Object in, boolean noValidate) {
if (in == null)
return null;
if (in instanceof HttpPartSchema)
return (HttpPartSchema)in;
return ((HttpPartSchemaBuilder)in).noValidate(noValidate).build();
}
//-----------------------------------------------------------------------------------------------------------------
// Helper methods.
//-----------------------------------------------------------------------------------------------------------------
private boolean resolve(Boolean b) {
return b == null ? false : b;
}
final static Set<String> toSet(String[]...s) {
for (String[] ss : s)
if (ss != null && ss.length > 0)
return toSet(joinnl(ss));
return null;
}
final static Set<String> toSet(String s) {
if (isEmpty(s))
return null;
Set<String> set = ASet.of();
try {
for (Object o : StringUtils.parseListOrCdl(s))
set.add(o.toString());
} catch (ParseException e) {
throw new RuntimeException(e);
}
return set;
}
final static Number toNumber(String...s) {
try {
for (String ss : s)
if (isNotEmpty(ss))
return parseNumber(ss, Number.class);
return null;
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
final static OMap toOMap(String[] ss) {
String s = joinnl(ss);
if (s.isEmpty())
return null;
if (! isJsonObject(s, true))
s = "{" + s + "}";
try {
return OMap.ofJson(s);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
@Override
public String toString() {
try {
OMap m = new OMap()
.appendSkipEmpty("name", name)
.appendSkipEmpty("type", type)
.appendSkipEmpty("format", format)
.appendSkipEmpty("codes", codes)
.appendSkipEmpty("default", _default)
.appendSkipEmpty("enum", _enum)
.appendSkipEmpty("properties", properties)
.appendSkipFalse("allowEmptyValue", allowEmptyValue)
.appendSkipFalse("exclusiveMaximum", exclusiveMaximum)
.appendSkipFalse("exclusiveMinimum", exclusiveMinimum)
.appendSkipFalse("required", required)
.appendSkipFalse("uniqueItems", uniqueItems)
.appendSkipFalse("skipIfEmpty", skipIfEmpty)
.appendIf(collectionFormat != HttpPartCollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat", collectionFormat)
.appendSkipEmpty("pattern", pattern)
.appendSkipNull("items", items)
.appendSkipNull("additionalProperties", additionalProperties)
.appendSkipMinusOne("maximum", maximum)
.appendSkipMinusOne("minimum", minimum)
.appendSkipMinusOne("multipleOf", multipleOf)
.appendSkipMinusOne("maxLength", maxLength)
.appendSkipMinusOne("minLength", minLength)
.appendSkipMinusOne("maxItems", maxItems)
.appendSkipMinusOne("minItems", minItems)
.appendSkipMinusOne("maxProperties", maxProperties)
.appendSkipMinusOne("minProperties", minProperties)
.append("parsedType", parsedType)
;
return m.toString();
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
}