swamirishi commented on code in PR #8404: URL: https://github.com/apache/ozone/pull/8404#discussion_r2103206949
########## hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/validation/RequestValidations.java: ########## @@ -56,15 +66,17 @@ public RequestValidations withinContext(ValidationContext validationContext) { } public synchronized RequestValidations load() { - registry = new ValidatorRegistry(validationsPackageName); + registry = new ValidatorRegistry<>(Type.class, validationsPackageName, + Arrays.stream(VersionExtractor.values()).map(VersionExtractor::getValidatorClass).collect(Collectors.toSet()), + ALLOWED_REQUEST_PROCESSING_PHASES); return this; } public OMRequest validateRequest(OMRequest request) throws Exception { - List<Method> validations = registry.validationsFor( - conditions(request), request.getCmdType(), PRE_PROCESS); + List<Method> validations = registry.validationsFor(request.getCmdType(), PRE_PROCESS, + this.getVersions(request)); Review Comment: done ########## hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/validation/ValidatorRegistry.java: ########## @@ -42,156 +47,274 @@ * Registry that loads and stores the request validators to be applied by * a service. */ -public class ValidatorRegistry { +public class ValidatorRegistry<RequestType extends Enum<RequestType>> { - private final EnumMap<ValidationCondition, - EnumMap<Type, EnumMap<RequestProcessingPhase, List<Method>>>> - validators = new EnumMap<>(ValidationCondition.class); + /** + * A validator registered should have the following parameters: + * applyBeforeVersion: Enum extending Version + * RequestType: Enum signifying the type of request. + * RequestProcessingPhase: Signifying if the validator is supposed to run pre or post submitting the request. + * Based on the afforementioned parameters a complete map is built which stores the validators in a sorted order of + * the applyBeforeVersion value of the validator method. + * Thus when a request comes with a certain version value, all validators containing `applyBeforeVersion` parameter + * greater than the request versions get triggered. + * {@link #validationsFor(Enum, RequestProcessingPhase, Class, Versioned)} + */ + private final Map<Class<? extends Versioned>, EnumMap<RequestType, + EnumMap<RequestProcessingPhase, IndexedItems<Method, Integer>>>> indexedValidatorMap; /** * Creates a {@link ValidatorRegistry} instance that discovers validation * methods in the provided package and the packages in the same resource. - * A validation method is recognized by the {@link RequestFeatureValidator} - * annotation that contains important information about how and when to use - * the validator. + * A validation method is recognized by all the annotations classes which + * are annotated by {@link RegisterValidator} annotation that contains + * important information about how and when to use the validator. + * @param requestType class of request type enum. * @param validatorPackage the main package inside which validatiors should * be discovered. + * @param allowedValidators a set containing the various types of version allowed to be registered. + * @param allowedProcessingPhases set of request processing phases which would be allowed to be registered to + * registry. + * */ - ValidatorRegistry(String validatorPackage) { - this(ClasspathHelper.forPackage(validatorPackage)); + public ValidatorRegistry(Class<RequestType> requestType, + String validatorPackage, + Set<Class<? extends Annotation>> allowedValidators, + Set<RequestProcessingPhase> allowedProcessingPhases) { + this(requestType, ClasspathHelper.forPackage(validatorPackage), allowedValidators, allowedProcessingPhases); } /** * Creates a {@link ValidatorRegistry} instance that discovers validation * methods under the provided URL. - * A validation method is recognized by the {@link RequestFeatureValidator} + * A validation method is recognized by all annotations annotated by the {@link RegisterValidator} * annotation that contains important information about how and when to use * the validator. + * @param requestType class of request type enum. * @param searchUrls the path in which the annotated methods are searched. + * @param allowedValidators a set containing the various types of validator annotation allowed to be registered. + * @param allowedProcessingPhases set of request processing phases which would be allowed to be registered to + * registry. */ - ValidatorRegistry(Collection<URL> searchUrls) { + public ValidatorRegistry(Class<RequestType> requestType, + Collection<URL> searchUrls, + Set<Class<? extends Annotation>> allowedValidators, + Set<RequestProcessingPhase> allowedProcessingPhases) { + Class<RequestType[]> requestArrayClass = (Class<RequestType[]>) Array.newInstance(requestType, 0) + .getClass(); + Set<Class<? extends Annotation>> validatorsToBeRegistered = + new Reflections(new ConfigurationBuilder().setUrls(ClasspathHelper.forPackage("org.apache.hadoop")) + .setScanners(Scanners.TypesAnnotated) + .setParallel(true)).getTypesAnnotatedWith(RegisterValidator.class).stream() + .filter(allowedValidators::contains) + .filter(annotationClass -> getReturnTypeOfAnnotationMethod((Class<? extends Annotation>) annotationClass, + RegisterValidator.REQUEST_TYPE_METHOD_NAME) + .equals(requestArrayClass)) + .map(annotationClass -> (Class<? extends Annotation>) annotationClass) + .collect(Collectors.toSet()); + this.indexedValidatorMap = + allowedValidators.stream().collect(ImmutableMap.toImmutableMap(annotationClass -> + (Class<? extends Versioned>) getReturnTypeOfAnnotationMethod(annotationClass, + RegisterValidator.APPLY_BEFORE_METHOD_NAME), + validatorClass -> new EnumMap<>(requestType))); Reflections reflections = new Reflections(new ConfigurationBuilder() .setUrls(searchUrls) .setScanners(Scanners.MethodsAnnotated) .setParallel(true) ); - - Set<Method> describedValidators = - reflections.getMethodsAnnotatedWith(RequestFeatureValidator.class); - initMaps(describedValidators); + initMaps(requestArrayClass, allowedProcessingPhases, validatorsToBeRegistered, reflections); } /** - * Get the validators that has to be run in the given list of - * {@link ValidationCondition}s, for the given requestType and + * Get the validators that has to be run in the given list of, + * for the given requestType and for the given request versions. * {@link RequestProcessingPhase}. * - * @param conditions conditions that are present for the request * @param requestType the type of the protocol message * @param phase the request processing phase + * @param requestVersions different versions extracted from the request. * @return the list of validation methods that has to run. */ - List<Method> validationsFor( - List<ValidationCondition> conditions, - Type requestType, - RequestProcessingPhase phase) { - - if (conditions.isEmpty() || validators.isEmpty()) { - return Collections.emptyList(); - } - - Set<Method> returnValue = new HashSet<>(); - - for (ValidationCondition condition: conditions) { - returnValue.addAll(validationsFor(condition, requestType, phase)); - } - return new ArrayList<>(returnValue); + public List<Method> validationsFor(RequestType requestType, + RequestProcessingPhase phase, + Map<Class<? extends Annotation>, ? extends Versioned> requestVersions) { + return requestVersions.entrySet().stream() + .flatMap(requestVersion -> this.validationsFor(requestType, phase, requestVersion.getKey(), + requestVersion.getValue()).stream()) + .distinct().collect(Collectors.toList()); } /** - * Grabs validations for one particular condition. + * Get the validators that has to be run in the given list of, + * for the given requestType and for the given request versions. + * {@link RequestProcessingPhase}. * - * @param condition conditions that are present for the request * @param requestType the type of the protocol message * @param phase the request processing phase + * @param requestVersion version extracted corresponding to the request. * @return the list of validation methods that has to run. */ - private List<Method> validationsFor( - ValidationCondition condition, - Type requestType, - RequestProcessingPhase phase) { - - EnumMap<Type, EnumMap<RequestProcessingPhase, List<Method>>> - requestTypeMap = validators.get(condition); - if (requestTypeMap == null || requestTypeMap.isEmpty()) { - return Collections.emptyList(); + public <V extends Versioned> List<Method> validationsFor(RequestType requestType, + RequestProcessingPhase phase, + Class<? extends Annotation> validatorClass, + V requestVersion) { + + return Optional.ofNullable(this.indexedValidatorMap.get(requestVersion.getClass())) + .map(requestTypeMap -> requestTypeMap.get(requestType)) + .map(phaseMap -> phaseMap.get(phase)) + .map(indexedMethods -> requestVersion.version() < 0 ? + indexedMethods.getItemsEqualToIdx(requestVersion.version()) : + indexedMethods.getItemsGreaterThanIdx(requestVersion.version())) + .orElse(Collections.emptyList()); + + } + + /** + * Calls a specified method on the validator. + * @Throws IllegalArgumentException when the specified method in the validator is invalid. + */ + private <ReturnValue, Validator extends Annotation> ReturnValue callAnnotationMethod( + Validator validator, String methodName, Class<ReturnValue> returnValueType) { + try { + return (ReturnValue) validator.getClass().getMethod(methodName).invoke(validator); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("Method " + methodName + " not found in class:" + + validator.getClass().getCanonicalName(), e); + } catch (InvocationTargetException | IllegalAccessException e) { + throw new IllegalArgumentException("Error while invoking Method " + methodName + " from " + + validator.getClass().getCanonicalName(), e); } + } - EnumMap<RequestProcessingPhase, List<Method>> phases = - requestTypeMap.get(requestType); - if (phases == null) { - return Collections.emptyList(); + private Class<?> getReturnTypeOfAnnotationMethod(Class<? extends Annotation> clzz, String methodName) { + try { + return clzz.getMethod(methodName).getReturnType(); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("Method " + methodName + " not found in class:" + clzz.getCanonicalName()); } + } - List<Method> validatorsForPhase = phases.get(phase); - if (validatorsForPhase == null) { - return Collections.emptyList(); + private <Validator extends Annotation> Versioned getApplyBeforeVersion(Validator validator) { + return callAnnotationMethod(validator, RegisterValidator.APPLY_BEFORE_METHOD_NAME, Versioned.class); + } + + private <Validator extends Annotation> RequestProcessingPhase getRequestPhase(Validator validator) { + return callAnnotationMethod(validator, RegisterValidator.PROCESSING_PHASE_METHOD_NAME, + RequestProcessingPhase.class); + } + + private <Validator extends Annotation> RequestType[] getRequestType(Validator validator, + Class<RequestType[]> requestType) { + return callAnnotationMethod(validator, RegisterValidator.REQUEST_TYPE_METHOD_NAME, requestType); + } + + private <V> void checkAllowedAnnotationValues(Set<V> values, V value, String valueName, String methodName) { + if (!values.contains(value)) { + throw new IllegalArgumentException( + String.format("Invalid %1$s defined at annotation defined for method : %2$s, Annotation value : %3$s " + + "Allowed versionType: %4$s", valueName, methodName, value.toString(), values)); } - return validatorsForPhase; } /** * Initializes the internal request validator store. * The requests are stored in the following structure: - * - An EnumMap with the {@link ValidationCondition} as the key, and in which - * - values are an EnumMap with the request type as the key, and in which - * - values are Pair of lists, in which - * - left side is the pre-processing validations list - * - right side is the post-processing validations list - * @param describedValidators collection of the annotated methods to process. + * - An EnumMap with the RequestType as the key, and in which + * - values are an EnumMap with the request processing phase as the key, and in which + * - values is an {@link IndexedItems } containing the validation list + * @param validatorsToBeRegistered collection of the annotated validtors to process. */ - void initMaps(Collection<Method> describedValidators) { - for (Method m : describedValidators) { - RequestFeatureValidator descriptor = - m.getAnnotation(RequestFeatureValidator.class); - m.setAccessible(true); - - for (ValidationCondition condition : descriptor.conditions()) { - EnumMap<Type, EnumMap<RequestProcessingPhase, List<Method>>> - requestTypeMap = getAndInitialize( - condition, this::newTypeMap, validators); - EnumMap<RequestProcessingPhase, List<Method>> phases = getAndInitialize( - descriptor.requestType(), this::newPhaseMap, requestTypeMap); - if (isPreProcessValidator(descriptor)) { - getAndInitialize(PRE_PROCESS, ArrayList::new, phases).add(m); - } else if (isPostProcessValidator(descriptor)) { - getAndInitialize(POST_PROCESS, ArrayList::new, phases).add(m); - } - } + private void initMaps(Class<RequestType[]> requestType, + Set<RequestProcessingPhase> allowedPhases, + Collection<Class<? extends Annotation>> validatorsToBeRegistered, + Reflections reflections) { + for (Class<? extends Annotation> validator : validatorsToBeRegistered) { + registerValidator(requestType, allowedPhases, validator, reflections); } } - private EnumMap<Type, - EnumMap<RequestProcessingPhase, List<Method>>> newTypeMap() { - return new EnumMap<>(Type.class); + private void registerValidator(Class<RequestType[]> requestType, + Set<RequestProcessingPhase> allowedPhases, + Class<? extends Annotation> validatorToBeRegistered, + Reflections reflections) { + Collection<Method> methods = reflections.getMethodsAnnotatedWith(validatorToBeRegistered); + Class<? extends Versioned> versionClass = + (Class<? extends Versioned>) this.getReturnTypeOfAnnotationMethod(validatorToBeRegistered, + RegisterValidator.APPLY_BEFORE_METHOD_NAME); + List<Pair<? extends Annotation, Method>> sortedMethodsByApplyBeforeVersion = methods.stream() + .map(method -> Pair.of(method.getAnnotation(validatorToBeRegistered), method)) + .sorted((validatorMethodPair1, validatorMethodPair2) -> + Integer.compare( + this.getApplyBeforeVersion(validatorMethodPair1.getKey()).version(), + this.getApplyBeforeVersion(validatorMethodPair2.getKey()).version())) Review Comment: done ########## hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/validation/ValidatorRegistry.java: ########## @@ -42,156 +47,274 @@ * Registry that loads and stores the request validators to be applied by * a service. */ -public class ValidatorRegistry { +public class ValidatorRegistry<RequestType extends Enum<RequestType>> { - private final EnumMap<ValidationCondition, - EnumMap<Type, EnumMap<RequestProcessingPhase, List<Method>>>> - validators = new EnumMap<>(ValidationCondition.class); + /** + * A validator registered should have the following parameters: + * applyBeforeVersion: Enum extending Version + * RequestType: Enum signifying the type of request. + * RequestProcessingPhase: Signifying if the validator is supposed to run pre or post submitting the request. + * Based on the afforementioned parameters a complete map is built which stores the validators in a sorted order of + * the applyBeforeVersion value of the validator method. + * Thus when a request comes with a certain version value, all validators containing `applyBeforeVersion` parameter + * greater than the request versions get triggered. + * {@link #validationsFor(Enum, RequestProcessingPhase, Class, Versioned)} + */ + private final Map<Class<? extends Versioned>, EnumMap<RequestType, + EnumMap<RequestProcessingPhase, IndexedItems<Method, Integer>>>> indexedValidatorMap; /** * Creates a {@link ValidatorRegistry} instance that discovers validation * methods in the provided package and the packages in the same resource. - * A validation method is recognized by the {@link RequestFeatureValidator} - * annotation that contains important information about how and when to use - * the validator. + * A validation method is recognized by all the annotations classes which + * are annotated by {@link RegisterValidator} annotation that contains + * important information about how and when to use the validator. + * @param requestType class of request type enum. * @param validatorPackage the main package inside which validatiors should * be discovered. + * @param allowedValidators a set containing the various types of version allowed to be registered. + * @param allowedProcessingPhases set of request processing phases which would be allowed to be registered to + * registry. + * */ - ValidatorRegistry(String validatorPackage) { - this(ClasspathHelper.forPackage(validatorPackage)); + public ValidatorRegistry(Class<RequestType> requestType, + String validatorPackage, + Set<Class<? extends Annotation>> allowedValidators, + Set<RequestProcessingPhase> allowedProcessingPhases) { + this(requestType, ClasspathHelper.forPackage(validatorPackage), allowedValidators, allowedProcessingPhases); } /** * Creates a {@link ValidatorRegistry} instance that discovers validation * methods under the provided URL. - * A validation method is recognized by the {@link RequestFeatureValidator} + * A validation method is recognized by all annotations annotated by the {@link RegisterValidator} * annotation that contains important information about how and when to use * the validator. + * @param requestType class of request type enum. * @param searchUrls the path in which the annotated methods are searched. + * @param allowedValidators a set containing the various types of validator annotation allowed to be registered. + * @param allowedProcessingPhases set of request processing phases which would be allowed to be registered to + * registry. */ - ValidatorRegistry(Collection<URL> searchUrls) { + public ValidatorRegistry(Class<RequestType> requestType, + Collection<URL> searchUrls, + Set<Class<? extends Annotation>> allowedValidators, + Set<RequestProcessingPhase> allowedProcessingPhases) { + Class<RequestType[]> requestArrayClass = (Class<RequestType[]>) Array.newInstance(requestType, 0) + .getClass(); + Set<Class<? extends Annotation>> validatorsToBeRegistered = + new Reflections(new ConfigurationBuilder().setUrls(ClasspathHelper.forPackage("org.apache.hadoop")) + .setScanners(Scanners.TypesAnnotated) + .setParallel(true)).getTypesAnnotatedWith(RegisterValidator.class).stream() + .filter(allowedValidators::contains) + .filter(annotationClass -> getReturnTypeOfAnnotationMethod((Class<? extends Annotation>) annotationClass, + RegisterValidator.REQUEST_TYPE_METHOD_NAME) + .equals(requestArrayClass)) + .map(annotationClass -> (Class<? extends Annotation>) annotationClass) + .collect(Collectors.toSet()); + this.indexedValidatorMap = + allowedValidators.stream().collect(ImmutableMap.toImmutableMap(annotationClass -> + (Class<? extends Versioned>) getReturnTypeOfAnnotationMethod(annotationClass, + RegisterValidator.APPLY_BEFORE_METHOD_NAME), + validatorClass -> new EnumMap<>(requestType))); Reflections reflections = new Reflections(new ConfigurationBuilder() .setUrls(searchUrls) .setScanners(Scanners.MethodsAnnotated) .setParallel(true) ); - - Set<Method> describedValidators = - reflections.getMethodsAnnotatedWith(RequestFeatureValidator.class); - initMaps(describedValidators); + initMaps(requestArrayClass, allowedProcessingPhases, validatorsToBeRegistered, reflections); } /** - * Get the validators that has to be run in the given list of - * {@link ValidationCondition}s, for the given requestType and + * Get the validators that has to be run in the given list of, + * for the given requestType and for the given request versions. * {@link RequestProcessingPhase}. * - * @param conditions conditions that are present for the request * @param requestType the type of the protocol message * @param phase the request processing phase + * @param requestVersions different versions extracted from the request. * @return the list of validation methods that has to run. */ - List<Method> validationsFor( - List<ValidationCondition> conditions, - Type requestType, - RequestProcessingPhase phase) { - - if (conditions.isEmpty() || validators.isEmpty()) { - return Collections.emptyList(); - } - - Set<Method> returnValue = new HashSet<>(); - - for (ValidationCondition condition: conditions) { - returnValue.addAll(validationsFor(condition, requestType, phase)); - } - return new ArrayList<>(returnValue); + public List<Method> validationsFor(RequestType requestType, + RequestProcessingPhase phase, + Map<Class<? extends Annotation>, ? extends Versioned> requestVersions) { + return requestVersions.entrySet().stream() + .flatMap(requestVersion -> this.validationsFor(requestType, phase, requestVersion.getKey(), + requestVersion.getValue()).stream()) + .distinct().collect(Collectors.toList()); } /** - * Grabs validations for one particular condition. + * Get the validators that has to be run in the given list of, + * for the given requestType and for the given request versions. + * {@link RequestProcessingPhase}. * - * @param condition conditions that are present for the request * @param requestType the type of the protocol message * @param phase the request processing phase + * @param requestVersion version extracted corresponding to the request. * @return the list of validation methods that has to run. */ - private List<Method> validationsFor( - ValidationCondition condition, - Type requestType, - RequestProcessingPhase phase) { - - EnumMap<Type, EnumMap<RequestProcessingPhase, List<Method>>> - requestTypeMap = validators.get(condition); - if (requestTypeMap == null || requestTypeMap.isEmpty()) { - return Collections.emptyList(); + public <V extends Versioned> List<Method> validationsFor(RequestType requestType, + RequestProcessingPhase phase, + Class<? extends Annotation> validatorClass, + V requestVersion) { + + return Optional.ofNullable(this.indexedValidatorMap.get(requestVersion.getClass())) + .map(requestTypeMap -> requestTypeMap.get(requestType)) + .map(phaseMap -> phaseMap.get(phase)) + .map(indexedMethods -> requestVersion.version() < 0 ? + indexedMethods.getItemsEqualToIdx(requestVersion.version()) : + indexedMethods.getItemsGreaterThanIdx(requestVersion.version())) + .orElse(Collections.emptyList()); + + } + + /** + * Calls a specified method on the validator. + * @Throws IllegalArgumentException when the specified method in the validator is invalid. + */ + private <ReturnValue, Validator extends Annotation> ReturnValue callAnnotationMethod( + Validator validator, String methodName, Class<ReturnValue> returnValueType) { + try { + return (ReturnValue) validator.getClass().getMethod(methodName).invoke(validator); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("Method " + methodName + " not found in class:" + + validator.getClass().getCanonicalName(), e); + } catch (InvocationTargetException | IllegalAccessException e) { + throw new IllegalArgumentException("Error while invoking Method " + methodName + " from " + + validator.getClass().getCanonicalName(), e); } + } - EnumMap<RequestProcessingPhase, List<Method>> phases = - requestTypeMap.get(requestType); - if (phases == null) { - return Collections.emptyList(); + private Class<?> getReturnTypeOfAnnotationMethod(Class<? extends Annotation> clzz, String methodName) { + try { + return clzz.getMethod(methodName).getReturnType(); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("Method " + methodName + " not found in class:" + clzz.getCanonicalName()); } + } - List<Method> validatorsForPhase = phases.get(phase); - if (validatorsForPhase == null) { - return Collections.emptyList(); + private <Validator extends Annotation> Versioned getApplyBeforeVersion(Validator validator) { + return callAnnotationMethod(validator, RegisterValidator.APPLY_BEFORE_METHOD_NAME, Versioned.class); + } + + private <Validator extends Annotation> RequestProcessingPhase getRequestPhase(Validator validator) { + return callAnnotationMethod(validator, RegisterValidator.PROCESSING_PHASE_METHOD_NAME, + RequestProcessingPhase.class); + } + + private <Validator extends Annotation> RequestType[] getRequestType(Validator validator, + Class<RequestType[]> requestType) { + return callAnnotationMethod(validator, RegisterValidator.REQUEST_TYPE_METHOD_NAME, requestType); + } + + private <V> void checkAllowedAnnotationValues(Set<V> values, V value, String valueName, String methodName) { + if (!values.contains(value)) { + throw new IllegalArgumentException( + String.format("Invalid %1$s defined at annotation defined for method : %2$s, Annotation value : %3$s " + + "Allowed versionType: %4$s", valueName, methodName, value.toString(), values)); } - return validatorsForPhase; } /** * Initializes the internal request validator store. * The requests are stored in the following structure: - * - An EnumMap with the {@link ValidationCondition} as the key, and in which - * - values are an EnumMap with the request type as the key, and in which - * - values are Pair of lists, in which - * - left side is the pre-processing validations list - * - right side is the post-processing validations list - * @param describedValidators collection of the annotated methods to process. + * - An EnumMap with the RequestType as the key, and in which + * - values are an EnumMap with the request processing phase as the key, and in which + * - values is an {@link IndexedItems } containing the validation list + * @param validatorsToBeRegistered collection of the annotated validtors to process. */ - void initMaps(Collection<Method> describedValidators) { - for (Method m : describedValidators) { - RequestFeatureValidator descriptor = - m.getAnnotation(RequestFeatureValidator.class); - m.setAccessible(true); - - for (ValidationCondition condition : descriptor.conditions()) { - EnumMap<Type, EnumMap<RequestProcessingPhase, List<Method>>> - requestTypeMap = getAndInitialize( - condition, this::newTypeMap, validators); - EnumMap<RequestProcessingPhase, List<Method>> phases = getAndInitialize( - descriptor.requestType(), this::newPhaseMap, requestTypeMap); - if (isPreProcessValidator(descriptor)) { - getAndInitialize(PRE_PROCESS, ArrayList::new, phases).add(m); - } else if (isPostProcessValidator(descriptor)) { - getAndInitialize(POST_PROCESS, ArrayList::new, phases).add(m); - } - } + private void initMaps(Class<RequestType[]> requestType, + Set<RequestProcessingPhase> allowedPhases, + Collection<Class<? extends Annotation>> validatorsToBeRegistered, + Reflections reflections) { + for (Class<? extends Annotation> validator : validatorsToBeRegistered) { + registerValidator(requestType, allowedPhases, validator, reflections); } } - private EnumMap<Type, - EnumMap<RequestProcessingPhase, List<Method>>> newTypeMap() { - return new EnumMap<>(Type.class); + private void registerValidator(Class<RequestType[]> requestType, + Set<RequestProcessingPhase> allowedPhases, + Class<? extends Annotation> validatorToBeRegistered, + Reflections reflections) { + Collection<Method> methods = reflections.getMethodsAnnotatedWith(validatorToBeRegistered); + Class<? extends Versioned> versionClass = + (Class<? extends Versioned>) this.getReturnTypeOfAnnotationMethod(validatorToBeRegistered, + RegisterValidator.APPLY_BEFORE_METHOD_NAME); + List<Pair<? extends Annotation, Method>> sortedMethodsByApplyBeforeVersion = methods.stream() + .map(method -> Pair.of(method.getAnnotation(validatorToBeRegistered), method)) + .sorted((validatorMethodPair1, validatorMethodPair2) -> + Integer.compare( + this.getApplyBeforeVersion(validatorMethodPair1.getKey()).version(), + this.getApplyBeforeVersion(validatorMethodPair2.getKey()).version())) + .collect(Collectors.toList()); + for (Pair<? extends Annotation, Method> validatorMethodPair : sortedMethodsByApplyBeforeVersion) { + Annotation validator = validatorMethodPair.getKey(); + Method method = validatorMethodPair.getValue(); + Versioned applyBeforeVersion = this.getApplyBeforeVersion(validator); + RequestProcessingPhase phase = this.getRequestPhase(validator); + checkAllowedAnnotationValues(allowedPhases, phase, RegisterValidator.PROCESSING_PHASE_METHOD_NAME, + method.getName()); + Set<RequestType> types = Sets.newHashSet(this.getRequestType(validator, requestType)); + method.setAccessible(true); + for (RequestType type : types) { + EnumMap<RequestType, EnumMap<RequestProcessingPhase, IndexedItems<Method, Integer>>> requestMap = + this.indexedValidatorMap.get(versionClass); + EnumMap<RequestProcessingPhase, IndexedItems<Method, Integer>> phaseMap = + requestMap.computeIfAbsent(type, k -> new EnumMap<>(RequestProcessingPhase.class)); + phaseMap.computeIfAbsent(phase, k -> new IndexedItems<>()).add(method, applyBeforeVersion.version()); + } + } } - private EnumMap<RequestProcessingPhase, List<Method>> newPhaseMap() { - return new EnumMap<>(RequestProcessingPhase.class); - } + /** + * Class responsible for maintaining indexs of items. Here each item should have an index corresponding to it. + * The class implements functions for efficiently fetching range gets on the items added to the data structure. + * @param <T> Refers to the Type of the item in the data structure + * @param <IDX> Type of the index of an item added in the data structure. It is important that the index is + * comparable to each other. + */ + private static final class IndexedItems<T, IDX extends Comparable<IDX>> { + private final List<T> items; + private final TreeMap<IDX, Integer> indexMap; Review Comment: done ########## hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/validation/ValidatorRegistry.java: ########## @@ -42,156 +47,274 @@ * Registry that loads and stores the request validators to be applied by * a service. */ -public class ValidatorRegistry { +public class ValidatorRegistry<RequestType extends Enum<RequestType>> { - private final EnumMap<ValidationCondition, - EnumMap<Type, EnumMap<RequestProcessingPhase, List<Method>>>> - validators = new EnumMap<>(ValidationCondition.class); + /** + * A validator registered should have the following parameters: + * applyBeforeVersion: Enum extending Version + * RequestType: Enum signifying the type of request. + * RequestProcessingPhase: Signifying if the validator is supposed to run pre or post submitting the request. + * Based on the afforementioned parameters a complete map is built which stores the validators in a sorted order of + * the applyBeforeVersion value of the validator method. + * Thus when a request comes with a certain version value, all validators containing `applyBeforeVersion` parameter + * greater than the request versions get triggered. + * {@link #validationsFor(Enum, RequestProcessingPhase, Class, Versioned)} + */ + private final Map<Class<? extends Versioned>, EnumMap<RequestType, + EnumMap<RequestProcessingPhase, IndexedItems<Method, Integer>>>> indexedValidatorMap; /** * Creates a {@link ValidatorRegistry} instance that discovers validation * methods in the provided package and the packages in the same resource. - * A validation method is recognized by the {@link RequestFeatureValidator} - * annotation that contains important information about how and when to use - * the validator. + * A validation method is recognized by all the annotations classes which + * are annotated by {@link RegisterValidator} annotation that contains + * important information about how and when to use the validator. + * @param requestType class of request type enum. * @param validatorPackage the main package inside which validatiors should * be discovered. + * @param allowedValidators a set containing the various types of version allowed to be registered. + * @param allowedProcessingPhases set of request processing phases which would be allowed to be registered to + * registry. + * */ - ValidatorRegistry(String validatorPackage) { - this(ClasspathHelper.forPackage(validatorPackage)); + public ValidatorRegistry(Class<RequestType> requestType, + String validatorPackage, + Set<Class<? extends Annotation>> allowedValidators, + Set<RequestProcessingPhase> allowedProcessingPhases) { + this(requestType, ClasspathHelper.forPackage(validatorPackage), allowedValidators, allowedProcessingPhases); } /** * Creates a {@link ValidatorRegistry} instance that discovers validation * methods under the provided URL. - * A validation method is recognized by the {@link RequestFeatureValidator} + * A validation method is recognized by all annotations annotated by the {@link RegisterValidator} * annotation that contains important information about how and when to use * the validator. + * @param requestType class of request type enum. * @param searchUrls the path in which the annotated methods are searched. + * @param allowedValidators a set containing the various types of validator annotation allowed to be registered. + * @param allowedProcessingPhases set of request processing phases which would be allowed to be registered to + * registry. */ - ValidatorRegistry(Collection<URL> searchUrls) { + public ValidatorRegistry(Class<RequestType> requestType, + Collection<URL> searchUrls, + Set<Class<? extends Annotation>> allowedValidators, + Set<RequestProcessingPhase> allowedProcessingPhases) { + Class<RequestType[]> requestArrayClass = (Class<RequestType[]>) Array.newInstance(requestType, 0) + .getClass(); + Set<Class<? extends Annotation>> validatorsToBeRegistered = + new Reflections(new ConfigurationBuilder().setUrls(ClasspathHelper.forPackage("org.apache.hadoop")) + .setScanners(Scanners.TypesAnnotated) + .setParallel(true)).getTypesAnnotatedWith(RegisterValidator.class).stream() + .filter(allowedValidators::contains) + .filter(annotationClass -> getReturnTypeOfAnnotationMethod((Class<? extends Annotation>) annotationClass, + RegisterValidator.REQUEST_TYPE_METHOD_NAME) + .equals(requestArrayClass)) + .map(annotationClass -> (Class<? extends Annotation>) annotationClass) + .collect(Collectors.toSet()); + this.indexedValidatorMap = + allowedValidators.stream().collect(ImmutableMap.toImmutableMap(annotationClass -> + (Class<? extends Versioned>) getReturnTypeOfAnnotationMethod(annotationClass, + RegisterValidator.APPLY_BEFORE_METHOD_NAME), + validatorClass -> new EnumMap<>(requestType))); Reflections reflections = new Reflections(new ConfigurationBuilder() .setUrls(searchUrls) .setScanners(Scanners.MethodsAnnotated) .setParallel(true) ); - - Set<Method> describedValidators = - reflections.getMethodsAnnotatedWith(RequestFeatureValidator.class); - initMaps(describedValidators); + initMaps(requestArrayClass, allowedProcessingPhases, validatorsToBeRegistered, reflections); } /** - * Get the validators that has to be run in the given list of - * {@link ValidationCondition}s, for the given requestType and + * Get the validators that has to be run in the given list of, + * for the given requestType and for the given request versions. * {@link RequestProcessingPhase}. * - * @param conditions conditions that are present for the request * @param requestType the type of the protocol message * @param phase the request processing phase + * @param requestVersions different versions extracted from the request. * @return the list of validation methods that has to run. */ - List<Method> validationsFor( - List<ValidationCondition> conditions, - Type requestType, - RequestProcessingPhase phase) { - - if (conditions.isEmpty() || validators.isEmpty()) { - return Collections.emptyList(); - } - - Set<Method> returnValue = new HashSet<>(); - - for (ValidationCondition condition: conditions) { - returnValue.addAll(validationsFor(condition, requestType, phase)); - } - return new ArrayList<>(returnValue); + public List<Method> validationsFor(RequestType requestType, + RequestProcessingPhase phase, + Map<Class<? extends Annotation>, ? extends Versioned> requestVersions) { + return requestVersions.entrySet().stream() + .flatMap(requestVersion -> this.validationsFor(requestType, phase, requestVersion.getKey(), + requestVersion.getValue()).stream()) + .distinct().collect(Collectors.toList()); } /** - * Grabs validations for one particular condition. + * Get the validators that has to be run in the given list of, + * for the given requestType and for the given request versions. + * {@link RequestProcessingPhase}. * - * @param condition conditions that are present for the request * @param requestType the type of the protocol message * @param phase the request processing phase + * @param requestVersion version extracted corresponding to the request. * @return the list of validation methods that has to run. */ - private List<Method> validationsFor( - ValidationCondition condition, - Type requestType, - RequestProcessingPhase phase) { - - EnumMap<Type, EnumMap<RequestProcessingPhase, List<Method>>> - requestTypeMap = validators.get(condition); - if (requestTypeMap == null || requestTypeMap.isEmpty()) { - return Collections.emptyList(); + public <V extends Versioned> List<Method> validationsFor(RequestType requestType, + RequestProcessingPhase phase, + Class<? extends Annotation> validatorClass, + V requestVersion) { + + return Optional.ofNullable(this.indexedValidatorMap.get(requestVersion.getClass())) + .map(requestTypeMap -> requestTypeMap.get(requestType)) + .map(phaseMap -> phaseMap.get(phase)) + .map(indexedMethods -> requestVersion.version() < 0 ? + indexedMethods.getItemsEqualToIdx(requestVersion.version()) : + indexedMethods.getItemsGreaterThanIdx(requestVersion.version())) + .orElse(Collections.emptyList()); + + } + + /** + * Calls a specified method on the validator. + * @Throws IllegalArgumentException when the specified method in the validator is invalid. + */ + private <ReturnValue, Validator extends Annotation> ReturnValue callAnnotationMethod( + Validator validator, String methodName, Class<ReturnValue> returnValueType) { + try { + return (ReturnValue) validator.getClass().getMethod(methodName).invoke(validator); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("Method " + methodName + " not found in class:" + + validator.getClass().getCanonicalName(), e); + } catch (InvocationTargetException | IllegalAccessException e) { + throw new IllegalArgumentException("Error while invoking Method " + methodName + " from " + + validator.getClass().getCanonicalName(), e); } + } - EnumMap<RequestProcessingPhase, List<Method>> phases = - requestTypeMap.get(requestType); - if (phases == null) { - return Collections.emptyList(); + private Class<?> getReturnTypeOfAnnotationMethod(Class<? extends Annotation> clzz, String methodName) { + try { + return clzz.getMethod(methodName).getReturnType(); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("Method " + methodName + " not found in class:" + clzz.getCanonicalName()); } + } - List<Method> validatorsForPhase = phases.get(phase); - if (validatorsForPhase == null) { - return Collections.emptyList(); + private <Validator extends Annotation> Versioned getApplyBeforeVersion(Validator validator) { + return callAnnotationMethod(validator, RegisterValidator.APPLY_BEFORE_METHOD_NAME, Versioned.class); + } + + private <Validator extends Annotation> RequestProcessingPhase getRequestPhase(Validator validator) { + return callAnnotationMethod(validator, RegisterValidator.PROCESSING_PHASE_METHOD_NAME, + RequestProcessingPhase.class); + } + + private <Validator extends Annotation> RequestType[] getRequestType(Validator validator, + Class<RequestType[]> requestType) { + return callAnnotationMethod(validator, RegisterValidator.REQUEST_TYPE_METHOD_NAME, requestType); + } + + private <V> void checkAllowedAnnotationValues(Set<V> values, V value, String valueName, String methodName) { + if (!values.contains(value)) { + throw new IllegalArgumentException( + String.format("Invalid %1$s defined at annotation defined for method : %2$s, Annotation value : %3$s " + + "Allowed versionType: %4$s", valueName, methodName, value.toString(), values)); } - return validatorsForPhase; } /** * Initializes the internal request validator store. * The requests are stored in the following structure: - * - An EnumMap with the {@link ValidationCondition} as the key, and in which - * - values are an EnumMap with the request type as the key, and in which - * - values are Pair of lists, in which - * - left side is the pre-processing validations list - * - right side is the post-processing validations list - * @param describedValidators collection of the annotated methods to process. + * - An EnumMap with the RequestType as the key, and in which + * - values are an EnumMap with the request processing phase as the key, and in which + * - values is an {@link IndexedItems } containing the validation list + * @param validatorsToBeRegistered collection of the annotated validtors to process. */ - void initMaps(Collection<Method> describedValidators) { - for (Method m : describedValidators) { - RequestFeatureValidator descriptor = - m.getAnnotation(RequestFeatureValidator.class); - m.setAccessible(true); - - for (ValidationCondition condition : descriptor.conditions()) { - EnumMap<Type, EnumMap<RequestProcessingPhase, List<Method>>> - requestTypeMap = getAndInitialize( - condition, this::newTypeMap, validators); - EnumMap<RequestProcessingPhase, List<Method>> phases = getAndInitialize( - descriptor.requestType(), this::newPhaseMap, requestTypeMap); - if (isPreProcessValidator(descriptor)) { - getAndInitialize(PRE_PROCESS, ArrayList::new, phases).add(m); - } else if (isPostProcessValidator(descriptor)) { - getAndInitialize(POST_PROCESS, ArrayList::new, phases).add(m); - } - } + private void initMaps(Class<RequestType[]> requestType, + Set<RequestProcessingPhase> allowedPhases, + Collection<Class<? extends Annotation>> validatorsToBeRegistered, + Reflections reflections) { + for (Class<? extends Annotation> validator : validatorsToBeRegistered) { + registerValidator(requestType, allowedPhases, validator, reflections); } } - private EnumMap<Type, - EnumMap<RequestProcessingPhase, List<Method>>> newTypeMap() { - return new EnumMap<>(Type.class); + private void registerValidator(Class<RequestType[]> requestType, + Set<RequestProcessingPhase> allowedPhases, + Class<? extends Annotation> validatorToBeRegistered, + Reflections reflections) { + Collection<Method> methods = reflections.getMethodsAnnotatedWith(validatorToBeRegistered); + Class<? extends Versioned> versionClass = + (Class<? extends Versioned>) this.getReturnTypeOfAnnotationMethod(validatorToBeRegistered, + RegisterValidator.APPLY_BEFORE_METHOD_NAME); + List<Pair<? extends Annotation, Method>> sortedMethodsByApplyBeforeVersion = methods.stream() + .map(method -> Pair.of(method.getAnnotation(validatorToBeRegistered), method)) + .sorted((validatorMethodPair1, validatorMethodPair2) -> + Integer.compare( + this.getApplyBeforeVersion(validatorMethodPair1.getKey()).version(), + this.getApplyBeforeVersion(validatorMethodPair2.getKey()).version())) + .collect(Collectors.toList()); + for (Pair<? extends Annotation, Method> validatorMethodPair : sortedMethodsByApplyBeforeVersion) { + Annotation validator = validatorMethodPair.getKey(); + Method method = validatorMethodPair.getValue(); + Versioned applyBeforeVersion = this.getApplyBeforeVersion(validator); + RequestProcessingPhase phase = this.getRequestPhase(validator); + checkAllowedAnnotationValues(allowedPhases, phase, RegisterValidator.PROCESSING_PHASE_METHOD_NAME, + method.getName()); + Set<RequestType> types = Sets.newHashSet(this.getRequestType(validator, requestType)); + method.setAccessible(true); + for (RequestType type : types) { + EnumMap<RequestType, EnumMap<RequestProcessingPhase, IndexedItems<Method, Integer>>> requestMap = + this.indexedValidatorMap.get(versionClass); + EnumMap<RequestProcessingPhase, IndexedItems<Method, Integer>> phaseMap = + requestMap.computeIfAbsent(type, k -> new EnumMap<>(RequestProcessingPhase.class)); + phaseMap.computeIfAbsent(phase, k -> new IndexedItems<>()).add(method, applyBeforeVersion.version()); + } + } } - private EnumMap<RequestProcessingPhase, List<Method>> newPhaseMap() { - return new EnumMap<>(RequestProcessingPhase.class); - } + /** + * Class responsible for maintaining indexs of items. Here each item should have an index corresponding to it. + * The class implements functions for efficiently fetching range gets on the items added to the data structure. + * @param <T> Refers to the Type of the item in the data structure + * @param <IDX> Type of the index of an item added in the data structure. It is important that the index is + * comparable to each other. + */ + private static final class IndexedItems<T, IDX extends Comparable<IDX>> { + private final List<T> items; + private final TreeMap<IDX, Integer> indexMap; - private <K, V> V getAndInitialize(K key, Supplier<V> defaultSupplier, Map<K, V> from) { - return from.computeIfAbsent(key, k -> defaultSupplier.get()); - } + private IndexedItems() { + this.items = new ArrayList<>(); + this.indexMap = new TreeMap<>(); + } - private boolean isPreProcessValidator(RequestFeatureValidator descriptor) { - return descriptor.processingPhase() - .equals(PRE_PROCESS); - } + /** + * Add an item to the collection and update index if required. The order of items added should have their index + * sorted in increasing order. + * @param item + * @param idx + */ + public void add(T item, IDX idx) { + indexMap.putIfAbsent(idx, items.size()); + items.add(item); + } + + /** + * @param indexValue Given index value. + * @return All the items which has an index value greater than given index value. + */ + public List<T> getItemsGreaterThanIdx(IDX indexValue) { + return Optional.ofNullable(indexMap.higherEntry(indexValue)) + .map(Map.Entry::getValue) + .map(startIndex -> items.subList(startIndex, items.size())).orElse(Collections.emptyList()); + } + + /** + * @param indexValue Given index value. + * @return All the items which has an index value greater than given index value. + */ + public List<T> getItemsEqualToIdx(IDX indexValue) { Review Comment: done -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: issues-unsubscr...@ozone.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org --------------------------------------------------------------------- To unsubscribe, e-mail: issues-unsubscr...@ozone.apache.org For additional commands, e-mail: issues-h...@ozone.apache.org