blob: 972e64f875c603880790e5231be8d9dc8f78a671 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.bval.jsr303;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Locale;
import java.util.Map;
import javax.validation.Constraint;
import javax.validation.ConstraintDefinitionException;
import javax.validation.Payload;
import javax.validation.ValidationException;
import org.apache.bval.jsr303.util.SecureActions;
import org.apache.commons.lang3.reflect.TypeUtils;
/**
* Defines the well-known attributes of {@link Constraint} annotations.
*
* @version $Rev: 1165923 $ $Date: 2011-09-06 18:07:53 -0500 (Tue, 06 Sep 2011) $
*/
public enum ConstraintAnnotationAttributes {
/**
* "message"
*/
MESSAGE,
/**
* "groups"
*/
GROUPS,
/**
* "payload"
*/
PAYLOAD,
/**
* "value" for multi-valued constraints
*/
VALUE(true);
@SuppressWarnings("unused")
private static class Types {
String message;
Class<?>[] groups;
Class<? extends Payload>[] payload;
Annotation[] value;
}
private Type type;
private boolean permitNullDefaultValue;
private ConstraintAnnotationAttributes() {
this(false);
}
private ConstraintAnnotationAttributes(boolean permitNullDefaultValue) {
this.permitNullDefaultValue = permitNullDefaultValue;
try {
this.type = Types.class.getDeclaredField(getAttributeName()).getGenericType();
} catch (Exception e) {
// should never happen
throw new RuntimeException(e);
}
}
/**
* Get the expected type of the represented attribute.
*
* @return Class<?>
*/
public Type getType() {
return type;
}
/**
* Get the attribute name represented.
*
* @return String
*/
public String getAttributeName() {
return name().toLowerCase(Locale.US);
}
/**
* Put <code>value</code> into a map with <code>this.attributeName</code> as
* key.
*
* @param <V>
* @param map
* @param value
* @return previous value mapped to <code>this.attributeName</code>
*/
public <V> Object put(Map<? super String, ? super V> map, V value) {
if (!TypeUtils.isInstance(value, getType())) {
throw new IllegalArgumentException(String.format("Invalid '%s' value: %s", getAttributeName(), value));
}
return map.put(getAttributeName(), value);
}
/**
* Get the value of <code>this.attributeName</code> from <code>map</code>.
*
* @param <V>
* @param map
* @return V if you say so
*/
public <V> V get(Map<? super String, ? super V> map) {
@SuppressWarnings("unchecked")
final V result = (V) map.get(getAttributeName());
if (!TypeUtils.isInstance(result, getType())) {
throw new IllegalStateException(String.format("Invalid '%s' value: %s", getAttributeName(), result));
}
return result;
}
/**
* Verify that this attribute is validly defined on the given type.
*
* @param type
* @throws ConstraintDefinitionException
*/
public <A extends Annotation> void validateOn(Class<A> type) {
new Worker<A>(type);
}
/**
* Benign means of checking for an attribute's existence.
*
* @param type
* @return whether the attribute was (properly) declared
*/
public <A extends Annotation> boolean isDeclaredOn(Class<A> type) {
return new Worker<A>(type, true).valid;
}
/**
* Get the value of this attribute from the specified constraint annotation.
*
* @param constraint
* @return Object
*/
public <T> T getValue(Annotation constraint) {
try {
@SuppressWarnings({ "rawtypes", "unchecked" })
T result = (T) new Worker(constraint.annotationType()).read(constraint);
return result;
} catch (Exception e) {
throw new ValidationException(String.format("Could not get value of %1$s() from %2$s", getType(),
constraint));
}
}
/**
* Get the default value of this attribute on the given annotation type.
* @param <T>
* @param type
* @return Object
*/
public <T, A extends Annotation> T getDefaultValue(Class<A> type) {
@SuppressWarnings("unchecked")
final T result = (T) new Worker<A>(type).defaultValue;
return result;
}
private static <T> T doPrivileged(final PrivilegedAction<T> action) {
if (System.getSecurityManager() != null) {
return AccessController.doPrivileged(action);
} else {
return action.run();
}
}
private class Worker<C> {
final Method method;
final Object defaultValue;
final boolean valid;
/**
* Create a new Worker instance.
* @param constraintType to handle
*/
Worker(Class<C> constraintType) {
this(constraintType, false);
}
/**
* Create a new Worker instance.
* @param constraintType to handle
* @param quiet whether to simply set !valid rather than throw an Exception on error
*/
Worker(Class<C> constraintType, boolean quiet) {
super();
boolean _valid = true;
Object _defaultValue = null;
try {
method = doPrivileged(SecureActions.getPublicMethod(constraintType, getAttributeName()));
if (method == null) {
if (quiet) {
_valid = false;
return;
}
throw new ConstraintDefinitionException(String.format("Annotation %1$s has no %2$s() method",
constraintType, getAttributeName()));
}
if (!TypeUtils.isAssignable(method.getReturnType(), getType())) {
if (quiet) {
_valid = false;
return;
}
throw new ConstraintDefinitionException(String.format(
"Return type for %1$s() must be of type %2$s", getAttributeName(), getType()));
}
_defaultValue = method.getDefaultValue();
if (_defaultValue == null && permitNullDefaultValue) {
return;
}
if (TypeUtils.isArrayType(getType()) && Array.getLength(_defaultValue) > 0) {
if (quiet) {
_valid = false;
return;
}
throw new ConstraintDefinitionException(String.format(
"Default value for %1$s() must be an empty array", getAttributeName()));
}
} finally {
valid = _valid;
defaultValue = _defaultValue;
}
}
<T> T read(final C constraint) {
@SuppressWarnings("unchecked")
T result = (T) doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
try {
method.setAccessible(true);
return method.invoke(constraint);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
return result;
}
}
}