/*
* JBoss, Home of Professional Open Source
* Copyright 2011 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @authors tag. All rights reserved.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package org.jboss.as.controller.operations.validation;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.EnumSet;
import org.jboss.as.controller.logging.ControllerLogger;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
/**
* Validates that the given parameter is of the correct type.
* <p>
* Note on strict type matching:
* </p>
* <p>
* The constructor takes a parameter {@code strictType}. If {@code strictType} is {@code false}, nodes being validated do not
* need to precisely match the type(s) passed to the constructor; rather a limited set of value conversions
* will be attempted, and if the node value can be converted, the node is considered to match the required type.
* The conversions are:
* <ul>
* <li>For BIG_DECIMAL, BIG_INTEGER, DOUBLE, INT, LONG and PROPERTY, the related ModelNode.asXXX() method is invoked; if
* no exception is thrown the type is considered to match.</li>
* <li>For BOOLEAN, if the node is of type BOOLEAN it is considered to match. If it is of type STRING with a value
* ignoring case of "true" or "false" it is considered to match.</li>
* <li>For OBJECT, if the node is of type OBJECT or PROPERTY it is considered to match. If it is of type LIST and each element
* in the list is of type PROPERTY it is considered to match.</li>
* <li>For STRING, if the node is of type STRING, BIG_DECIMAL, BIG_INTEGER, DOUBLE, INT or LONG it is considered to match.</li>
* </ul>
* For all other types, an exact match is required.
* </p>
*
* @author Brian Stansberry (c) 2011 Red Hat Inc.
*/
public class ModelTypeValidator implements ParameterValidator {
protected static final BigDecimal BIGDECIMAL_MAX = BigDecimal.valueOf(Integer.MAX_VALUE);
protected static final BigDecimal BIGDECIMAL_MIN = BigDecimal.valueOf(Integer.MIN_VALUE);
protected static final BigInteger BIGINTEGER_MAX = BigInteger.valueOf(Integer.MAX_VALUE);
protected static final BigInteger BIGINTEGER_MIN = BigInteger.valueOf(Integer.MIN_VALUE);
protected final EnumSet<ModelType> validTypes;
protected final boolean nullable;
protected final boolean strictType;
/**
* Same as {@code ModelTypeValidator(type, false, false, false)}.
*
* @param type the valid type. Cannot be {@code null}
*/
public ModelTypeValidator(final ModelType type) {
this(false, false, false, type);
}
/**
* Same as {@code ModelTypeValidator(type, nullable, false, false)}.
*
* @param type the valid type. Cannot be {@code null}
* @param nullable whether {@link ModelType#UNDEFINED} is allowed
*/
public ModelTypeValidator(final ModelType type, final boolean nullable) {
this(nullable, false, false, type);
}
/**
* Same as {@code ModelTypeValidator(type, nullable, allowExpressions, false)}.
*
* @param type the valid type. Cannot be {@code null}
* @param nullable whether {@link ModelType#UNDEFINED} is allowed
* @param allowExpressions whether {@link ModelType#EXPRESSION} is allowed
*/
public ModelTypeValidator(final ModelType type, final boolean nullable, final boolean allowExpressions) {
this(nullable, allowExpressions, false, type);
}
/**
* Creates a ModelTypeValidator that allows the given type.
*
* @param type the valid type. Cannot be {@code null}
* @param nullable whether {@link ModelType#UNDEFINED} is allowed
* @param allowExpressions whether {@link ModelType#EXPRESSION} is allowed
* @param strictType {@code true} if the type of a node must precisely match {@code type}; {@code false} if the value
* conversions described in the class javadoc can be performed to check for compatible types
*/
public ModelTypeValidator(final ModelType type, final boolean nullable, final boolean allowExpressions, final boolean strictType) {
this(nullable, allowExpressions, strictType, type);
}
/**
* Creates a ModelTypeValidator that allows potentially more than one type.
*
* @param nullable whether {@link ModelType#UNDEFINED} is allowed
* @param allowExpressions whether {@link ModelType#EXPRESSION} is allowed
* @param strictType {@code true} if the type of a node must precisely match {@code type}; {@code false} if the value
* conversions described in the class javadoc can be performed to check for compatible types
* @param firstValidType a valid type. Cannot be {@code null}
* @param otherValidTypes additional valid types. May be {@code null}
*/
public ModelTypeValidator(final boolean nullable, final boolean allowExpressions, final boolean strictType, ModelType firstValidType, ModelType... otherValidTypes) {
this.validTypes = EnumSet.of(firstValidType, otherValidTypes);
this.nullable = nullable;
if (allowExpressions) {
this.validTypes.add(ModelType.EXPRESSION);
}
this.strictType = strictType;
}
/**
* {@inheritDoc}
*/
@Override
public void validateParameter(String parameterName, ModelNode value) throws OperationFailedException {
if (!value.isDefined()) {
if (!nullable)
throw ControllerLogger.ROOT_LOGGER.nullNotAllowed(parameterName);
} else {
boolean matched = false;
if (strictType) {
matched = validTypes.contains(value.getType());
} else {
for (ModelType validType : validTypes) {
if (matches(value, validType)) {
matched = true;
break;
}
}
}
if (!matched)
throw ControllerLogger.ROOT_LOGGER.incorrectType(parameterName, validTypes, value.getType());
}
}
/**
* {@inheritDoc}
*/
@Override
public void validateResolvedParameter(String parameterName, ModelNode value) throws OperationFailedException {
validateParameter(parameterName, value.resolve());
}
private boolean matches(ModelNode value, ModelType validType) {
try {
if (validType == value.getType())
return true;
switch (validType) {
case BIG_DECIMAL: {
value.asBigDecimal();
return true;
}
case BIG_INTEGER: {
value.asBigInteger();
return true;
}
case DOUBLE: {
value.asDouble();
return true;
}
case INT: {
switch (value.getType()){
case BIG_DECIMAL:
BigDecimal valueBigDecimal = value.asBigDecimal();
return (valueBigDecimal.compareTo(BIGDECIMAL_MAX) <= 0) && (valueBigDecimal.compareTo(BIGDECIMAL_MIN) >= 0);
case BIG_INTEGER:
BigInteger valueBigInteger = value.asBigInteger();
return (valueBigInteger.compareTo(BIGINTEGER_MAX) <= 0) && (valueBigInteger.compareTo(BIGINTEGER_MIN) >= 0);
case LONG:
Long valueLong = value.asLong();
return valueLong <= Integer.MAX_VALUE && valueLong >= Integer.MIN_VALUE;
case DOUBLE:
Double valueDouble = value.asDouble();
return valueDouble <= Integer.MAX_VALUE && valueDouble >= Integer.MIN_VALUE;
case STRING:
value.asInt();
return true;
default:
return false;
}
}
case LONG: {
switch(value.getType()){
case BIG_DECIMAL:
BigDecimal valueBigDecimal = value.asBigDecimal();
return (valueBigDecimal.compareTo(BIGDECIMAL_MAX) <= 0) && (valueBigDecimal.compareTo(BIGDECIMAL_MIN) >= 0);
case BIG_INTEGER:
BigInteger valueBigInteger = value.asBigInteger();
return (valueBigInteger.compareTo(BIGINTEGER_MAX) <= 0) && (valueBigInteger.compareTo(BIGINTEGER_MIN) >= 0);
case DOUBLE:
Double valueDouble = value.asDouble();
return valueDouble <= Long.MAX_VALUE && valueDouble >= Long.MIN_VALUE;
case INT:
value.asLong();
return true;
case STRING:
value.asLong();
return true;
default:
return false;
}
}
case PROPERTY: {
value.asProperty();
return true;
}
case BOOLEAN: {
// Allow some type conversions, not others.
switch (value.getType()) {
case STRING: {
String s = value.asString();
return "false".equalsIgnoreCase(s) || "true".equalsIgnoreCase(s);
}
case BOOLEAN:
//case INT:
return true;
}
return false;
}
case OBJECT: {
// We accept OBJECT, PROPERTY or LIST where all elements are PROPERTY
switch (value.getType()) {
case PROPERTY:
case OBJECT:
return true;
case LIST: {
for (ModelNode node : value.asList()) {
if (node.getType() != ModelType.PROPERTY) {
return false;
}
}
return true;
}
}
return false;
}
case STRING: {
// Allow some type conversions, not others.
switch (value.getType()) {
case BIG_DECIMAL:
case BIG_INTEGER:
case BOOLEAN:
case DOUBLE:
case INT:
case LONG:
case STRING:
return true;
}
return false;
}
case BYTES:
// we could handle STRING but IMO if people want to allow STRING to byte[] conversion
// they should use a different validator class
case LIST:
// we could handle OBJECT but IMO if people want to allow OBJECT to LIST conversion
// they should use a different validator class
case EXPRESSION:
case TYPE:
case UNDEFINED:
default:
return false;
}
}
catch (RuntimeException e) {
return false;
}
}
}