org.apache.juneau.httppart.HttpPartSchema Maven / Gradle / Ivy
// ***************************************************************************************************************************
// * 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.HttpPartSchema.Format.*;
import static org.apache.juneau.httppart.HttpPartSchema.Type.*;
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.http.annotation.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.utils.*;
/**
* Represents an OpenAPI schema definition.
*
*
* The schema definition can be applied to any HTTP parts such as bodies, headers, query/form parameters, and URL path parts.
*
The API is generic enough to apply to any path part although some attributes may only applicable for certain parts.
*
*
* Schema objects are created via builders instantiated through the {@link #create()} method.
*
*
* This class is thread safe and reusable.
*
*
See Also:
*
* - {@doc juneau-marshall.OpenApiDetails}
*
*/
public class HttpPartSchema {
//-------------------------------------------------------------------------------------------------------------------
// Predefined instances
//-------------------------------------------------------------------------------------------------------------------
/** Reusable instance of this object, all default settings. */
public static final HttpPartSchema DEFAULT = HttpPartSchema.create().allowEmptyValue(true).build();
final String name;
final Set codes;
final String _default;
final Set _enum;
final Map properties;
final boolean allowEmptyValue, exclusiveMaximum, exclusiveMinimum, required, uniqueItems, skipIfEmpty;
final CollectionFormat collectionFormat;
final Type type;
final Format 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();
}
/**
* Finds the schema information for the specified method parameter.
*
*
* This method will gather all the schema information from the annotations at the following locations:
*
* - The method parameter.
*
- The method parameter class.
*
- The method parameter parent classes and interfaces.
*
*
* @param c
* The annotation to look for.
*
Valid values:
*
* - {@link Body}
*
- {@link Header}
*
- {@link Query}
*
- {@link FormData}
*
- {@link Path}
*
- {@link Response}
*
- {@link ResponseHeader}
*
- {@link ResponseBody}
*
- {@link HasQuery}
*
- {@link HasFormData}
*
* @param m
* The Java method containing the parameter.
* @param mi
* The index of the parameter on the method.
* @return The schema information about the parameter.
*/
public static HttpPartSchema create(Class extends Annotation> c, Method m, int mi) {
return create().apply(c, m, mi).build();
}
/**
* Finds the schema information for the specified method return.
*
*
* This method will gather all the schema information from the annotations at the following locations:
*
* - The method.
*
- The method return class.
*
- The method return parent classes and interfaces.
*
*
* @param c
* The annotation to look for.
*
Valid values:
*
* - {@link Body}
*
- {@link Header}
*
- {@link Query}
*
- {@link FormData}
*
- {@link Path}
*
- {@link Response}
*
- {@link ResponseHeader}
*
- {@link HasQuery}
*
- {@link HasFormData}
*
* @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.
*
*
* 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.
*
Valid values:
*
* - {@link Body}
*
- {@link Header}
*
- {@link Query}
*
- {@link FormData}
*
- {@link Path}
*
- {@link Response}
*
- {@link ResponseHeader}
*
- {@link HasQuery}
*
- {@link HasFormData}
*
* @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 create().type(type);
*
* @param type The schema type value.
* @return A new builder.
*/
public static HttpPartSchemaBuilder create(String type) {
return create().type(type);
}
/**
* Shortcut for calling create().type(type).format(format);
*
* @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 errors = new ArrayList<>();
AList notAllowed = new AList<>();
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 != CollectionFormat.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(Format.BYTE, Format.BINARY, Format.BINARY_SPACED, Format.DATE, Format.DATE_TIME, Format.PASSWORD, Format.UON, Format.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(Format.NO_FORMAT, Format.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 != CollectionFormat.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(Format.NO_FORMAT, Format.UON);
break;
}
case FILE: {
break;
}
case INTEGER: {
notAllowed.appendIf(properties != null, "properties");
notAllowed.appendIf(additionalProperties != null, "additionalProperties");
notAllowed.appendIf(uniqueItems, "uniqueItems");
notAllowed.appendIf(collectionFormat != CollectionFormat.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(Format.NO_FORMAT, Format.UON, Format.INT32, Format.INT64);
break;
}
case NUMBER: {
notAllowed.appendIf(properties != null, "properties");
notAllowed.appendIf(additionalProperties != null, "additionalProperties");
notAllowed.appendIf(uniqueItems, "uniqueItems");
notAllowed.appendIf(collectionFormat != CollectionFormat.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(Format.NO_FORMAT, Format.UON, Format.FLOAT, Format.DOUBLE);
break;
}
case OBJECT: {
notAllowed.appendIf(exclusiveMaximum, "exclusiveMaximum");
notAllowed.appendIf(exclusiveMinimum, "exclusiveMinimum");
notAllowed.appendIf(uniqueItems, "uniqueItems");
notAllowed.appendIf(collectionFormat != CollectionFormat.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(minItems != null, "minItems");
notAllowed.appendIf(minLength != null, "minLength");
invalidFormat = ! format.isOneOf(Format.NO_FORMAT, Format.UON);
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 != Format.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"));
}
/**
* Valid values for the collectionFormat
field.
*/
public static enum CollectionFormat {
/**
* Comma-separated values (e.g. "foo,bar" ).
*/
CSV,
/**
* Space-separated values (e.g. "foo bar" ).
*/
SSV,
/**
* Tab-separated values (e.g. "foo\tbar" ).
*/
TSV,
/**
* Pipe-separated values (e.g. "foo|bar" ).
*/
PIPES,
/**
* Corresponds to multiple parameter instances instead of multiple values for a single instance (e.g. "foo=bar&foo=baz" ).
*/
MULTI,
/**
* UON notation (e.g. "@(foo,bar)" ).
*/
UON,
/**
* Not specified.
*/
NO_COLLECTION_FORMAT;
static CollectionFormat fromString(String value) {
return valueOf(value.toUpperCase());
}
@Override
public String toString() {
return name().toLowerCase();
}
}
/**
* Valid values for the type
field.
*/
public static enum Type {
/**
* String.
*/
STRING,
/**
* Floating point number.
*/
NUMBER,
/**
* Decimal number.
*/
INTEGER,
/**
* Boolean.
*/
BOOLEAN,
/**
* Array or collection.
*/
ARRAY,
/**
* Map or bean.
*/
OBJECT,
/**
* File.
*/
FILE,
/**
* Not specified.
*/
NO_TYPE;
static Type fromString(String value) {
return valueOf(value.toUpperCase());
}
@Override
public String toString() {
return name().toLowerCase();
}
}
/**
* Valid values for the format
field.
*/
public static enum Format {
/**
* Signed 32 bits.
*/
INT32,
/**
* Signed 64 bits.
*/
INT64,
/**
* 32-bit floating point number.
*/
FLOAT,
/**
* 64-bit floating point number.
*/
DOUBLE,
/**
* BASE-64 encoded characters.
*/
BYTE,
/**
* Hexadecimal encoded octets (e.g. "00FF" ).
*/
BINARY,
/**
* Spaced-separated hexadecimal encoded octets (e.g. "00 FF" ).
*/
BINARY_SPACED,
/**
* An RFC3339 full-date.
*/
DATE,
/**
* An RFC3339 date-time.
*/
DATE_TIME,
/**
* Used to hint UIs the input needs to be obscured.
*/
PASSWORD,
/**
* UON notation (e.g. "(foo=bar,baz=@(qux,123))" ).
*/
UON,
/**
* Not specified.
*/
NO_FORMAT;
static Format fromString(String value) {
value = value.toUpperCase().replace('-','_');
return valueOf(value);
}
@Override
public String toString() {
String s = name().toLowerCase().replace('_','-');
return s;
}
/**
* Returns true if this format is in the provided list.
*
* @param list The list of formats to check against.
* @return true if this format is in the provided list.
*/
public boolean isOneOf(Format...list) {
for (Format ff : list)
if (this == ff)
return true;
return false;
}
}
/**
* Returns the default parsed type for this schema.
*
* @return The default parsed type for this schema. Never null .
*/
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 null 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.
*
Never null .
* @see HttpPartSchemaBuilder#code(int)
* @see HttpPartSchemaBuilder#codes(int[])
*/
public Set 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.
*
A singleton set containing the default value if the set is empty.
*
Never null .
* @see HttpPartSchemaBuilder#code(int)
* @see HttpPartSchemaBuilder#codes(int[])
*/
public Set 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.
*
A singleton set containing the default value if the set is empty.
*
Never null .
* @see HttpPartSchemaBuilder#code(int)
* @see HttpPartSchemaBuilder#codes(int[])
*/
public Integer getCode(Integer def) {
return codes.isEmpty() ? def : codes.iterator().next();
}
/**
* Returns the type
field of this schema.
*
* @return The type
field of this schema, or null if not specified.
* @see HttpPartSchemaBuilder#type(String)
*/
public Type getType() {
return type;
}
/**
* Returns the type
field of this schema.
*
* @param cm
* The class meta of the object.
*
Used to auto-detect the type if the type was not specified.
* @return The format field of this schema, or null if not specified.
* @see HttpPartSchemaBuilder#format(String)
*/
public Type getType(ClassMeta> cm) {
if (type != Type.NO_TYPE)
return type;
if (cm.isMapOrBean())
return Type.OBJECT;
if (cm.isCollectionOrArray())
return Type.ARRAY;
if (cm.isNumber()) {
if (cm.isDecimal())
return Type.NUMBER;
return Type.INTEGER;
}
if (cm.isBoolean())
return Type.BOOLEAN;
return Type.STRING;
}
/**
* Returns the default
field of this schema.
*
* @return The default value for this schema, or null if not specified.
* @see HttpPartSchemaBuilder#_default(String)
*/
public String getDefault() {
return _default;
}
/**
* Returns the collectionFormat
field of this schema.
*
* @return The collectionFormat
field of this schema, or null if not specified.
* @see HttpPartSchemaBuilder#collectionFormat(String)
*/
public CollectionFormat getCollectionFormat() {
return collectionFormat;
}
/**
* Returns the format
field of this schema.
*
* @see HttpPartSchemaBuilder#format(String)
* @return The format
field of this schema, or null if not specified.
*/
public Format getFormat() {
return format;
}
/**
* Returns the format
field of this schema.
*
* @param cm
* The class meta of the object.
*
Used to auto-detect the format if the format was not specified.
* @return The format
field of this schema, or null if not specified.
* @see HttpPartSchemaBuilder#format(String)
*/
public Format getFormat(ClassMeta> cm) {
if (format != Format.NO_FORMAT)
return format;
if (cm.isNumber()) {
if (cm.isDecimal()) {
if (cm.isDouble())
return Format.DOUBLE;
return Format.FLOAT;
}
if (cm.isLong())
return Format.INT64;
return Format.INT32;
}
return format;
}
/**
* Returns the maximum
field of this schema.
*
* @return The schema for child items of the object represented by this schema, or null if not defined.
* @see HttpPartSchemaBuilder#items(HttpPartSchemaBuilder)
*/
public HttpPartSchema getItems() {
return items;
}
/**
* Returns the maximum
field of this schema.
*
* @return The maximum
field of this schema, or null if not specified.
* @see HttpPartSchemaBuilder#maximum(Number)
*/
public Number getMaximum() {
return maximum;
}
/**
* Returns the minimum
field of this schema.
*
* @return The minimum
field of this schema, or null if not specified.
* @see HttpPartSchemaBuilder#minimum(Number)
*/
public Number getMinimum() {
return minimum;
}
/**
* Returns the xxx
field of this schema.
*
* @return The xxx
field of this schema, or null if not specified.
* @see HttpPartSchemaBuilder#multipleOf(Number)
*/
public Number getMultipleOf() {
return multipleOf;
}
/**
* Returns the xxx
field of this schema.
*
* @return The xxx
field of this schema, or null if not specified.
* @see HttpPartSchemaBuilder#pattern(String)
*/
public Pattern getPattern() {
return pattern;
}
/**
* Returns the xxx
field of this schema.
*
* @return The xxx
field of this schema, or null if not specified.
* @see HttpPartSchemaBuilder#maxLength(Long)
*/
public Long getMaxLength() {
return maxLength;
}
/**
* Returns the xxx
field of this schema.
*
* @return The xxx
field of this schema, or null if not specified.
* @see HttpPartSchemaBuilder#minLength(Long)
*/
public Long getMinLength() {
return minLength;
}
/**
* Returns the xxx
field of this schema.
*
* @return The xxx
field of this schema, or null if not specified.
* @see HttpPartSchemaBuilder#maxItems(Long)
*/
public Long getMaxItems() {
return maxItems;
}
/**
* Returns the xxx
field of this schema.
*
* @return The xxx
field of this schema, or null if not specified.
* @see HttpPartSchemaBuilder#minItems(Long)
*/
public Long getMinItems() {
return minItems;
}
/**
* Returns the xxx
field of this schema.
*
* @return The xxx
field of this schema, or null if not specified.
* @see HttpPartSchemaBuilder#maxProperties(Long)
*/
public Long getMaxProperties() {
return maxProperties;
}
/**
* Returns the xxx
field of this schema.
*
* @return The xxx
field of this schema, or null if not specified.
* @see HttpPartSchemaBuilder#minProperties(Long)
*/
public Long getMinProperties() {
return minProperties;
}
/**
* Returns the exclusiveMaximum
field of this schema.
*
* @return The exclusiveMaximum
field of this schema.
* @see HttpPartSchemaBuilder#exclusiveMaximum(Boolean)
*/
public boolean isExclusiveMaximum() {
return exclusiveMaximum;
}
/**
* Returns the exclusiveMinimum
field of this schema.
*
* @return The exclusiveMinimum
field of this schema.
* @see HttpPartSchemaBuilder#exclusiveMinimum(Boolean)
*/
public boolean isExclusiveMinimum() {
return exclusiveMinimum;
}
/**
* Returns the uniqueItems
field of this schema.
*
* @return The uniqueItems
field of this schema.
* @see HttpPartSchemaBuilder#uniqueItems(Boolean)
*/
public boolean isUniqueItems() {
return uniqueItems;
}
/**
* Returns the required
field of this schema.
*
* @return The required
field of this schema.
* @see HttpPartSchemaBuilder#required(Boolean)
*/
public boolean isRequired() {
return required;
}
/**
* Returns the skipIfEmpty
field of this schema.
*
* @return The skipIfEmpty
field of this schema.
* @see HttpPartSchemaBuilder#skipIfEmpty(Boolean)
*/
public boolean isSkipIfEmpty() {
return skipIfEmpty;
}
/**
* Returns the allowEmptyValue
field of this schema.
*
* @return The skipIfEmpty
field of this schema.
* @see HttpPartSchemaBuilder#skipIfEmpty(Boolean)
*/
public boolean isAllowEmptyValue() {
return allowEmptyValue;
}
/**
* Returns the enum
field of this schema.
*
* @return The enum
field of this schema, or null if not specified.
* @see HttpPartSchemaBuilder#_enum(Set)
*/
public Set getEnum() {
return _enum;
}
/**
* Returns the parser
field of this schema.
*
* @return The parser
field of this schema, or null if not specified.
* @see HttpPartSchemaBuilder#parser(Class)
*/
public Class extends HttpPartParser> getParser() {
return parser;
}
/**
* Returns the serializer
field of this schema.
*
* @return The serializer
field of this schema, or null 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 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