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

org.apache.bval.jsr303.ConstraintAnnotationAttributes Maven / Gradle / Ivy

The 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.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[] 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 value into a map with this.attributeName as
     * key.
     * 
     * @param 
     * @param map
     * @param value
     * @return previous value mapped to this.attributeName
     */
    public  Object put(Map 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 this.attributeName from map.
     * 
     * @param 
     * @param map
     * @return V if you say so
     */
    public  V get(Map 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  void validateOn(Class type) {
        new Worker(type);
    }

    /**
     * Benign means of checking for an attribute's existence.
     * 
     * @param type
     * @return whether the attribute was (properly) declared
     */
    public  boolean isDeclaredOn(Class type) {
        return new Worker(type, true).valid;
    }

    /**
     * Get the value of this attribute from the specified constraint annotation.
     * 
     * @param constraint
     * @return Object
     */
    public  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 
     * @param type
     * @return Object
     */
    public  T getDefaultValue(Class type) {
        @SuppressWarnings("unchecked")
        final T result = (T) new Worker(type).defaultValue;
        return result;
    }

    private static  T doPrivileged(final PrivilegedAction action) {
        if (System.getSecurityManager() != null) {
            return AccessController.doPrivileged(action);
        } else {
            return action.run();
        }
    }

    private class Worker {
        final Method method;
        final Object defaultValue;
        final boolean valid;

        /**
         * Create a new Worker instance.
         * @param constraintType to handle
         */
        Worker(Class 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 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 read(final C constraint) {
            @SuppressWarnings("unchecked")
            T result = (T) doPrivileged(new PrivilegedAction() {
                public Object run() {
                    try {
                        method.setAccessible(true);
                        return method.invoke(constraint);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            });
            return result;
        }
    }
}