You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bval.apache.org by mb...@apache.org on 2018/02/21 21:02:01 UTC

[11/17] bval git commit: BV2: new descriptor model

BV2: new descriptor model


Project: http://git-wip-us.apache.org/repos/asf/bval/repo
Commit: http://git-wip-us.apache.org/repos/asf/bval/commit/40ac09f7
Tree: http://git-wip-us.apache.org/repos/asf/bval/tree/40ac09f7
Diff: http://git-wip-us.apache.org/repos/asf/bval/diff/40ac09f7

Branch: refs/heads/bv2
Commit: 40ac09f77a673fc61863304a72facc38f2ef2e42
Parents: 59bd964
Author: Matt Benson <mb...@apache.org>
Authored: Wed Feb 21 14:50:59 2018 -0600
Committer: Matt Benson <mb...@apache.org>
Committed: Wed Feb 21 14:59:57 2018 -0600

----------------------------------------------------------------------
 .../org/apache/bval/jsr/descriptor/BeanD.java   | 128 ++++++++
 .../jsr/descriptor/CascadableContainerD.java    |  88 ++++++
 .../apache/bval/jsr/descriptor/ComposedD.java   | 123 ++++++++
 .../ComputeConstraintValidatorClass.java        | 183 ++++++++++++
 .../apache/bval/jsr/descriptor/ConstraintD.java | 276 ++++++++++++++++++
 .../bval/jsr/descriptor/ConstructorD.java       |  41 +++
 .../jsr/descriptor/ContainerElementTypeD.java   | 119 ++++++++
 .../bval/jsr/descriptor/CrossParameterD.java    |  18 ++
 .../bval/jsr/descriptor/DescriptorManager.java  |  74 +++++
 .../apache/bval/jsr/descriptor/ElementD.java    | 122 ++++++++
 .../apache/bval/jsr/descriptor/ExecutableD.java |  84 ++++++
 .../org/apache/bval/jsr/descriptor/Finder.java  | 103 +++++++
 .../bval/jsr/descriptor/GroupConversion.java    |  85 ++++++
 .../bval/jsr/descriptor/MetadataReader.java     | 291 +++++++++++++++++++
 .../org/apache/bval/jsr/descriptor/MethodD.java |  49 ++++
 .../apache/bval/jsr/descriptor/ParameterD.java  |  62 ++++
 .../apache/bval/jsr/descriptor/PropertyD.java   | 106 +++++++
 .../bval/jsr/descriptor/ReturnValueD.java       |  36 +++
 18 files changed, 1988 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/BeanD.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/BeanD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/BeanD.java
new file mode 100644
index 0000000..7f52c6d
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/BeanD.java
@@ -0,0 +1,128 @@
+/*
+ * 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.jsr.descriptor;
+
+import java.lang.reflect.Type;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.validation.metadata.BeanDescriptor;
+import javax.validation.metadata.ConstructorDescriptor;
+import javax.validation.metadata.MethodDescriptor;
+import javax.validation.metadata.MethodType;
+import javax.validation.metadata.PropertyDescriptor;
+
+import org.apache.bval.jsr.metadata.Signature;
+import org.apache.bval.jsr.util.ToUnmodifiable;
+import org.apache.bval.util.Exceptions;
+import org.apache.bval.util.Lazy;
+import org.apache.bval.util.StringUtils;
+
+public class BeanD extends ElementD<Class<?>, MetadataReader.ForBean> implements BeanDescriptor {
+
+    private static boolean constrainedProperty(PropertyDescriptor pd) {
+        return pd.hasConstraints() || pd.isCascaded();
+    }
+
+    private final Class<?> beanClass;
+
+    private final Lazy<List<Class<?>>> groupSequence;
+    private final Lazy<Map<String, PropertyDescriptor>> propertiesMap;
+    private final Lazy<Set<PropertyDescriptor>> properties;
+    private final Lazy<Map<Signature, ConstructorD>> constructors;
+    private final Lazy<Map<Signature, MethodD>> methods;
+
+    BeanD(MetadataReader.ForBean reader) {
+        super(reader);
+        this.beanClass = reader.meta.getHost();
+
+        groupSequence = new Lazy<>(reader::getGroupSequence);
+        propertiesMap = new Lazy<>(() -> reader.getProperties(this));
+        properties = new Lazy<>(() -> propertiesMap.get().values().stream().filter(BeanD::constrainedProperty)
+            .collect(ToUnmodifiable.set()));
+        constructors = new Lazy<>(() -> reader.getConstructors(this));
+        methods = new Lazy<>(() -> reader.getMethods(this));
+    }
+
+    @Override
+    public Class<?> getElementClass() {
+        return beanClass;
+    }
+
+    @Override
+    public boolean isBeanConstrained() {
+        return hasConstraints() || properties.get().stream().anyMatch(DescriptorManager::isConstrained);
+    }
+
+    @Override
+    public PropertyDescriptor getConstraintsForProperty(String propertyName) {
+        return Optional.ofNullable(getProperty(propertyName)).filter(BeanD::constrainedProperty).orElse(null);
+    }
+
+    @Override
+    public Set<PropertyDescriptor> getConstrainedProperties() {
+        return properties.get();
+    }
+
+    @Override
+    public MethodDescriptor getConstraintsForMethod(String methodName, Class<?>... parameterTypes) {
+        return methods.get().get(new Signature(methodName, parameterTypes));
+    }
+
+    @SuppressWarnings("unlikely-arg-type")
+    @Override
+    public Set<MethodDescriptor> getConstrainedMethods(MethodType methodType, MethodType... methodTypes) {
+        return methods.get().values().stream().filter(EnumSet.of(methodType, methodTypes)::contains)
+            .collect(ToUnmodifiable.set());
+    }
+
+    @Override
+    public ConstructorDescriptor getConstraintsForConstructor(Class<?>... parameterTypes) {
+        return constructors.get().get(new Signature(beanClass.getName(), parameterTypes));
+    }
+
+    @Override
+    public Set<ConstructorDescriptor> getConstrainedConstructors() {
+        return constructors.get().values().stream().collect(ToUnmodifiable.set());
+    }
+
+    public PropertyDescriptor getProperty(String propertyName) {
+        Exceptions.raiseIf(StringUtils.isBlank(propertyName), IllegalArgumentException::new,
+            "propertyName was null/empty/blank");
+
+        return propertiesMap.get().get(propertyName);
+    }
+
+    @Override
+    protected BeanD getBean() {
+        return this;
+    }
+
+    @Override
+    public List<Class<?>> getGroupSequence() {
+        return groupSequence.get();
+    }
+
+    public final Type getGenericType() {
+        return getElementClass();
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/CascadableContainerD.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/CascadableContainerD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/CascadableContainerD.java
new file mode 100644
index 0000000..8c76fb0
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/CascadableContainerD.java
@@ -0,0 +1,88 @@
+/*
+ * 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.jsr.descriptor;
+
+import java.lang.reflect.AnnotatedElement;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import javax.validation.ValidationException;
+import javax.validation.metadata.CascadableDescriptor;
+import javax.validation.metadata.ContainerDescriptor;
+import javax.validation.metadata.ContainerElementTypeDescriptor;
+import javax.validation.metadata.GroupConversionDescriptor;
+
+import org.apache.bval.jsr.GraphContext;
+import org.apache.bval.jsr.util.ToUnmodifiable;
+import org.apache.bval.util.Lazy;
+import org.apache.bval.util.Validate;
+import org.apache.bval.util.reflection.TypeUtils;
+
+public abstract class CascadableContainerD<P extends ElementD<?, ?>, E extends AnnotatedElement> extends
+    ElementD.NonRoot<P, E, MetadataReader.ForContainer<E>> implements CascadableDescriptor, ContainerDescriptor {
+
+    private final boolean cascaded;
+    private final Set<GroupConversion> groupConversions;
+    private final Lazy<Set<ContainerElementTypeD>> containerElementTypes;
+
+    protected CascadableContainerD(MetadataReader.ForContainer<E> reader, P parent) {
+        super(reader, parent);
+        cascaded = reader.isCascaded();
+        groupConversions = reader.getGroupConversions();
+        containerElementTypes = new Lazy<>(() -> reader.getContainerElementTypes(this));
+    }
+
+    @Override
+    public Class<?> getElementClass() {
+        return TypeUtils.getRawType(getGenericType(), parent.getElementClass());
+    }
+
+    @Override
+    public boolean isCascaded() {
+        return cascaded;
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Override
+    public Set<GroupConversionDescriptor> getGroupConversions() {
+        return (Set) groupConversions;
+    }
+
+    @Override
+    public Set<ContainerElementTypeDescriptor> getConstrainedContainerElementTypes() {
+        return containerElementTypes.get().stream().filter(DescriptorManager::isConstrained)
+            .collect(ToUnmodifiable.set());
+    }
+
+    public final Stream<GraphContext> read(GraphContext context) {
+        Validate.notNull(context);
+        if (context.getValue() == null) {
+            return Stream.empty();
+        }
+        try {
+            return readImpl(context);
+        } catch (Exception e) {
+            throw new ValidationException(e);
+        }
+    }
+
+    protected Stream<GraphContext> readImpl(GraphContext context) throws Exception {
+        throw new UnsupportedOperationException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ComposedD.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ComposedD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ComposedD.java
new file mode 100644
index 0000000..6d3b004
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ComposedD.java
@@ -0,0 +1,123 @@
+/*
+ * 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.jsr.descriptor;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import javax.validation.metadata.CascadableDescriptor;
+import javax.validation.metadata.ConstraintDescriptor;
+import javax.validation.metadata.ContainerDescriptor;
+import javax.validation.metadata.ContainerElementTypeDescriptor;
+import javax.validation.metadata.ElementDescriptor;
+import javax.validation.metadata.GroupConversionDescriptor;
+import javax.validation.metadata.PropertyDescriptor;
+
+import org.apache.bval.jsr.util.ToUnmodifiable;
+import org.apache.bval.util.Validate;
+
+public abstract class ComposedD<D extends ElementD<?, ?>> implements ElementDescriptor {
+
+    static abstract class ForCascadableContainer<D extends CascadableContainerD<?, ?>> extends ComposedD<D>
+        implements CascadableDescriptor, ContainerDescriptor {
+
+        ForCascadableContainer(List<D> delegates) {
+            super(delegates);
+        }
+
+        @Override
+        public Set<ContainerElementTypeDescriptor> getConstrainedContainerElementTypes() {
+            return delegates.stream().map(ContainerDescriptor::getConstrainedContainerElementTypes)
+                .flatMap(Collection::stream).collect(ToUnmodifiable.set());
+        }
+
+        @Override
+        public boolean isCascaded() {
+            return delegates.stream().anyMatch(CascadableDescriptor::isCascaded);
+        }
+
+        @Override
+        public Set<GroupConversionDescriptor> getGroupConversions() {
+            return delegates.stream().map(CascadableDescriptor::getGroupConversions).flatMap(Collection::stream)
+                .collect(ToUnmodifiable.set());
+        }
+    }
+
+    static class ForProperty extends ComposedD.ForCascadableContainer<PropertyD<?>> implements PropertyDescriptor {
+
+        ForProperty(List<PropertyD<?>> delegates) {
+            super(delegates);
+        }
+
+        @Override
+        public String getPropertyName() {
+            return delegates.stream().map(PropertyDescriptor::getPropertyName).findFirst()
+                .orElseThrow(IllegalStateException::new);
+        }
+    }
+
+    public static <T extends ElementD<?, ?>> Stream<T> unwrap(ElementDescriptor descriptor, Class<T> delegateType) {
+        final Stream<?> s;
+
+        if (descriptor instanceof ComposedD<?>) {
+            s = ((ComposedD<?>) descriptor).delegates.stream()
+                // unwrap recursively:
+                .flatMap(d -> unwrap(d, delegateType));
+        } else {
+            s = Stream.of(descriptor);
+        }
+        return s.map(delegateType::cast);
+    }
+
+    protected final List<D> delegates;
+
+    ComposedD(List<D> delegates) {
+        super();
+        this.delegates = delegates;
+
+        Validate.notNull(delegates, "delegates");
+        Validate.isTrue(!delegates.isEmpty(), "At least one delegate is required");
+        Validate.isTrue(delegates.stream().noneMatch(Objects::isNull), "null delegates not permitted");
+    }
+
+    @Override
+    public boolean hasConstraints() {
+        return delegates.stream().anyMatch(ElementDescriptor::hasConstraints);
+    }
+
+    @Override
+    public Class<?> getElementClass() {
+        return delegates.stream().map(ElementDescriptor::getElementClass).findFirst()
+            .orElseThrow(IllegalStateException::new);
+    }
+
+    @Override
+    public Set<ConstraintDescriptor<?>> getConstraintDescriptors() {
+        return delegates.stream().map(ElementDescriptor::getConstraintDescriptors).flatMap(Collection::stream)
+            .collect(ToUnmodifiable.set());
+    }
+
+    @Override
+    public ConstraintFinder findConstraints() {
+        return new Finder(this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ComputeConstraintValidatorClass.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ComputeConstraintValidatorClass.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ComputeConstraintValidatorClass.java
new file mode 100644
index 0000000..7eeefd2
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ComputeConstraintValidatorClass.java
@@ -0,0 +1,183 @@
+package org.apache.bval.jsr.descriptor;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.WildcardType;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.validation.ConstraintDefinitionException;
+import javax.validation.ConstraintValidator;
+import javax.validation.UnexpectedTypeException;
+import javax.validation.constraintvalidation.ValidationTarget;
+
+import org.apache.bval.jsr.ApacheValidatorFactory;
+import org.apache.bval.jsr.ConstraintCached.ConstraintValidatorInfo;
+import org.apache.bval.util.Exceptions;
+import org.apache.bval.util.Validate;
+import org.apache.bval.util.reflection.Reflection;
+import org.apache.bval.util.reflection.Reflection.Interfaces;
+import org.apache.bval.util.reflection.TypeUtils;
+import org.apache.commons.weaver.privilizer.Privilizing;
+import org.apache.commons.weaver.privilizer.Privilizing.CallTo;
+
+@Privilizing(@CallTo(Reflection.class))
+class ComputeConstraintValidatorClass<A extends Annotation>
+    implements Supplier<Class<? extends ConstraintValidator<A, ?>>> {
+
+    private static class TypeWrapper {
+        final Class<?> componentType;
+        final int arrayDepth;
+
+        TypeWrapper(Class<?> type) {
+            Class<?> c = type;
+            int d = 0;
+            while (Object[].class.isAssignableFrom(c)) {
+                d++;
+                c = c.getComponentType();
+            }
+            this.componentType = c;
+            this.arrayDepth = d;
+        }
+
+        Class<?> unwrap(Class<?> t) {
+            Exceptions.raiseUnless(t.isAssignableFrom(componentType), IllegalArgumentException::new,
+                "%s not assignable from %s", t, componentType);
+            if (arrayDepth == 0) {
+                return t;
+            }
+            return Array.newInstance(t, new int[arrayDepth]).getClass();
+        }
+    }
+
+    private static final String CV = ConstraintValidator.class.getSimpleName();
+    private static final WildcardType UNBOUNDED = TypeUtils.wildcardType().build();
+
+    private static Class<?> getValidatedType(Class<? extends ConstraintValidator<?, ?>> validatorType) {
+        final Type result = TypeUtils.getTypeArguments(validatorType, ConstraintValidator.class)
+            .get(ConstraintValidator.class.getTypeParameters()[1]);
+        Exceptions.raiseUnless(isSupported(result), ConstraintDefinitionException::new,
+            "Validated type %s declared by %s %s is unsupported", result, CV, validatorType.getName());
+        return TypeUtils.getRawType(result, null);
+    }
+
+    private static boolean isSupported(Type validatedType) {
+        if (validatedType instanceof Class<?>) {
+            return true;
+        }
+        if (validatedType instanceof ParameterizedType) {
+            return Stream.of(((ParameterizedType) validatedType).getActualTypeArguments())
+                .allMatch(arg -> TypeUtils.equals(arg, UNBOUNDED));
+        }
+        return false;
+    }
+
+    private final ApacheValidatorFactory validatorFactory;
+    private final Class<?> validatedType;
+    private final ValidationTarget validationTarget;
+    private final A constraint;
+    private final boolean composed;
+
+    ComputeConstraintValidatorClass(ApacheValidatorFactory validatorFactory, ValidationTarget validationTarget,
+        A constraint, Class<?> validatedType) {
+        super();
+        this.validatorFactory = Validate.notNull(validatorFactory, "validatorFactory");
+        this.validationTarget = Validate.notNull(validationTarget, "validationTarget");
+        this.constraint = Validate.notNull(constraint, "constraint");
+        this.validatedType = Validate.notNull(validatedType, "validatedType");
+        this.composed = validatorFactory.getAnnotationsManager().isComposed(constraint);
+    }
+
+    @Override
+    public Class<? extends ConstraintValidator<A, ?>> get() {
+        @SuppressWarnings("unchecked")
+        final Class<A> constraintType = (Class<A>) constraint.annotationType();
+        return findValidator(validatorFactory.getConstraintsCache().getConstraintValidatorInfo(constraintType));
+    }
+
+    private Class<? extends ConstraintValidator<A, ?>> findValidator(Set<ConstraintValidatorInfo<A>> infos) {
+        switch (validationTarget) {
+        case PARAMETERS:
+            return findCrossParameterValidator(infos);
+        case ANNOTATED_ELEMENT:
+            return findAnnotatedElementValidator(infos);
+        default:
+            return null;
+        }
+    }
+
+    private Class<? extends ConstraintValidator<A, ?>> findCrossParameterValidator(
+        Set<ConstraintValidatorInfo<A>> infos) {
+
+        final Set<ConstraintValidatorInfo<A>> set =
+            infos.stream().filter(info -> info.getSupportedTargets().contains(ValidationTarget.PARAMETERS))
+                .collect(Collectors.toSet());
+
+        @SuppressWarnings("unchecked")
+        final Class<A> constraintType = (Class<A>) constraint.annotationType();
+
+        Exceptions.raiseIf(set.size() > 1 || !composed && set.isEmpty(), UnexpectedTypeException::new,
+            "%d cross-parameter %ss found for constraint type %s", set.size(), CV, constraintType);
+
+        final Class<? extends ConstraintValidator<A, ?>> result = set.iterator().next().getType();
+        Exceptions.raiseUnless(TypeUtils.isAssignable(Object[].class, getValidatedType(result)),
+            ConstraintDefinitionException::new,
+            "Cross-parameter %s %s does not support the validation of an object array", CV, result.getName());
+
+        return result;
+    }
+
+    private Class<? extends ConstraintValidator<A, ?>> findAnnotatedElementValidator(
+        Set<ConstraintValidatorInfo<A>> infos) {
+
+        final Map<Class<?>, Class<? extends ConstraintValidator<?, ?>>> validators =
+            infos.stream().filter(info -> info.getSupportedTargets().contains(ValidationTarget.ANNOTATED_ELEMENT))
+                .map(ConstraintValidatorInfo::getType)
+                .collect(Collectors.toMap(ComputeConstraintValidatorClass::getValidatedType, Function.identity()));
+
+        final Map<Type, Class<? extends ConstraintValidator<?, ?>>> candidates = new HashMap<>();
+
+        walkHierarchy().filter(validators::containsKey).forEach(type -> {
+            // if we haven't already found a candidate whose validated type
+            // is a subtype of the current evaluated type, save:
+            if (!candidates.keySet().stream().anyMatch(k -> TypeUtils.isAssignable(k, type))) {
+                candidates.put(type, validators.get(type));
+            }
+        });
+        final String cond;
+        switch (candidates.size()) {
+        case 1:
+            @SuppressWarnings("unchecked")
+            final Class<? extends ConstraintValidator<A, ?>> result =
+                (Class<? extends ConstraintValidator<A, ?>>) candidates.values().iterator().next();
+            return result;
+        case 0:
+            if (composed) {
+                return null;
+            }
+            cond = "No compliant";
+            break;
+        default:
+            cond = "> 1 maximally specific";
+            break;
+        }
+        throw Exceptions.create(UnexpectedTypeException::new, "%s %s %s found for annotated element of type %s", cond,
+            constraint.annotationType().getName(), CV, TypeUtils.toString(validatedType));
+    }
+
+    // account for validated array types by unwrapping and rewrapping component
+    // type hierarchy:
+    private Stream<Class<?>> walkHierarchy() {
+        final TypeWrapper w = new TypeWrapper(Reflection.primitiveToWrapper(validatedType));
+        Stream.Builder<Class<?>> hierarchy = Stream.builder();
+        Reflection.hierarchy(w.componentType, Interfaces.INCLUDE).forEach(hierarchy);
+        return hierarchy.build().map(w::unwrap);
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ConstraintD.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ConstraintD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ConstraintD.java
new file mode 100644
index 0000000..0c1be1b
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ConstraintD.java
@@ -0,0 +1,276 @@
+/*
+ * 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.jsr.descriptor;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+import javax.validation.ConstraintDeclarationException;
+import javax.validation.ConstraintDefinitionException;
+import javax.validation.ConstraintTarget;
+import javax.validation.ConstraintValidator;
+import javax.validation.Payload;
+import javax.validation.ReportAsSingleViolation;
+import javax.validation.UnexpectedTypeException;
+import javax.validation.ValidationException;
+import javax.validation.groups.Default;
+import javax.validation.metadata.ConstraintDescriptor;
+import javax.validation.metadata.Scope;
+import javax.validation.metadata.ValidateUnwrappedValue;
+import javax.validation.valueextraction.UnwrapByDefault;
+import javax.validation.valueextraction.Unwrapping;
+import javax.validation.valueextraction.Unwrapping.Skip;
+import javax.validation.valueextraction.Unwrapping.Unwrap;
+import javax.validation.valueextraction.ValueExtractor;
+
+import org.apache.bval.jsr.ApacheValidatorFactory;
+import org.apache.bval.jsr.ConstraintAnnotationAttributes;
+import org.apache.bval.jsr.ConstraintAnnotationAttributes.Worker;
+import org.apache.bval.jsr.metadata.ContainerElementKey;
+import org.apache.bval.jsr.metadata.Metas;
+import org.apache.bval.jsr.util.AnnotationsManager;
+import org.apache.bval.jsr.util.ToUnmodifiable;
+import org.apache.bval.jsr.valueextraction.ValueExtractors;
+import org.apache.bval.util.Exceptions;
+import org.apache.bval.util.Lazy;
+import org.apache.bval.util.Validate;
+import org.apache.bval.util.reflection.TypeUtils;
+
+public class ConstraintD<A extends Annotation> implements ConstraintDescriptor<A> {
+    private static <T> Set<T> set(Supplier<T[]> array) {
+        return Stream.of(array.get()).collect(ToUnmodifiable.set());
+    }
+
+    private final A annotation;
+    private final Scope scope;
+    private final Metas<?> meta;
+    private final Class<?> validatedType;
+
+    private final Lazy<Set<Class<?>>> groups = new Lazy<>(this::computeGroups);
+
+    private final Set<Class<? extends Payload>> payload;
+
+    private final Lazy<Boolean> reportAsSingle =
+        new Lazy<>(() -> getAnnotation().annotationType().isAnnotationPresent(ReportAsSingleViolation.class));
+
+    private final Lazy<ValidateUnwrappedValue> valueUnwrapping = new Lazy<>(this::computeValidateUnwrappedValue);
+
+    private final Lazy<Map<String, Object>> attributes;
+    private final Lazy<Set<ConstraintDescriptor<?>>> composingConstraints;
+    private final Lazy<List<Class<? extends ConstraintValidator<A, ?>>>> constraintValidatorClasses;
+    private final Lazy<Class<? extends ConstraintValidator<A, ?>>> constraintValidatorClass;
+
+    public ConstraintD(A annotation, Scope scope, Metas<?> meta, ApacheValidatorFactory validatorFactory) {
+        this.annotation = Validate.notNull(annotation, "annotation");
+        this.scope = Validate.notNull(scope, "scope");
+        this.meta = Validate.notNull(meta, "meta");
+        this.payload = computePayload();
+        this.validatedType = computeValidatedType(validatorFactory);
+
+        attributes = new Lazy<>(() -> AnnotationsManager.readAttributes(annotation));
+
+        // retain no references to the validatorFactory; only wrap it in lazy
+        // suppliers
+        Validate.notNull(validatorFactory, "validatorFactory");
+        composingConstraints = new Lazy<>(computeComposingConstraints(validatorFactory));
+        constraintValidatorClasses = new Lazy<>(computeConstraintValidatorClasses(validatorFactory));
+
+        final Supplier<Class<? extends ConstraintValidator<A, ?>>> computeConstraintValidatorClass =
+            new ComputeConstraintValidatorClass<>(validatorFactory, meta.getValidationTarget(), annotation,
+                validatedType);
+
+        constraintValidatorClass = new Lazy<>(computeConstraintValidatorClass);
+    }
+
+    @Override
+    public A getAnnotation() {
+        return annotation;
+    }
+
+    @Override
+    public Set<Class<?>> getGroups() {
+        return groups.get();
+    }
+
+    @Override
+    public Set<Class<? extends Payload>> getPayload() {
+        return payload;
+    }
+
+    @Override
+    public List<Class<? extends ConstraintValidator<A, ?>>> getConstraintValidatorClasses() {
+        return constraintValidatorClasses.get();
+    }
+
+    @Override
+    public Map<String, Object> getAttributes() {
+        return attributes.get();
+    }
+
+    @Override
+    public Set<ConstraintDescriptor<?>> getComposingConstraints() {
+        return composingConstraints.get();
+    }
+
+    @Override
+    public boolean isReportAsSingleViolation() {
+        return reportAsSingle.get().booleanValue();
+    }
+
+    @Override
+    public String getMessageTemplate() {
+        final boolean required = true;
+        return read(ConstraintAnnotationAttributes.MESSAGE, required);
+    }
+
+    @Override
+    public ConstraintTarget getValidationAppliesTo() {
+        return read(ConstraintAnnotationAttributes.VALIDATION_APPLIES_TO);
+    }
+
+    @Override
+    public ValidateUnwrappedValue getValueUnwrapping() {
+        return valueUnwrapping.get();
+    }
+
+    @Override
+    public <U> U unwrap(Class<U> type) throws ValidationException {
+        try {
+            return type.cast(this);
+        } catch (ClassCastException e) {
+            throw new ValidationException(e);
+        }
+    }
+
+    public Scope getScope() {
+        return scope;
+    }
+
+    public Class<?> getDeclaringClass() {
+        return meta.getDeclaringClass();
+    }
+
+    public ElementType getDeclaredOn() {
+        return meta.getElementType();
+    }
+
+    public Class<?> getValidatedType() {
+        return validatedType;
+    }
+
+    public Class<? extends ConstraintValidator<A, ?>> getConstraintValidatorClass() {
+        return constraintValidatorClass.get();
+    }
+
+    private <T> T read(ConstraintAnnotationAttributes attr) {
+        return read(attr, false);
+    }
+
+    private <T> T read(ConstraintAnnotationAttributes attr, boolean required) {
+        final Class<? extends Annotation> constraintType = annotation.annotationType();
+        final Optional<T> result =
+            Optional.of(constraintType).map(attr::analyze).filter(Worker::isValid).map(w -> w.<T> read(annotation));
+
+        Exceptions.raiseIf(required && !result.isPresent(), ConstraintDefinitionException::new,
+            "Required attribute %s missing from constraint type %s", attr.getAttributeName(), constraintType);
+
+        return result.orElse(null);
+    }
+
+    private Supplier<Set<ConstraintDescriptor<?>>> computeComposingConstraints(
+        ApacheValidatorFactory validatorFactory) {
+        return () -> Stream.of(validatorFactory.getAnnotationsManager().getComposingConstraints(annotation))
+            .map(c -> new ConstraintD<>(c, scope, meta, validatorFactory))
+            .collect(ToUnmodifiable.set(LinkedHashSet::new));
+    }
+
+    @SuppressWarnings("unchecked")
+    private Supplier<List<Class<? extends ConstraintValidator<A, ?>>>> computeConstraintValidatorClasses(
+        ApacheValidatorFactory validatorFactory) {
+        return () -> validatorFactory.getConstraintsCache()
+            .getConstraintValidatorClasses((Class<A>) annotation.annotationType());
+    }
+
+    private ValidateUnwrappedValue computeValidateUnwrappedValue() {
+        final Set<Class<? extends Payload>> p = getPayload();
+        final boolean unwrap = p.contains(Unwrap.class);
+        final boolean skip = p.contains(Skip.class);
+        if (unwrap) {
+            Validate.validState(!skip, "Cannot specify both %s and %s", Unwrap.class.getSimpleName(),
+                Skip.class.getSimpleName());
+            return ValidateUnwrappedValue.UNWRAP;
+        }
+        return skip ? ValidateUnwrappedValue.SKIP : ValidateUnwrappedValue.DEFAULT;
+    }
+
+    private Set<Class<?>> computeGroups() {
+        final boolean required = true;
+        final Class<?>[] groups = read(ConstraintAnnotationAttributes.GROUPS, required);
+        if (groups.length == 0) {
+            return Collections.singleton(Default.class);
+        }
+        return set(() -> groups);
+    }
+
+    private Set<Class<? extends Payload>> computePayload() {
+        final boolean required = true;
+        final Set<Class<? extends Payload>> result = set(() -> read(ConstraintAnnotationAttributes.PAYLOAD, required));
+        Exceptions.raiseIf(result.containsAll(Arrays.asList(Unwrapping.Unwrap.class, Unwrapping.Skip.class)),
+            ConstraintDeclarationException::new,
+            "Constraint %s declared at %s specifies conflicting value unwrapping hints", annotation, meta.getHost());
+        return result;
+    }
+
+    private Class<?> computeValidatedType(ApacheValidatorFactory validatorFactory) {
+        final Class<?> rawType = TypeUtils.getRawType(meta.getType(), null);
+
+        Exceptions.raiseIf(rawType == null, UnexpectedTypeException::new, "Could not calculate validated type from %s",
+            meta.getType());
+
+        if (payload.contains(Unwrapping.Skip.class)) {
+            return rawType;
+        }
+        final ValueExtractor<?> valueExtractor =
+            validatorFactory.getValueExtractors().find(new ContainerElementKey(meta.getAnnotatedType(), null));
+
+        final boolean unwrap = payload.contains(Unwrapping.Unwrap.class);
+
+        if (valueExtractor == null) {
+            Exceptions.raiseIf(unwrap, ConstraintDeclarationException::new, "No compatible %s found for %s",
+                ValueExtractor.class.getSimpleName(), meta.getType());
+        } else {
+            @SuppressWarnings("unchecked")
+            final Class<? extends ValueExtractor<?>> extractorClass =
+                (Class<? extends ValueExtractor<?>>) valueExtractor.getClass();
+            if (unwrap || extractorClass.isAnnotationPresent(UnwrapByDefault.class)) {
+                return ValueExtractors.getExtractedType(valueExtractor, meta.getType());
+            }
+        }
+        return rawType;
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ConstructorD.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ConstructorD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ConstructorD.java
new file mode 100644
index 0000000..b89d29c
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ConstructorD.java
@@ -0,0 +1,41 @@
+/*
+ * 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.jsr.descriptor;
+
+import java.lang.reflect.Constructor;
+
+import javax.validation.metadata.ConstructorDescriptor;
+
+public class ConstructorD extends ExecutableD<Constructor<?>, MetadataReader.ForConstructor, ConstructorD>
+    implements ConstructorDescriptor {
+
+    ConstructorD(MetadataReader.ForConstructor reader, BeanD parent) {
+        super(reader, parent);
+    }
+
+    @Override
+    public Class<?> getElementClass() {
+        return getParent().getElementClass();
+    }
+
+    @Override
+    protected String nameOf(Constructor<?> e) {
+        return e.getDeclaringClass().getSimpleName();
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ContainerElementTypeD.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ContainerElementTypeD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ContainerElementTypeD.java
new file mode 100644
index 0000000..7cacff3
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ContainerElementTypeD.java
@@ -0,0 +1,119 @@
+/*
+ * 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.jsr.descriptor;
+
+import java.lang.reflect.AnnotatedType;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+import javax.validation.ValidationException;
+import javax.validation.metadata.ContainerElementTypeDescriptor;
+import javax.validation.valueextraction.ValueExtractor;
+
+import org.apache.bval.jsr.GraphContext;
+import org.apache.bval.jsr.metadata.ContainerElementKey;
+import org.apache.bval.jsr.util.NodeImpl;
+import org.apache.bval.util.Exceptions;
+import org.apache.bval.util.Lazy;
+import org.apache.bval.util.Validate;
+
+public class ContainerElementTypeD extends CascadableContainerD<CascadableContainerD<?, ?>, AnnotatedType>
+    implements ContainerElementTypeDescriptor {
+
+    private static class Receiver implements ValueExtractor.ValueReceiver {
+        private final GraphContext context;
+        private Lazy<List<GraphContext>> result = new Lazy<>(ArrayList::new);
+
+        Receiver(GraphContext context) {
+            super();
+            this.context = context;
+        }
+
+        @Override
+        public void value(String nodeName, Object object) {
+            addChild(new NodeImpl.PropertyNodeImpl(nodeName), object);
+        }
+
+        @Override
+        public void iterableValue(String nodeName, Object object) {
+            final NodeImpl.PropertyNodeImpl node = new NodeImpl.PropertyNodeImpl(nodeName);
+            node.setInIterable(true);
+            addChild(node, object);
+        }
+
+        @Override
+        public void indexedValue(String nodeName, int i, Object object) {
+            final NodeImpl.PropertyNodeImpl node = new NodeImpl.PropertyNodeImpl(nodeName);
+            node.setIndex(Integer.valueOf(i));
+            addChild(node, object);
+        }
+
+        @Override
+        public void keyedValue(String nodeName, Object key, Object object) {
+            final NodeImpl.PropertyNodeImpl node = new NodeImpl.PropertyNodeImpl(nodeName);
+            node.setKey(key);
+            addChild(node, object);
+        }
+
+        private void addChild(NodeImpl node, Object value) {
+            result.get().add(context.child(node, value));
+        }
+    }
+
+    private final ContainerElementKey key;
+
+    ContainerElementTypeD(ContainerElementKey key, MetadataReader.ForContainer<AnnotatedType> reader,
+        CascadableContainerD<?, ?> parent) {
+        super(reader, parent);
+        this.key = Validate.notNull(key, "key");
+    }
+
+    @Override
+    public Class<?> getContainerClass() {
+        return key.getContainerClass();
+    }
+
+    @Override
+    public Integer getTypeArgumentIndex() {
+        return Integer.valueOf(key.getTypeArgumentIndex());
+    }
+
+    public ContainerElementKey getKey() {
+        return key;
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Override
+    protected Stream<GraphContext> readImpl(GraphContext context) throws Exception {
+        final ValueExtractor valueExtractor = context.getValidatorContext().getValueExtractors().find(key);
+        Exceptions.raiseIf(valueExtractor == null, ValidationException::new, "No %s found for %s",
+            ValueExtractor.class.getSimpleName(), key);
+
+        final Receiver receiver = new Receiver(context);
+        try {
+            valueExtractor.extractValues(context.getValue(), receiver);
+        } catch (ValidationException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new ValidationException(e);
+        }
+        return receiver.result.get().stream();
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/CrossParameterD.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/CrossParameterD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/CrossParameterD.java
new file mode 100644
index 0000000..0d51800
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/CrossParameterD.java
@@ -0,0 +1,18 @@
+package org.apache.bval.jsr.descriptor;
+
+import java.lang.reflect.Executable;
+
+import javax.validation.metadata.CrossParameterDescriptor;
+
+public class CrossParameterD<P extends ExecutableD<?, ?, P>, E extends Executable>
+    extends ElementD.NonRoot<P, E, MetadataReader.ForElement<E, ?>> implements CrossParameterDescriptor {
+
+    protected CrossParameterD(MetadataReader.ForElement<E, ?> reader, P parent) {
+        super(reader, parent);
+    }
+
+    @Override
+    public Class<?> getElementClass() {
+        return Object[].class;
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java
new file mode 100644
index 0000000..21816d7
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java
@@ -0,0 +1,74 @@
+/*
+ *  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.jsr.descriptor;
+
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.validation.metadata.BeanDescriptor;
+import javax.validation.metadata.CascadableDescriptor;
+import javax.validation.metadata.ElementDescriptor;
+
+import org.apache.bval.jsr.ApacheValidatorFactory;
+import org.apache.bval.jsr.metadata.AnnotationBehaviorMergeStrategy;
+import org.apache.bval.jsr.metadata.CompositeBuilder;
+import org.apache.bval.jsr.metadata.HierarchyBuilder;
+import org.apache.bval.jsr.metadata.MetadataBuilder;
+import org.apache.bval.jsr.metadata.DualBuilder;
+import org.apache.bval.jsr.metadata.ReflectionBuilder;
+import org.apache.bval.util.Validate;
+
+public class DescriptorManager {
+    public static <D extends ElementDescriptor & CascadableDescriptor> boolean isConstrained(D descriptor) {
+        return descriptor.hasConstraints() || descriptor.isCascaded();
+    }
+
+    private final ApacheValidatorFactory validatorFactory;
+    private final ConcurrentMap<Class<?>, BeanD> beanDescriptors = new ConcurrentHashMap<>();
+    private final ReflectionBuilder reflectionBuilder;
+    private final MetadataReader metadataReader;
+
+    public DescriptorManager(ApacheValidatorFactory validatorFactory) {
+        super();
+        this.validatorFactory = Validate.notNull(validatorFactory, "validatorFactory");
+        this.reflectionBuilder = new ReflectionBuilder(validatorFactory);
+        this.metadataReader = new MetadataReader(validatorFactory);
+    }
+
+    public BeanDescriptor getBeanDescriptor(Class<?> beanClass) {
+        Validate.notNull(beanClass, IllegalArgumentException::new, "beanClass");
+        return beanDescriptors.computeIfAbsent(beanClass, k -> new BeanD(metadataReader.forBean(k, builder(k))));
+    }
+
+    private MetadataBuilder.ForBean builder(Class<?> beanClass) {
+        final MetadataBuilder.ForBean primaryBuilder =
+            new HierarchyBuilder(reflectionBuilder::forBean).forBean(beanClass);
+
+        final MetadataBuilder.ForBean customBuilder = new HierarchyBuilder(this::customBuilder).forBean(beanClass);
+
+        return customBuilder.isEmpty() ? primaryBuilder : DualBuilder.forBean(primaryBuilder, customBuilder);
+    }
+
+    private MetadataBuilder.ForBean customBuilder(Class<?> beanClass) {
+        final List<MetadataBuilder.ForBean> customBuilders =
+            validatorFactory.getMetadataBuilders().getCustomBuilders(beanClass);
+
+        return customBuilders.isEmpty() ? null : customBuilders.stream()
+            .collect(CompositeBuilder.with(AnnotationBehaviorMergeStrategy.consensus()).compose());
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ElementD.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ElementD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ElementD.java
new file mode 100644
index 0000000..c139773
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ElementD.java
@@ -0,0 +1,122 @@
+/*
+ * 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.jsr.descriptor;
+
+import java.lang.annotation.ElementType;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.validation.metadata.ConstraintDescriptor;
+import javax.validation.metadata.ElementDescriptor;
+
+import org.apache.bval.util.Validate;
+import org.apache.bval.util.reflection.TypeUtils;
+
+public abstract class ElementD<E extends AnnotatedElement, R extends MetadataReader.ForElement<E, ?>>
+    implements ElementDescriptor {
+
+    public static abstract class NonRoot<P extends ElementD<?, ?>, E extends AnnotatedElement, R extends MetadataReader.ForElement<E, ?>>
+        extends ElementD<E, R> {
+
+        protected final P parent;
+
+        protected NonRoot(R reader, P parent) {
+            super(reader);
+            this.parent = Validate.notNull(parent, "parent");
+        }
+
+        public P getParent() {
+            return parent;
+        }
+
+        @Override
+        public final Type getGenericType() {
+            if (TypeUtils.containsTypeVariables(genericType)) {
+                final Map<TypeVariable<?>, Type> args =
+                    TypeUtils.getTypeArguments(parent.getGenericType(), Object.class);
+                return TypeUtils.unrollVariables(args, genericType);
+            }
+            return genericType;
+        }
+
+        @Override
+        final protected BeanD getBean() {
+            return parent.getBean();
+        }
+
+        @Override
+        public final List<Class<?>> getGroupSequence() {
+            return getBean().getGroupSequence();
+        }
+    }
+
+    protected final Type genericType;
+
+    private final E target;
+    private final ElementType elementType;
+    private final Set<ConstraintD<?>> constraints;
+
+    protected ElementD(R reader) {
+        super();
+        Validate.notNull(reader, "reader");
+        this.genericType = reader.meta.getType();
+        this.target = reader.meta.getHost();
+        this.elementType = reader.meta.getElementType();
+        this.constraints = reader.getConstraints();
+    }
+
+    @Override
+    public final boolean hasConstraints() {
+        return !constraints.isEmpty();
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Override
+    public final Set<ConstraintDescriptor<?>> getConstraintDescriptors() {
+        return (Set) constraints;
+    }
+
+    @Override
+    public final ConstraintFinder findConstraints() {
+        return new Finder(this);
+    }
+
+    public final ElementType getElementType() {
+        return elementType;
+    }
+
+    public final E getTarget() {
+        return target;
+    }
+
+    public abstract Type getGenericType();
+
+    public abstract List<Class<?>> getGroupSequence();
+
+    protected abstract BeanD getBean();
+
+    @Override
+    public String toString() {
+        return String.format("%s: %s", getClass().getSimpleName(), target);
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ExecutableD.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ExecutableD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ExecutableD.java
new file mode 100644
index 0000000..db3df6c
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ExecutableD.java
@@ -0,0 +1,84 @@
+/*
+ * 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.jsr.descriptor;
+
+import java.lang.reflect.Executable;
+import java.util.List;
+
+import javax.validation.metadata.CrossParameterDescriptor;
+import javax.validation.metadata.ExecutableDescriptor;
+import javax.validation.metadata.ParameterDescriptor;
+import javax.validation.metadata.ReturnValueDescriptor;
+
+public abstract class ExecutableD<E extends Executable, R extends MetadataReader.ForExecutable<E, R>, SELF extends ExecutableD<E, R, SELF>>
+    extends ElementD.NonRoot<BeanD, E, R> implements ExecutableDescriptor {
+
+    private final String name;
+    private final ReturnValueD<SELF, E> returnValue;
+    private final List<ParameterD<SELF>> parameters;
+    private final CrossParameterD<SELF, E> crossParameter;
+
+    @SuppressWarnings("unchecked")
+    protected ExecutableD(R reader, BeanD parent) {
+        super(reader, parent);
+
+        name = reader.meta.getName();
+
+        returnValue = reader.getReturnValueDescriptor((SELF) this);
+        parameters = reader.getParameterDescriptors((SELF) this);
+        crossParameter = reader.getCrossParameterDescriptor((SELF) this);
+    }
+
+    @Override
+    public final String getName() {
+        return name;
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Override
+    public final List<ParameterDescriptor> getParameterDescriptors() {
+        return (List) parameters;
+    }
+
+    @Override
+    public final CrossParameterDescriptor getCrossParameterDescriptor() {
+        return crossParameter;
+    }
+
+    @Override
+    public final ReturnValueDescriptor getReturnValueDescriptor() {
+        return returnValue;
+    }
+
+    @Override
+    public final boolean hasConstrainedParameters() {
+        return parameters.stream().anyMatch(this::isConstrained);
+    }
+
+    @Override
+    public final boolean hasConstrainedReturnValue() {
+        return isConstrained(returnValue);
+    }
+
+    protected abstract String nameOf(E e);
+
+    private boolean isConstrained(CascadableContainerD<?, ?> child) {
+        return child.isCascaded() || child.hasConstraints();
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/Finder.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/Finder.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/Finder.java
new file mode 100644
index 0000000..c01e069
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/Finder.java
@@ -0,0 +1,103 @@
+/*
+ * 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.jsr.descriptor;
+
+import java.lang.annotation.ElementType;
+import java.util.EnumSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.validation.groups.Default;
+import javax.validation.metadata.ConstraintDescriptor;
+import javax.validation.metadata.ElementDescriptor;
+import javax.validation.metadata.ElementDescriptor.ConstraintFinder;
+import javax.validation.metadata.Scope;
+
+import org.apache.bval.jsr.util.ToUnmodifiable;
+import org.apache.bval.util.Validate;
+
+class Finder implements ConstraintFinder, Supplier<Stream<ConstraintD<?>>> {
+    private Predicate<ConstraintD<?>> groups = c -> true;
+    private Predicate<ConstraintD<?>> scope;
+    private Predicate<ConstraintD<?>> elements;
+
+    private final ElementDescriptor owner;
+
+    Finder(ElementDescriptor owner) {
+        this.owner = Validate.notNull(owner, "owner");
+    }
+
+    @Override
+    public ConstraintFinder unorderedAndMatchingGroups(Class<?>... groups) {
+        this.groups = c -> Stream.of(groups).anyMatch(t -> {
+            final Set<Class<?>> constraintGroups = c.getGroups();
+            return constraintGroups.contains(t)
+                || constraintGroups.contains(Default.class) && c.getDeclaringClass().isAssignableFrom(t);
+        });
+        return this;
+    }
+
+    @Override
+    public ConstraintFinder lookingAt(Scope scope) {
+        this.scope = scope == Scope.HIERARCHY ? null : c -> c.getScope() == scope;
+        return this;
+    }
+
+    @Override
+    public ConstraintFinder declaredOn(ElementType... types) {
+        this.elements = c -> Stream.of(types).filter(Objects::nonNull)
+            .collect(Collectors.toCollection(() -> EnumSet.noneOf(ElementType.class))).contains(c.getDeclaredOn());
+
+        return this;
+    }
+
+    @Override
+    public Set<ConstraintDescriptor<?>> getConstraintDescriptors() {
+        return get().collect(ToUnmodifiable.set());
+    }
+
+    @Override
+    public Stream<ConstraintD<?>> get() {
+        return getConstraints().filter(filter());
+    }
+
+    @Override
+    public boolean hasConstraints() {
+        return getConstraints().anyMatch(filter());
+    }
+
+    private Stream<ConstraintD<?>> getConstraints() {
+        return owner.getConstraintDescriptors().stream().<ConstraintD<?>> map(c -> c.unwrap(ConstraintD.class));
+    }
+
+    private Predicate<ConstraintD<?>> filter() {
+        Predicate<ConstraintD<?>> result = groups;
+        if (scope != null) {
+            result = result.and(scope);
+        }
+        if (elements != null) {
+            result = result.and(elements);
+        }
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/GroupConversion.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/GroupConversion.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/GroupConversion.java
new file mode 100644
index 0000000..9ef724e
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/GroupConversion.java
@@ -0,0 +1,85 @@
+/*
+ * 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.jsr.descriptor;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import javax.validation.metadata.GroupConversionDescriptor;
+
+import org.apache.bval.util.Lazy;
+import org.apache.bval.util.LazyInt;
+import org.apache.bval.util.Validate;
+
+public class GroupConversion implements GroupConversionDescriptor {
+    public static class Builder {
+        private final Class<?> from;
+
+        private Builder(Class<?> from) {
+            this.from = from;
+        }
+
+        public GroupConversion to(Class<?> to) {
+            return new GroupConversion(from, to);
+        }
+    }
+
+    public static Builder from(Class<?> from) {
+        return new Builder(from);
+    }
+
+    private final Class<?> from;
+    private final Class<?> to;
+    private final LazyInt hashCode;
+    private final Lazy<String> toString;
+
+    private GroupConversion(Class<?> from, Class<?> to) {
+        super();
+        this.from = Validate.notNull(from, "from");
+        this.to = Validate.notNull(to, "to");
+        this.hashCode = new LazyInt(() -> Objects.hash(this.from, this.to));
+        this.toString = new Lazy<>(
+            () -> String.format("%s from %s to %s", GroupConversion.class.getSimpleName(), this.from, this.to));
+    }
+
+    @Override
+    public Class<?> getFrom() {
+        return from;
+    }
+
+    @Override
+    public Class<?> getTo() {
+        return to;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return obj == this
+            || Optional.ofNullable(obj).filter(GroupConversion.class::isInstance).map(GroupConversion.class::cast)
+                .filter(gc -> Objects.equals(from, gc.from) && Objects.equals(to, gc.to)).isPresent();
+    }
+
+    @Override
+    public int hashCode() {
+        return hashCode.getAsInt();
+    }
+
+    @Override
+    public String toString() {
+        return toString.get();
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java
new file mode 100644
index 0000000..c2f9f0c
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java
@@ -0,0 +1,291 @@
+/*
+ * 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.jsr.descriptor;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.AnnotatedType;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import javax.validation.GroupDefinitionException;
+import javax.validation.GroupSequence;
+import javax.validation.ParameterNameProvider;
+import javax.validation.groups.Default;
+import javax.validation.metadata.PropertyDescriptor;
+import javax.validation.metadata.Scope;
+
+import org.apache.bval.jsr.ApacheValidatorFactory;
+import org.apache.bval.jsr.metadata.ContainerElementKey;
+import org.apache.bval.jsr.metadata.EmptyBuilder;
+import org.apache.bval.jsr.metadata.MetadataBuilder;
+import org.apache.bval.jsr.metadata.Metas;
+import org.apache.bval.jsr.metadata.Signature;
+import org.apache.bval.jsr.util.Methods;
+import org.apache.bval.jsr.util.ToUnmodifiable;
+import org.apache.bval.util.Exceptions;
+import org.apache.bval.util.Validate;
+import org.apache.bval.util.reflection.Reflection;
+
+class MetadataReader {
+
+    class ForElement<E extends AnnotatedElement, B extends MetadataBuilder.ForElement<E>> {
+        final Metas<E> meta;
+        protected final B builder;
+
+        ForElement(Metas<E> meta, B builder) {
+            super();
+            this.meta = Validate.notNull(meta, "meta");
+            this.builder = Validate.notNull(builder, "builder");
+        }
+
+        Set<ConstraintD<?>> getConstraints() {
+            return builder.getConstraintsByScope(meta).entrySet().stream()
+                .flatMap(e -> describe(e.getValue(), e.getKey(), meta)).collect(ToUnmodifiable.set());
+        }
+
+        private Stream<ConstraintD<?>> describe(Annotation[] constraints, Scope scope, Metas<?> meta) {
+            return Stream.of(constraints).map(c -> new ConstraintD<>(c, scope, meta, validatorFactory));
+        }
+    }
+
+    class ForBean extends MetadataReader.ForElement<Class<?>, MetadataBuilder.ForClass> {
+        private final MetadataBuilder.ForBean beanBuilder;
+
+        ForBean(Metas<Class<?>> meta, MetadataBuilder.ForBean builder) {
+            super(meta, Validate.notNull(builder, "builder").getClass(meta));
+            this.beanBuilder = builder;
+        }
+
+        Map<String, PropertyDescriptor> getProperties(BeanD parent) {
+            final Map<String, List<PropertyD<?>>> properties = new LinkedHashMap<>();
+            final Function<? super String, ? extends List<PropertyD<?>>> descriptorList = k -> new ArrayList<>();
+
+            beanBuilder.getFields(meta).forEach((f, builder) -> {
+                final Field fld = Reflection.find(meta.getHost(), t -> Reflection.getDeclaredField(t, f));
+                properties.computeIfAbsent(f, descriptorList).add(new PropertyD.ForField(
+                    new MetadataReader.ForContainer<>(new Metas.ForField(fld), builder), parent));
+            });
+
+            beanBuilder.getGetters(meta).forEach((g, builder) -> {
+                final Method getter = Reflection.find(meta.getHost(), t -> {
+                    return Stream.of(Reflection.getDeclaredMethods(t)).filter(Methods::isGetter)
+                        .filter(m -> g.equals(Methods.propertyName(m))).findFirst().orElse(null);
+                });
+                Exceptions.raiseIf(getter == null, IllegalStateException::new,
+                    "Getter method for property %s not found", g);
+
+                properties.computeIfAbsent(g, descriptorList).add(new PropertyD.ForMethod(
+                    new MetadataReader.ForContainer<>(new Metas.ForMethod(getter), builder), parent));
+            });
+            return properties.entrySet().stream().collect(ToUnmodifiable.map(Map.Entry::getKey, e -> {
+                final List<PropertyD<?>> delegates = e.getValue();
+
+                if (delegates.size() == 1) {
+                    return delegates.get(0);
+                }
+                final Set<PropertyD<?>> constrained =
+                    delegates.stream().filter(DescriptorManager::isConstrained).collect(Collectors.toSet());
+                if (constrained.isEmpty()) {
+                    return delegates.get(0);
+                }
+                if (constrained.size() == 1) {
+                    return constrained.iterator().next();
+                }
+                return new ComposedD.ForProperty(delegates);
+            }));
+        }
+
+        Map<Signature, MethodD> getMethods(BeanD parent) {
+            final Map<Signature, MetadataBuilder.ForExecutable<Method>> methodBuilders = beanBuilder.getMethods(meta);
+            if (methodBuilders.isEmpty()) {
+                return Collections.emptyMap();
+            }
+            final Map<Signature, MethodD> result = new LinkedHashMap<>();
+
+            methodBuilders.forEach((sig, builder) -> {
+                final Method m = Reflection.find(meta.getHost(),
+                    t -> Reflection.getDeclaredMethod(t, sig.getName(), sig.getParameterTypes()));
+
+                result.put(sig, new MethodD(new MetadataReader.ForMethod(new Metas.ForMethod(m), builder), parent));
+            });
+            return Collections.unmodifiableMap(result);
+        }
+
+        Map<Signature, ConstructorD> getConstructors(BeanD parent) {
+            final Map<Signature, MetadataBuilder.ForExecutable<Constructor<?>>> ctorBuilders =
+                beanBuilder.getConstructors(meta);
+
+            if (ctorBuilders.isEmpty()) {
+                return Collections.emptyMap();
+            }
+            final Map<Signature, ConstructorD> result = new LinkedHashMap<>();
+
+            ctorBuilders.forEach((sig, builder) -> {
+                final Constructor<?> c = Reflection.getDeclaredConstructor(meta.getHost(), sig.getParameterTypes());
+                result.put(sig,
+                    new ConstructorD(new MetadataReader.ForConstructor(new Metas.ForConstructor(c), builder), parent));
+            });
+            return Collections.unmodifiableMap(result);
+        }
+
+        List<Class<?>> getGroupSequence() {
+            List<Class<?>> result = builder.getGroupSequence(meta);
+            if (result == null) {
+                // resolve group sequence/Default redefinition up class hierarchy:
+                final Class<?> superclass = meta.getHost().getSuperclass();
+                if (superclass != null) {
+                    // attempt to mock parent sequence intent by appending this type immediately after supertype:
+                    result = ((ElementD<?, ?>) validatorFactory.getDescriptorManager().getBeanDescriptor(superclass))
+                        .getGroupSequence();
+                    if (result != null) {
+                        result = new ArrayList<>(result);
+                        result.add(result.indexOf(superclass) + 1, meta.getHost());
+                    }
+                }
+            }
+            if (result != null) {
+                Exceptions.raiseUnless(result.contains(meta.getHost()), GroupDefinitionException::new,
+                    "@%s for %s must contain %<s", GroupSequence.class.getSimpleName(), meta.getHost());
+                Exceptions.raiseIf(result.contains(Default.class), GroupDefinitionException::new,
+                    "@%s for %s must not contain %s", GroupSequence.class.getSimpleName(), meta.getHost(),
+                    Default.class.getName());
+            }
+            return result;
+        }
+    }
+
+    class ForContainer<E extends AnnotatedElement> extends ForElement<E, MetadataBuilder.ForContainer<E>> {
+
+        ForContainer(Metas<E> meta, MetadataBuilder.ForContainer<E> builder) {
+            super(meta, builder);
+        }
+
+        boolean isCascaded() {
+            return builder.isCascade(meta);
+        }
+
+        Set<GroupConversion> getGroupConversions() {
+            return builder.getGroupConversions(meta);
+        }
+
+        Set<ContainerElementTypeD> getContainerElementTypes(CascadableContainerD<?, ?> parent) {
+            final Map<ContainerElementKey, MetadataBuilder.ForContainer<AnnotatedType>> containerElementTypes =
+                builder.getContainerElementTypes(meta);
+
+            if (containerElementTypes.isEmpty()) {
+                return Collections.emptySet();
+            }
+            final Set<ContainerElementTypeD> result =
+                new TreeSet<>(Comparator.comparing(ContainerElementTypeD::getKey));
+
+            containerElementTypes.forEach((k, builder) -> {
+                result.add(new ContainerElementTypeD(k,
+                    new MetadataReader.ForContainer<>(new Metas.ForContainerElement(meta, k), builder), parent));
+            });
+            return Collections.unmodifiableSet(result);
+        }
+    }
+
+    abstract class ForExecutable<E extends Executable, SELF extends ForExecutable<E, SELF>>
+        extends ForElement<E, MetadataBuilder.ForElement<E>> {
+        private final MetadataBuilder.ForExecutable<E> executableBuilder;
+
+        ForExecutable(Metas<E> meta, MetadataBuilder.ForExecutable<E> executableBuilder) {
+            super(meta, EmptyBuilder.instance().forElement());
+            this.executableBuilder = Validate.notNull(executableBuilder, "executableBuilder");
+        }
+
+        <X extends ExecutableD<E, SELF, X>> List<ParameterD<X>> getParameterDescriptors(X parent) {
+            final Parameter[] parameters = meta.getHost().getParameters();
+
+            final List<String> parameterNames =
+                getParameterNames(validatorFactory.getParameterNameProvider(), meta.getHost());
+
+            final List<MetadataBuilder.ForContainer<Parameter>> builders = executableBuilder.getParameters(meta);
+
+            return IntStream.range(0, parameters.length).mapToObj(i -> {
+                final Metas.ForParameter param = new Metas.ForParameter(parameters[i], parameterNames.get(i));
+                return new ParameterD<>(param, i, new MetadataReader.ForContainer<>(param, builders.get(i)), parent);
+            }).collect(ToUnmodifiable.list());
+        }
+
+        <X extends ExecutableD<E, SELF, X>> CrossParameterD<X, E> getCrossParameterDescriptor(X parent) {
+            final Metas.ForCrossParameter<E> cp = new Metas.ForCrossParameter<>(meta);
+            return new CrossParameterD<>(new MetadataReader.ForElement<>(cp, executableBuilder.getCrossParameter(cp)),
+                parent);
+        }
+
+        <X extends ExecutableD<E, SELF, X>> ReturnValueD<X, E> getReturnValueDescriptor(X parent) {
+            return new ReturnValueD<>(new MetadataReader.ForContainer<>(meta, executableBuilder.getReturnValue(meta)),
+                parent);
+        }
+
+        abstract List<String> getParameterNames(ParameterNameProvider parameterNameProvider, E host);
+    }
+
+    class ForMethod extends ForExecutable<Method, ForMethod> {
+        ForMethod(Metas<Method> meta, MetadataBuilder.ForExecutable<Method> builder) {
+            super(meta, builder);
+        }
+
+        @Override
+        List<String> getParameterNames(ParameterNameProvider parameterNameProvider, Method host) {
+            return parameterNameProvider.getParameterNames(host);
+        }
+    }
+
+    class ForConstructor extends ForExecutable<Constructor<?>, ForConstructor> {
+
+        ForConstructor(Metas<Constructor<?>> meta, MetadataBuilder.ForExecutable<Constructor<?>> builder) {
+            super(meta, builder);
+        }
+
+        @Override
+        List<String> getParameterNames(ParameterNameProvider parameterNameProvider, Constructor<?> host) {
+            return parameterNameProvider.getParameterNames(host);
+        }
+    }
+
+    private final ApacheValidatorFactory validatorFactory;
+
+    MetadataReader(ApacheValidatorFactory validatorFactory) {
+        super();
+        this.validatorFactory = Validate.notNull(validatorFactory, "validatorFactory");
+    }
+
+    MetadataReader.ForBean forBean(Class<?> beanClass, MetadataBuilder.ForBean builder) {
+        return new MetadataReader.ForBean(new Metas.ForClass(beanClass), builder);
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MethodD.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MethodD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MethodD.java
new file mode 100644
index 0000000..647569d
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MethodD.java
@@ -0,0 +1,49 @@
+/*
+ * 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.jsr.descriptor;
+
+import java.lang.reflect.Method;
+
+import javax.validation.metadata.MethodDescriptor;
+import javax.validation.metadata.MethodType;
+
+import org.apache.bval.jsr.util.Methods;
+
+class MethodD extends ExecutableD<Method, MetadataReader.ForMethod, MethodD> implements MethodDescriptor {
+    private final MethodType methodType;
+
+    MethodD(MetadataReader.ForMethod reader, BeanD parent) {
+        super(reader, parent);
+        methodType = Methods.isGetter(reader.meta.getHost()) ? MethodType.GETTER : MethodType.NON_GETTER;
+    }
+
+    @Override
+    public Class<?> getElementClass() {
+        return getTarget().getReturnType();
+    }
+
+    MethodType getMethodType() {
+        return methodType;
+    }
+
+    @Override
+    protected String nameOf(Method e) {
+        return e.getName();
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ParameterD.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ParameterD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ParameterD.java
new file mode 100644
index 0000000..951eccb
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ParameterD.java
@@ -0,0 +1,62 @@
+/*
+ * 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.jsr.descriptor;
+
+import java.lang.reflect.Parameter;
+
+import javax.validation.metadata.ParameterDescriptor;
+
+import org.apache.bval.jsr.metadata.Metas;
+import org.apache.bval.util.Validate;
+import org.apache.bval.util.reflection.TypeUtils;
+
+public class ParameterD<P extends ExecutableD<?, ?, P>> extends CascadableContainerD<P, Parameter>
+    implements ParameterDescriptor {
+
+    private final int index;
+    private final String name;
+    private final Class<?> type;
+
+    protected ParameterD(Metas.ForParameter meta, int index, MetadataReader.ForContainer<Parameter> reader, P parent) {
+        super(reader, parent);
+
+        Validate.isTrue(index >= 0 && index < meta.getHost().getDeclaringExecutable().getParameterCount(),
+            "Invalid parameter index %d", index);
+
+        this.index = index;
+
+        name = reader.meta.getName();
+        type = TypeUtils.getRawType(reader.meta.getType(), parent.getElementClass());
+    }
+
+    @Override
+    public Class<?> getElementClass() {
+        return type;
+    }
+
+    @Override
+    public int getIndex() {
+        return index;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/PropertyD.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/PropertyD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/PropertyD.java
new file mode 100644
index 0000000..818e7e0
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/PropertyD.java
@@ -0,0 +1,106 @@
+/*
+ *  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.jsr.descriptor;
+
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+import javax.validation.metadata.PropertyDescriptor;
+
+import org.apache.bval.jsr.GraphContext;
+import org.apache.bval.jsr.util.Methods;
+import org.apache.bval.jsr.util.NodeImpl;
+import org.apache.bval.util.reflection.Reflection;
+import org.apache.commons.weaver.privilizer.Privilizing;
+import org.apache.commons.weaver.privilizer.Privilizing.CallTo;
+
+@Privilizing(@CallTo(Reflection.class))
+public abstract class PropertyD<E extends AnnotatedElement> extends CascadableContainerD<BeanD, E>
+    implements PropertyDescriptor {
+
+    static class ForField extends PropertyD<Field> {
+
+        ForField(MetadataReader.ForContainer<Field> reader, BeanD parent) {
+            super(reader, parent);
+        }
+
+        @Override
+        public String getPropertyName() {
+            return host.getName();
+        }
+
+        @Override
+        public Object getValue(Object parent) throws Exception {
+            final boolean mustUnset = Reflection.setAccessible(host, true);
+            try {
+                return host.get(parent);
+            } catch (IllegalAccessException e) {
+                throw new IllegalArgumentException(e);
+            } finally {
+                if (mustUnset) {
+                    Reflection.setAccessible(host, false);
+                }
+            }
+        }
+    }
+
+    static class ForMethod extends PropertyD<Method> {
+
+        ForMethod(MetadataReader.ForContainer<Method> reader, BeanD parent) {
+            super(reader, parent);
+        }
+
+        @Override
+        public String getPropertyName() {
+            return Methods.propertyName(host);
+        }
+
+        @Override
+        public Object getValue(Object parent) throws Exception {
+            final boolean mustUnset = Reflection.setAccessible(host, true);
+            try {
+                return host.invoke(parent);
+            } catch (IllegalAccessException | InvocationTargetException e) {
+                throw new IllegalArgumentException(e);
+            } finally {
+                if (mustUnset) {
+                    Reflection.setAccessible(host, false);
+                }
+            }
+        }
+    }
+
+    protected final E host;
+
+    protected PropertyD(MetadataReader.ForContainer<E> reader, BeanD parent) {
+        super(reader, parent);
+        this.host = reader.meta.getHost();
+    }
+
+    @Override
+    protected Stream<GraphContext> readImpl(GraphContext context) throws Exception {
+        final Supplier<NodeImpl> propertyNode = () -> new NodeImpl.PropertyNodeImpl(getPropertyName());
+        final Object value = getValue(context.getValue());
+        return Stream.of(context.child(propertyNode.get(), value));
+    }
+
+    public abstract Object getValue(Object parent) throws Exception;
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ReturnValueD.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ReturnValueD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ReturnValueD.java
new file mode 100644
index 0000000..a2204fc
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ReturnValueD.java
@@ -0,0 +1,36 @@
+/*
+ * 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.jsr.descriptor;
+
+import java.lang.reflect.Executable;
+
+import javax.validation.metadata.ReturnValueDescriptor;
+
+public class ReturnValueD<P extends ExecutableD<?, ?, P>, E extends Executable> extends CascadableContainerD<P, E>
+    implements ReturnValueDescriptor {
+
+    ReturnValueD(MetadataReader.ForContainer<E> reader, P parent) {
+        super(reader, parent);
+    }
+
+    @Override
+    public Class<?> getElementClass() {
+        return parent.getElementClass();
+    }
+}