All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.juneau.httppart.HttpPartSchema Maven / Gradle / Ivy

There is a newer version: 9.0.1
Show newest version
// ***************************************************************************************************************************
// * 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:
*
    *
*/ 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 parser; final Class 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 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 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 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 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 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 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 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 null 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 true if this schema has properties associated with it. * * @return true if this schema has properties associated with it. */ public boolean hasProperties() { return properties != null || additionalProperties != null; } private static Set copy(Set in) { return in == null ? Collections.EMPTY_SET : unmodifiableSet(new LinkedHashSet<>(in)); } private static Map build(Map in, boolean noValidate) { if (in == null) return null; Map m = new LinkedHashMap<>(); for (Map.Entry e : in.entrySet()) m.put(e.getKey(), e.getValue().noValidate(noValidate).build()); return unmodifiableMap(m); } private static HttpPartSchema build(HttpPartSchemaBuilder in, boolean noValidate) { return in == null ? null : in.noValidate(noValidate).build(); } //----------------------------------------------------------------------------------------------------------------- // Helper methods. //----------------------------------------------------------------------------------------------------------------- private boolean resolve(Boolean b) { return b == null ? false : b; } final static Set toSet(String[] s) { return toSet(joinnl(s)); } final static Set toSet(String s) { if (isEmpty(s)) return null; Set set = new ASet<>(); 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 { if (isNotEmpty(s)) return parseNumber(s, Number.class); return null; } catch (ParseException e) { throw new RuntimeException(e); } } final static ObjectMap toObjectMap(String[] ss) { String s = joinnl(ss); if (s.isEmpty()) return null; if (! isObjectMap(s, true)) s = "{" + s + "}"; try { return new ObjectMap(s); } catch (ParseException e) { throw new RuntimeException(e); } } @Override public String toString() { try { ObjectMap m = new ObjectMap() .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 != CollectionFormat.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 ""; } } }