blob: 830706e9a54067df56000bf232c95a230faa4d60 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.ozone.annotations;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.AnnotationValueVisitor;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.SimpleAnnotationValueVisitor8;
import javax.tools.Diagnostic;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
/**
* This class is an annotation processor that is hooked into the java compiler
* and is used to validate the RequestFeatureValidator annotations in the
* codebase, to ensure that the annotated methods have the proper signature and
* return type.
*
* The module is compiled in a different execution via Maven before anything
* else is compiled, and then javac picks this class up as an annotation
* processor from the classpath via a ServiceLoader, based on the
* META-INF/services/javax.annotation.processing.Processor file in the module's
* resources folder.
*/
@SupportedAnnotationTypes(
"org.apache.hadoop.ozone.om.request.validation.RequestFeatureValidator")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class RequestFeatureValidatorProcessor extends AbstractProcessor {
public static final String ERROR_CONDITION_IS_EMPTY =
"RequestFeatureValidator has an empty condition list. Please define the"
+ " ValidationCondition in which the validator has to be applied.";
public static final String ERROR_ANNOTATED_ELEMENT_IS_NOT_A_METHOD =
"RequestFeatureValidator annotation is not applied to a method.";
public static final String ERROR_VALIDATOR_METHOD_HAS_TO_BE_STATIC =
"Only static methods can be annotated with the RequestFeatureValidator"
+ " annotation.";
public static final String ERROR_UNEXPECTED_PARAMETER_COUNT =
"Unexpected parameter count. Expected: %d; found: %d.";
public static final String ERROR_VALIDATOR_METHOD_HAS_TO_RETURN_OMREQUEST =
"Pre-processing validator methods annotated with RequestFeatureValidator"
+ " annotation has to return an OMRequest object.";
public static final String ERROR_VALIDATOR_METHOD_HAS_TO_RETURN_OMRESPONSE =
"Post-processing validator methods annotated with RequestFeatureValidator"
+ " annotation has to return an OMResponse object.";
public static final String ERROR_FIRST_PARAM_HAS_TO_BE_OMREQUEST =
"First parameter of a RequestFeatureValidator method has to be an"
+ " OMRequest object.";
public static final String ERROR_LAST_PARAM_HAS_TO_BE_VALIDATION_CONTEXT =
"Last parameter of a RequestFeatureValidator method has to be"
+ " ValidationContext object.";
public static final String ERROR_SECOND_PARAM_HAS_TO_BE_OMRESPONSE =
"Second parameter of a RequestFeatureValidator method has to be an"
+ " OMResponse object.";
public static final String OM_REQUEST_CLASS_NAME =
"org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos"
+ ".OMRequest";
public static final String OM_RESPONSE_CLASS_NAME =
"org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos"
+ ".OMResponse";
public static final String VALIDATION_CONTEXT_CLASS_NAME =
"org.apache.hadoop.ozone.om.request.validation.ValidationContext";
public static final String ANNOTATION_SIMPLE_NAME = "RequestFeatureValidator";
public static final String ANNOTATION_CONDITIONS_PROPERTY_NAME = "conditions";
public static final String ANNOTATION_PROCESSING_PHASE_PROPERTY_NAME =
"processingPhase";
public static final String PROCESSING_PHASE_PRE_PROCESS = "PRE_PROCESS";
public static final String PROCESSING_PHASE_POST_PROCESS = "POST_PROCESS";
public static final String ERROR_NO_PROCESSING_PHASE_DEFINED =
"RequestFeatureValidator has an invalid ProcessingPhase defined.";
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
if (!annotation.getSimpleName().contentEquals(ANNOTATION_SIMPLE_NAME)) {
continue;
}
processElements(roundEnv.getElementsAnnotatedWith(annotation));
}
return false;
}
private void processElements(Set<? extends Element> annotatedElements) {
for (Element elem : annotatedElements) {
for (AnnotationMirror methodAnnotation : elem.getAnnotationMirrors()) {
validateAnnotatedMethod(elem, methodAnnotation);
}
}
}
private void validateAnnotatedMethod(
Element elem, AnnotationMirror methodAnnotation) {
boolean isPreprocessor = checkAndEvaluateAnnotation(methodAnnotation);
checkMethodIsAnnotated(elem);
ensureAnnotatedMethodIsStatic(elem);
ensurePreProcessorReturnsOMReqest((ExecutableElement) elem, isPreprocessor);
ensurePostProcessorReturnsOMResponse(
(ExecutableElement) elem, isPreprocessor);
ensureMethodParameters(elem, isPreprocessor);
}
private void ensureMethodParameters(Element elem, boolean isPreprocessor) {
List<? extends TypeMirror> paramTypes =
((ExecutableType) elem.asType()).getParameterTypes();
ensureParameterCount(isPreprocessor, paramTypes);
ensureParameterRequirements(paramTypes, 0, OM_REQUEST_CLASS_NAME,
ERROR_FIRST_PARAM_HAS_TO_BE_OMREQUEST);
if (!isPreprocessor) {
ensureParameterRequirements(paramTypes, 1, OM_RESPONSE_CLASS_NAME,
ERROR_SECOND_PARAM_HAS_TO_BE_OMRESPONSE);
}
int contextOrder = isPreprocessor ? 1 : 2;
ensureParameterRequirements(paramTypes, contextOrder,
VALIDATION_CONTEXT_CLASS_NAME,
ERROR_LAST_PARAM_HAS_TO_BE_VALIDATION_CONTEXT);
}
private void ensureParameterCount(boolean isPreprocessor,
List<? extends TypeMirror> paramTypes) {
int realParamCount = paramTypes.size();
int expectedParamCount = isPreprocessor ? 2 : 3;
if (realParamCount != expectedParamCount) {
emitErrorMsg(String.format(ERROR_UNEXPECTED_PARAMETER_COUNT,
expectedParamCount, realParamCount));
}
}
private void ensureParameterRequirements(
List<? extends TypeMirror> paramTypes,
int index, String validationContextClassName,
String errorLastParamHasToBeValidationContext) {
if (paramTypes.size() >= index + 1 &&
!paramTypes.get(index).toString().equals(validationContextClassName)) {
emitErrorMsg(errorLastParamHasToBeValidationContext);
}
}
private void ensurePostProcessorReturnsOMResponse(
ExecutableElement elem, boolean isPreprocessor) {
if (!isPreprocessor && !elem.getReturnType().toString()
.equals(OM_RESPONSE_CLASS_NAME)) {
emitErrorMsg(ERROR_VALIDATOR_METHOD_HAS_TO_RETURN_OMRESPONSE);
}
}
private void ensurePreProcessorReturnsOMReqest(
ExecutableElement elem, boolean isPreprocessor) {
if (isPreprocessor && !elem.getReturnType().toString()
.equals(OM_REQUEST_CLASS_NAME)) {
emitErrorMsg(ERROR_VALIDATOR_METHOD_HAS_TO_RETURN_OMREQUEST);
}
}
private void ensureAnnotatedMethodIsStatic(Element elem) {
if (!elem.getModifiers().contains(Modifier.STATIC)) {
emitErrorMsg(ERROR_VALIDATOR_METHOD_HAS_TO_BE_STATIC);
}
}
private void checkMethodIsAnnotated(Element elem) {
if (elem.getKind() != ElementKind.METHOD) {
emitErrorMsg(ERROR_ANNOTATED_ELEMENT_IS_NOT_A_METHOD);
}
}
private boolean checkAndEvaluateAnnotation(
AnnotationMirror methodAnnotation) {
boolean isPreprocessor = false;
for (Entry<? extends ExecutableElement, ? extends AnnotationValue>
entry : methodAnnotation.getElementValues().entrySet()) {
if (hasInvalidValidationCondition(entry)) {
emitErrorMsg(ERROR_CONDITION_IS_EMPTY);
}
if (isProcessingPhaseValue(entry)) {
isPreprocessor = evaluateProcessingPhase(entry);
}
}
return isPreprocessor;
}
private boolean evaluateProcessingPhase(
Entry<? extends ExecutableElement, ? extends AnnotationValue> entry) {
String procPhase = visit(entry, new ProcessingPhaseVisitor());
if (procPhase.equals(PROCESSING_PHASE_PRE_PROCESS)) {
return true;
} else if (procPhase.equals(PROCESSING_PHASE_POST_PROCESS)) {
return false;
}
return false;
}
private boolean isProcessingPhaseValue(
Entry<? extends ExecutableElement, ? extends AnnotationValue> entry) {
return isPropertyNamedAs(entry, ANNOTATION_PROCESSING_PHASE_PROPERTY_NAME);
}
private boolean hasInvalidValidationCondition(
Entry<? extends ExecutableElement, ? extends AnnotationValue> entry) {
return isPropertyNamedAs(entry, ANNOTATION_CONDITIONS_PROPERTY_NAME)
&& !visit(entry, new ConditionValidator());
}
private boolean isPropertyNamedAs(
Entry<? extends ExecutableElement, ? extends AnnotationValue> entry,
String simpleName) {
return entry.getKey().getSimpleName().contentEquals(simpleName);
}
private <T> T visit(
Entry<? extends ExecutableElement, ? extends AnnotationValue> entry,
AnnotationValueVisitor<T, Void> visitor) {
return entry.getValue().accept(visitor, null);
}
private static class ConditionValidator
extends SimpleAnnotationValueVisitor8<Boolean, Void> {
ConditionValidator() {
super(Boolean.TRUE);
}
@Override
public Boolean visitArray(List<? extends AnnotationValue> vals,
Void unused) {
if (vals.isEmpty()) {
return Boolean.FALSE;
}
return Boolean.TRUE;
}
}
private static class ProcessingPhaseVisitor
extends SimpleAnnotationValueVisitor8<String, Void> {
ProcessingPhaseVisitor() {
super("UNKNOWN");
}
@Override
public String visitEnumConstant(VariableElement c, Void unused) {
if (c.getSimpleName().contentEquals(PROCESSING_PHASE_PRE_PROCESS)) {
return PROCESSING_PHASE_PRE_PROCESS;
}
if (c.getSimpleName().contentEquals(PROCESSING_PHASE_POST_PROCESS)) {
return PROCESSING_PHASE_POST_PROCESS;
}
throw new IllegalStateException(ERROR_NO_PROCESSING_PHASE_DEFINED);
}
}
private void emitErrorMsg(String s) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, s);
}
}