575 lines
21 KiB
Java
575 lines
21 KiB
Java
/*
|
|
* 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.
|
|
*
|
|
* $Header:$
|
|
*/
|
|
package org.apache.beehive.controls.runtime.bean;
|
|
|
|
import java.lang.annotation.Annotation;
|
|
import java.lang.reflect.Method;
|
|
import java.net.MalformedURLException;
|
|
import java.net.URI;
|
|
import java.net.URL;
|
|
import java.text.ParseException;
|
|
import java.text.SimpleDateFormat;
|
|
import java.text.ParsePosition;
|
|
import java.util.Date;
|
|
|
|
import org.apache.beehive.controls.api.bean.AnnotationMemberTypes;
|
|
import org.apache.beehive.controls.api.bean.AnnotationConstraints.MembershipRule;
|
|
import org.apache.beehive.controls.api.bean.AnnotationConstraints.MembershipRuleValues;
|
|
import org.apache.beehive.controls.api.properties.PropertyKey;
|
|
|
|
/**
|
|
* This class offers methods for validating values assigned to a control property.
|
|
* The validation process will ensure
|
|
* 1. The value is appropriate for the property's property type
|
|
* 2. The value satisfies the constraints defined on the property type
|
|
* 3. The value satisfies the constraints defined on the property set that the property is defined in.
|
|
* Refer to {@link org.apache.beehive.controls.api.bean.AnnotationMemberTypes AnnotationMemberTypes} and
|
|
* {@link org.apache.beehive.controls.api.bean.AnnotationConstraints AnnotationConstraints} for more
|
|
* information on property constraints.
|
|
*/
|
|
public class AnnotationConstraintValidator
|
|
{
|
|
|
|
public AnnotationConstraintValidator()
|
|
{
|
|
super();
|
|
}
|
|
|
|
/**
|
|
* This method ensures that any control property value assignment satisfies
|
|
* all property constraints. This method should be called by control
|
|
* property setters to ensure values assigned to properties at runtime are
|
|
* validated.
|
|
*
|
|
* @param key
|
|
* The property that the specified key is assigned to
|
|
* @param value
|
|
* The value assigned to the specified property key
|
|
* @throws IllegalArgumentException
|
|
* when the value assigned to the specified property key does
|
|
* not satisfy a property constraint.
|
|
*/
|
|
public static void validate(PropertyKey key, Object value)
|
|
throws IllegalArgumentException
|
|
{
|
|
validate(key.getAnnotations(), value);
|
|
}
|
|
|
|
/**
|
|
* This method ensures the membership constraints defined on a property set
|
|
* is satisfied.
|
|
*
|
|
* @param propertySet the property set to validate
|
|
*/
|
|
public static void validateMembership(Annotation propertySet)
|
|
{
|
|
Class c = propertySet.annotationType();
|
|
MembershipRule rule = (MembershipRule) c.getAnnotation(MembershipRule.class);
|
|
if (rule == null)
|
|
return;
|
|
MembershipRuleValues ruleValue = rule.value();
|
|
String[] memberNames = rule.memberNames();
|
|
Method[] members = getMembers(c, memberNames);
|
|
int i = getNumOfMembersSet(propertySet, members);
|
|
if (ruleValue == MembershipRuleValues.ALL_IF_ANY)
|
|
{
|
|
if (i != 0 && i != members.length)
|
|
throw new IllegalArgumentException("The membership rule on " + propertySet.toString() +
|
|
" is not satisfied. Either all members must be set or none is set");
|
|
}
|
|
else if (ruleValue == MembershipRuleValues.EXACTLY_ONE)
|
|
{
|
|
if (i != 1)
|
|
throw new IllegalArgumentException("The membership rule on " + propertySet.toString() +
|
|
" is not satisfied. Exactly one member must be set");
|
|
}
|
|
else if (ruleValue == MembershipRuleValues.AT_LEAST_ONE)
|
|
{
|
|
if (i < 1)
|
|
throw new IllegalArgumentException("The membership rule on " + propertySet.toString() +
|
|
" is not satisfied. At least one member must be set");
|
|
}
|
|
else if (ruleValue == MembershipRuleValues.AT_MOST_ONE)
|
|
{
|
|
if (i > 1)
|
|
throw new IllegalArgumentException("The membership rule on " + propertySet.toString() +
|
|
" is not satisfied. At most one member may be set");
|
|
}
|
|
}
|
|
|
|
private static Method[] getMembers(Class<? extends Annotation> c, String[] memberNames)
|
|
{
|
|
Method[] methods = null;
|
|
if (memberNames == null || memberNames.length == 0)
|
|
{
|
|
methods = c.getDeclaredMethods();
|
|
}
|
|
else
|
|
{
|
|
methods = new Method[memberNames.length];
|
|
for (int i = 0; i < memberNames.length; i++)
|
|
{
|
|
try
|
|
{
|
|
methods[i] = c.getMethod(memberNames[i], (Class[]) null);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// method is not found, so the member is ignored.
|
|
}
|
|
}
|
|
}
|
|
return methods;
|
|
}
|
|
|
|
private static int getNumOfMembersSet(Annotation propertySet,
|
|
Method[] members)
|
|
{
|
|
int num = 0;
|
|
for (Method m : members)
|
|
{
|
|
Class returnType = m.getReturnType();
|
|
Object o = null;
|
|
try
|
|
{
|
|
o = m.invoke(propertySet, (Object[]) null);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// This should never happen.
|
|
throw new RuntimeException(e);
|
|
}
|
|
|
|
if ((returnType == String.class && !((String) o)
|
|
.equals(AnnotationMemberTypes.OPTIONAL_STRING))
|
|
|| (returnType == int.class && ((Integer) o).intValue() != AnnotationMemberTypes.OPTIONAL_INT)
|
|
|| (returnType == short.class && ((Short) o)
|
|
.shortValue() != AnnotationMemberTypes.OPTIONAL_SHORT)
|
|
|| (returnType == long.class && ((Long) o).longValue() != AnnotationMemberTypes.OPTIONAL_LONG)
|
|
|| (returnType == float.class && ((Float) o)
|
|
.floatValue() != AnnotationMemberTypes.OPTIONAL_FLOAT)
|
|
|| (returnType == double.class && ((Double) o)
|
|
.doubleValue() != AnnotationMemberTypes.OPTIONAL_DOUBLE)
|
|
|| (returnType == char.class && ((Character) o)
|
|
.charValue() != AnnotationMemberTypes.OPTIONAL_CHAR)
|
|
|| (returnType == byte.class && ((Byte) o).byteValue() != AnnotationMemberTypes.OPTIONAL_BYTE)
|
|
|| (returnType == boolean.class && !((Boolean) o)
|
|
.booleanValue()))
|
|
num++;
|
|
}
|
|
return num;
|
|
}
|
|
|
|
protected static synchronized void validate(Annotation[] annotations,
|
|
Object value) throws IllegalArgumentException
|
|
{
|
|
|
|
// Determine if the member is optional. This is done in a separate loop
|
|
// because a control property may have multiple constraints and the
|
|
// optional
|
|
// annotation may be declared after another constraint annotation.
|
|
boolean optional = false;
|
|
for (Annotation a : annotations)
|
|
{
|
|
if (a instanceof AnnotationMemberTypes.Optional)
|
|
{
|
|
optional = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (Annotation a : annotations)
|
|
{
|
|
if (a instanceof AnnotationMemberTypes.Text)
|
|
validateText((AnnotationMemberTypes.Text) a, value, optional);
|
|
else if (a instanceof AnnotationMemberTypes.Decimal)
|
|
validateDecimal((AnnotationMemberTypes.Decimal) a, value,
|
|
optional);
|
|
else if (a instanceof AnnotationMemberTypes.Int)
|
|
validateInt((AnnotationMemberTypes.Int) a, value, optional);
|
|
else if (a instanceof AnnotationMemberTypes.Date)
|
|
validateDate((AnnotationMemberTypes.Date) a, value, optional);
|
|
else if (a instanceof AnnotationMemberTypes.FilePath)
|
|
validateFilePath((AnnotationMemberTypes.FilePath) a, value,
|
|
optional);
|
|
else if (a instanceof AnnotationMemberTypes.JndiName)
|
|
validateJndiName((AnnotationMemberTypes.JndiName) a, value,
|
|
optional);
|
|
else if (a instanceof AnnotationMemberTypes.QName)
|
|
validateQName((AnnotationMemberTypes.QName) a, value, optional);
|
|
else if (a instanceof AnnotationMemberTypes.URI)
|
|
validateURI((AnnotationMemberTypes.URI) a, value, optional);
|
|
else if (a instanceof AnnotationMemberTypes.URL)
|
|
validateURL((AnnotationMemberTypes.URL) a, value, optional);
|
|
else if (a instanceof AnnotationMemberTypes.URN)
|
|
validateURN((AnnotationMemberTypes.URN) a, value, optional);
|
|
else if (a instanceof AnnotationMemberTypes.XML)
|
|
validateXML((AnnotationMemberTypes.XML) a, value, optional);
|
|
}
|
|
}
|
|
|
|
private static void validateXML(AnnotationMemberTypes.XML a, Object value,
|
|
boolean optional)
|
|
{
|
|
}
|
|
|
|
private static void validateURN(AnnotationMemberTypes.URN a, Object value,
|
|
boolean optional)
|
|
{
|
|
if (optional
|
|
&& (value == null || value
|
|
.equals(AnnotationMemberTypes.OPTIONAL_STRING)))
|
|
return;
|
|
|
|
if (!(value instanceof String))
|
|
{
|
|
error("The value, "
|
|
+ value
|
|
+ ", assigned to an URN property must be of type java.lang.String.");
|
|
}
|
|
|
|
URI.create((String) value);
|
|
}
|
|
|
|
private static void validateURL(AnnotationMemberTypes.URL a, Object value,
|
|
boolean optional)
|
|
{
|
|
if (optional
|
|
&& (value == null || value
|
|
.equals(AnnotationMemberTypes.OPTIONAL_STRING)))
|
|
return;
|
|
|
|
if (!(value instanceof String))
|
|
{
|
|
error("The value, "
|
|
+ value
|
|
+ ", assigned to an URL property must be of type java.lang.String.");
|
|
}
|
|
|
|
try
|
|
{
|
|
new URL((String) value);
|
|
}
|
|
catch (MalformedURLException mue)
|
|
{
|
|
error("The value, " + value
|
|
+ ", assigned to the URL property is a malformed URL.", mue);
|
|
}
|
|
}
|
|
|
|
private static void validateURI(AnnotationMemberTypes.URI a, Object value, boolean optional)
|
|
{
|
|
if (optional
|
|
&& (value == null || value
|
|
.equals(AnnotationMemberTypes.OPTIONAL_STRING)))
|
|
return;
|
|
|
|
if (!(value instanceof String))
|
|
{
|
|
error("The value, "
|
|
+ value
|
|
+ ", assigned to an URI property must be of type java.lang.String.");
|
|
}
|
|
|
|
URI.create((String) value);
|
|
}
|
|
|
|
private static void validateQName(AnnotationMemberTypes.QName a, Object value, boolean optional)
|
|
{
|
|
}
|
|
|
|
private static void validateJndiName(AnnotationMemberTypes.JndiName a, Object value, boolean optional)
|
|
{
|
|
}
|
|
|
|
private static void validateFilePath(AnnotationMemberTypes.FilePath a, Object value, boolean optional)
|
|
{
|
|
if (optional
|
|
&& (value == null || value
|
|
.equals(AnnotationMemberTypes.OPTIONAL_STRING)))
|
|
return;
|
|
|
|
if (!(value instanceof String))
|
|
{
|
|
error("The value, "
|
|
+ value
|
|
+ ", assigned to a FilePath property must be of type java.lang.String.");
|
|
}
|
|
|
|
//Temporarily commenting out the following check on FilePath until
|
|
//an agreement is reached on what is a valid FilePath.
|
|
//
|
|
// File file = new File((String) value);
|
|
// if (!file.isFile() || !file.canRead())
|
|
// {
|
|
// error("The value, "
|
|
// + value
|
|
// + ", assigned to a FilePath property must be a readable file.");
|
|
// }
|
|
|
|
}
|
|
|
|
private static void validateDate(AnnotationMemberTypes.Date a, Object value, boolean optional) {
|
|
|
|
if (optional && (value == null || value.equals(AnnotationMemberTypes.OPTIONAL_STRING)))
|
|
return;
|
|
|
|
if (!(value instanceof String))
|
|
error("The value, "
|
|
+ value
|
|
+ ", assigned to a date property must be of type java.lang.String.");
|
|
|
|
String format = a.format();
|
|
Date date = null;
|
|
try {
|
|
date = parseDate(format , (String)value);
|
|
} catch (ParseException pe) {
|
|
error("The value, "
|
|
+ value
|
|
+ ", assigned to a date property is not in the specified format of: "
|
|
+ format);
|
|
}
|
|
|
|
String minValue = a.minValue();
|
|
if (minValue != null && minValue.length() > 0) {
|
|
|
|
Date minDate = null;
|
|
try {
|
|
minDate = parseDate(format, a.minValue());
|
|
} catch (ParseException pe) {
|
|
error("The value, "
|
|
+ minValue
|
|
+ ", assigned to minValue date constraint property is not in the specified format of: "
|
|
+ format);
|
|
}
|
|
|
|
if (minDate.compareTo(date) > 0) {
|
|
error("The value, "
|
|
+ value
|
|
+ ", assigned to a date property is earlier than the earliest date allowed: "
|
|
+ minValue);
|
|
}
|
|
}
|
|
|
|
String maxValue = a.maxValue();
|
|
if (maxValue != null && maxValue.length() > 0) {
|
|
|
|
Date maxDate = null;
|
|
try {
|
|
maxDate = parseDate(format, a.maxValue());
|
|
} catch (ParseException pe) {
|
|
error("The value, "
|
|
+ maxValue
|
|
+ ", assigned to maxValue date constraint property is not in the specified format of: "
|
|
+ format);
|
|
}
|
|
|
|
if (maxDate.compareTo(date) < 0) {
|
|
error("The date, "
|
|
+ value
|
|
+ ", assigned to a date property is later than the latest date allowed: "
|
|
+ maxValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse a date value into the specified format. Pay special attention to the case of the value
|
|
* having trailing characters, ex. 12/02/2005xx which will not cause the parse of the date to fail
|
|
* but should be still treated as an error for our purposes.
|
|
*
|
|
* @param format Format string for the date.
|
|
* @param value A String containing the date value to parse.
|
|
* @return A Date instance if the parse was successful.
|
|
* @throws ParseException If the value is not a valid date.
|
|
*/
|
|
public static Date parseDate(String format, String value)
|
|
throws ParseException {
|
|
|
|
SimpleDateFormat sdFormat = new SimpleDateFormat(format);
|
|
sdFormat.setLenient(false);
|
|
ParsePosition pp = new ParsePosition(0);
|
|
Date d = sdFormat.parse(value, pp);
|
|
|
|
/*
|
|
a date value such as: 12/01/2005x will not cause a parse error,
|
|
use the parse position to detect this case.
|
|
*/
|
|
if (d == null || pp.getIndex() < value.length())
|
|
throw new ParseException("Parsing date value, "
|
|
+ value
|
|
+ ", failed at index " + pp.getIndex(), pp.getIndex());
|
|
else return d;
|
|
}
|
|
|
|
/**
|
|
* @param value
|
|
*/
|
|
private static void validateInt(AnnotationMemberTypes.Int a, Object value, boolean optional) {
|
|
if (optional
|
|
&& (value == null ||
|
|
value.equals(AnnotationMemberTypes.OPTIONAL_STRING) ||
|
|
value.equals(AnnotationMemberTypes.OPTIONAL_INT)))
|
|
return;
|
|
|
|
int intValue = 0;
|
|
|
|
if (value instanceof String)
|
|
{
|
|
try
|
|
{
|
|
intValue = Integer.parseInt((String) value);
|
|
}
|
|
catch (NumberFormatException nfe)
|
|
{
|
|
error("The value ,"
|
|
+ value
|
|
+ ", assigned to an int property does not represent an integer.");
|
|
}
|
|
}
|
|
else if (value instanceof Integer)
|
|
{
|
|
intValue = ((Integer) value).intValue();
|
|
}
|
|
else
|
|
{
|
|
error("The value, "
|
|
+ value
|
|
+ ", assigned to an int property must be of type java.lang.String or int.");
|
|
}
|
|
|
|
if (intValue < a.minValue())
|
|
error("The value, "
|
|
+ intValue
|
|
+ ", assigned to an int property is less than the minimum value allowed: "
|
|
+ a.minValue() + ".");
|
|
else if (intValue > a.maxValue())
|
|
error("The value, "
|
|
+ intValue
|
|
+ ", assigned to an int property exeeds the maximum value allowed: "
|
|
+ a.maxValue() + ".");
|
|
}
|
|
|
|
private static void validateDecimal(AnnotationMemberTypes.Decimal a,
|
|
Object value, boolean optional)
|
|
{
|
|
if (optional
|
|
&& (value == null ||
|
|
value.equals(AnnotationMemberTypes.OPTIONAL_STRING) ||
|
|
value.equals(AnnotationMemberTypes.OPTIONAL_FLOAT) ||
|
|
value.equals(AnnotationMemberTypes.OPTIONAL_DOUBLE)))
|
|
return;
|
|
|
|
double doubleValue = 0;
|
|
String doubleString = null;
|
|
|
|
if (value instanceof String)
|
|
{
|
|
doubleValue = Double.parseDouble((String)value);
|
|
doubleString = (String)value;
|
|
}
|
|
else if (value instanceof Float)
|
|
{
|
|
doubleValue = ((Float)value).doubleValue();
|
|
doubleString = ((Float)value).toString();
|
|
}
|
|
else if (value instanceof Double)
|
|
{
|
|
doubleValue = ((Double)value).doubleValue();
|
|
doubleString = ((Double)value).toString();
|
|
}
|
|
else
|
|
{
|
|
error("The value, "
|
|
+ value
|
|
+ ", assigned to a decimal property must be of type float, double, or java.lang.String.");
|
|
}
|
|
|
|
if (doubleValue < a.minValue())
|
|
error("The value, "
|
|
+ doubleValue
|
|
+ ", assigned to a decimal property is less than the the minimum value allowed: "
|
|
+ a.minValue() + ".");
|
|
|
|
if (doubleValue > a.maxValue())
|
|
error("The value, "
|
|
+ doubleValue
|
|
+ ", assigned to a decimal property exceeds the maximum value allowed: "
|
|
+ a.maxValue() + ".");
|
|
|
|
int decimalPos = doubleString.indexOf('.');
|
|
|
|
if (decimalPos == -1 || a.places() == AnnotationMemberTypes.UNLIMITED_PLACES)
|
|
return;
|
|
|
|
if (doubleString.length() - decimalPos - 1 > a.places())
|
|
error("The decimal places in the value, " + doubleString
|
|
+ ", assigned to a decimal property exceeds " + a.places()
|
|
+ ", the number of decimal places allowed.");
|
|
|
|
}
|
|
|
|
private static void validateText(AnnotationMemberTypes.Text a,
|
|
Object value, boolean optional)
|
|
{
|
|
if (optional
|
|
&& (value == null || value
|
|
.equals(AnnotationMemberTypes.OPTIONAL_STRING)))
|
|
return;
|
|
|
|
if (!(value instanceof String))
|
|
error("The value, "
|
|
+ value
|
|
+ ", assigned to a text property must be of type java.lang.String.");
|
|
|
|
String str = (String) value;
|
|
if (str.length() > a.maxLength())
|
|
error("The value, "
|
|
+ str
|
|
+ ", assigned to a text property exceeds the maximum length allowed: "
|
|
+ a.maxLength());
|
|
|
|
if (a.isLong())
|
|
{
|
|
try
|
|
{
|
|
Long.parseLong(str);
|
|
}
|
|
catch (NumberFormatException nfe)
|
|
{
|
|
error("The value, "
|
|
+ str
|
|
+ ", assigned to a text property with a long number constraint does not represent a long number.");
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
private static void error(String message)
|
|
{
|
|
error(message, null);
|
|
}
|
|
|
|
private static void error(String message, Throwable t)
|
|
{
|
|
throw new IllegalArgumentException(message, t);
|
|
}
|
|
|
|
}
|