/* * #%L * Wisdom-Framework * %% * Copyright (C) 2013 - 2015 Wisdom Framework * %% * Licensed 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. * #L% */ package org.wisdom.source.ast.visitor; import com.github.javaparser.ast.body.*; import com.github.javaparser.ast.comments.BlockComment; import com.github.javaparser.ast.comments.JavadocComment; import com.github.javaparser.ast.expr.*; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; import org.apache.maven.plugin.logging.Log; import org.apache.maven.plugin.logging.SystemStreamLog; import org.wisdom.api.http.HttpMethod; import org.wisdom.source.ast.model.ControllerModel; import org.wisdom.source.ast.model.ControllerRouteModel; import org.wisdom.source.ast.model.RouteParamModel; import org.wisdom.source.ast.util.NameConstant; import java.util.List; import java.util.Set; import static org.wisdom.source.ast.model.RouteParamModel.ParamType; import static org.wisdom.source.ast.model.RouteParamModel.ParamType.*; import static org.wisdom.source.ast.util.ExtractUtil.*; /** * Visit the controller file AST in order to populate a {@link ControllerRouteModel}. * * @author barjo */ public final class ControllerSourceVisitor extends VoidVisitorAdapter<ControllerModel> implements NameConstant { /** * Use an instance the maven plugin SystemStreamLog as logger. */ private static final Log LOGGER = new SystemStreamLog(); /** * Visit the route methods and construct ControllerRouteModel. */ private static final RouteMethodSourceVisitor routeVisitor = new RouteMethodSourceVisitor(); /** * Visit the route parameters and construct the RouteParamModel. */ private static final RouteParamSourceVisitor paramVisitor = new RouteParamSourceVisitor(); /** * Visit the class declaration, this is the visitor entry point! * * @param declaration {@inheritDoc} * @param controller The ControllerModel we are building. */ @Override public void visit(ClassOrInterfaceDeclaration declaration, ControllerModel controller) { controller.setName(declaration.getName()); LOGGER.info("[controller]Visit " + controller.getName()); //Go on with the methods and annotations super.visit(declaration,controller); } /** * Visit the controller normal annotations. * <p> * We add the value of the Path annotation as the ControllerModel base Path. * </p> * @param anno {@inheritDoc} * @param controller The ControllerModel we are building. */ @Override public void visit(NormalAnnotationExpr anno, ControllerModel controller) { //org.wisdom.api.annotations.Path (There is only one pair ->value="") if(anno.getName().getName().equals(ANNOTATION_PATH)){ java.lang.String path = asString(anno.getPairs().get(0).getValue()); controller.setBasePath(path); } } /** * We ignore the InitializerDeclaration. * * @param n {@inheritDoc} * @param arg {@inheritDoc} */ @Override public void visit(InitializerDeclaration n, ControllerModel arg) { //Ignore initializer declaration, related to issue #4 } /** * We ignore the FieldDeclaration. * * @param n {@inheritDoc} * @param arg {@inheritDoc} */ @Override public void visit(FieldDeclaration n, ControllerModel arg) { //ignore field } /** * We ignore the FieldAccessExpr. * * @param n {@inheritDoc} * @param arg {@inheritDoc} */ @Override public void visit(FieldAccessExpr n, ControllerModel arg) { //ignore field } /** * Similar to {@link #visit(NormalAnnotationExpr, ControllerModel)}. * * @param anno {@inheritDoc} * @param controller The ControllerModel we are building. */ @Override public void visit(SingleMemberAnnotationExpr anno, ControllerModel controller) { //org.wisdom.api.annotations.Path if(anno.getName().getName().equals(ANNOTATION_PATH)){ controller.setBasePath(asString(anno.getMemberValue())); } } /** * Visit the Controller JavaDoc block. * <p> * Add the JavadocComment as the ControllerModel description. * Set the ControllerModel version as the javadoc version tag if it exists. * </p> * * @param jdoc {@inheritDoc} * @param controller The ControllerModel we are building. */ @Override public void visit(JavadocComment jdoc, ControllerModel controller) { controller.setDescription(extractDescription(jdoc)); Set<String> version = extractDocAnnotation("@version",jdoc); if(!version.isEmpty()){ controller.setVersion(version.iterator().next()); } } /** * Visit the Controller methods. * <p> * We visit each methods that are annotated with the Route annotations with the {@link RouteMethodSourceVisitor}. * The routes are add to the model indexed by their Path. The routes are ordered according to natural ordering * and their hierarchy. * </p> * * @param method {@inheritDoc} * @param controller The ControllerModel we are building. */ @Override public void visit(MethodDeclaration method, ControllerModel controller) { List<AnnotationExpr> annos = method.getAnnotations(); if(annos ==null || annos.isEmpty()){ return; } for(AnnotationExpr anno: annos){ if(anno.getName().getName().equals(ANNOTATION_ROUTE)){ ControllerRouteModel route = new ControllerRouteModel(); route.setMethodName(method.getName()); routeVisitor.visit(method, route); //controllerParsed the method, annotations and params LOGGER.info("[controller]The route method " + route.getMethodName() + " starting at line " + method.getBeginLine() + " has been properly visited."); //add the route to the controller controller.addRoute(route); } } } /** * Visit the methods, and their annotations */ private static final class RouteMethodSourceVisitor extends VoidVisitorAdapter<ControllerRouteModel>{ /** * Visit the Annotations of a Route method. * * @param anno Normal annotation on the route method. * @param route The route model that we construct. */ @Override public void visit(NormalAnnotationExpr anno, ControllerRouteModel route) { //Ignore methods that are not annotated with the Route annotation. if(!anno.getName().getName().equals(ANNOTATION_ROUTE)) { return; } for (MemberValuePair pair : anno.getPairs()){ switch (pair.getName()) { case "method": //TODO Do some check here ? route.setHttpMethod(HttpMethod.valueOf(pair.getValue().toString().replace("HttpMethod.", ""))); break; case "uri": route.setPath(asString(pair.getValue())); break; case ROUTE_ACCEPTS: route.setBodyMimes(asStringSet(pair.getValue())); break; case ROUTE_PRODUCES: route.setResponseMimes(asStringSet(pair.getValue())); break; default: break; //unknown route attributes } } } /** * Visit the parameter of a Route method. * @param param Parameter of the route method. * @param route The route model that we construct. */ @Override public void visit(Parameter param, ControllerRouteModel route) { List<AnnotationExpr> annos = param.getAnnotations(); if(annos == null || annos.isEmpty()){ LOGGER.warn("[controller]The parameter " + param + "at line " + param.getBeginLine() + " " + "is for a route method but has not been annotated!"); return; } RouteParamModel routeParam = new RouteParamModel(); routeParam.setParamName(String.valueOf(param.getId())); routeParam.setName(routeParam.getParamName()); //by default, will be override if name is specified //Parsed the param (for the annotation) paramVisitor.visit(param,routeParam); if(routeParam.getParamType() == null){ //ignore if the param has not been visited (i.e no annotations) return; } //TODO some cleaning here! routeParam.setValueType(param.getType().toString()); route.addParam(routeParam); } /** * Add the comment content. * * @param comment BlockComment on the route method. * @param route The route model that we construct. */ @Override public void visit(BlockComment comment, ControllerRouteModel route) { route.setDescription(comment.getContent()); } /** * Add the javadoc content. * @param comment JavadocComment on the route method. * @param route The route model that we construct. */ @Override public void visit(JavadocComment comment, ControllerRouteModel route) { //extract the body sample annotation if present route.setBodySamples(extractBodySample(comment)); //extract the description before the jdoc annotation route.setDescription(extractDescription(comment)); } } /** * Visit the Route params and their annotation. */ private static final class RouteParamSourceVisitor extends VoidVisitorAdapter<RouteParamModel>{ @Override public void visit(NormalAnnotationExpr anno, RouteParamModel param) { if(anno.getName().getName().equals(CONSTRAINT_NOTNULL)){ param.setMandatory(true); return; } if(anno.getName().getName().equals(CONSTRAINT_MIN)){ param.setMin(Long.valueOf(extractValueByName(anno.getPairs(),"value"))); return; } if(anno.getName().getName().equals(CONSTRAINT_MAX)){ param.setMax(Long.valueOf(extractValueByName(anno.getPairs(), "value"))); return; } if(anno.getName().getName().equals(ANNOTATION_PARAM)){ param.setParamType(PARAM); } else if(anno.getName().getName().equals(ANNOTATION_PATH_PARAM)){ param.setParamType(PATH_PARAM); } else if(anno.getName().getName().equals(ANNOTATION_QUERYPARAM)){ param.setParamType(QUERY); } else if(anno.getName().getName().equals(ANNOTATION_FORMPARAM)){ param.setParamType(FORM); } else if(anno.getName().getName().equals(ANNOTATION_DEFAULTVALUE)){ param.setDefaultValue(asString(anno.getPairs().get(0).getValue())); } else{ LOGGER.warn("[controller]Annotation " + anno + " at line " + anno.getBeginLine() + " " + "is unknown!"); return; } //Only one member with name! param.setName(asString(anno.getPairs().get(0).getValue())); } @Override public void visit(SingleMemberAnnotationExpr anno, RouteParamModel param) { if(anno.getName().getName().equals(ANNOTATION_DEFAULTVALUE)){ param.setDefaultValue(asString(anno.getMemberValue())); return; } if(anno.getName().getName().equals(CONSTRAINT_MIN)){ param.setMin(Long.valueOf(anno.getMemberValue().toString())); return; } if(anno.getName().getName().equals(CONSTRAINT_MAX)){ param.setMax(Long.valueOf(anno.getMemberValue().toString())); return; } if(anno.getName().getName().equals(ANNOTATION_PARAM)){ param.setParamType(PARAM); } else if(anno.getName().getName().equals(ANNOTATION_PATH_PARAM)){ param.setParamType(PATH_PARAM); } else if(anno.getName().getName().equals(ANNOTATION_QUERYPARAM)){ param.setParamType(QUERY); } else if(anno.getName().getName().equals(ANNOTATION_FORMPARAM)){ param.setParamType(FORM); } else{ LOGGER.warn("[controller]Annotation " + anno + " at line " + anno.getBeginLine() + " " + "is unknown!"); return; } param.setName(asString(anno.getMemberValue())); } @Override public void visit(MarkerAnnotationExpr anno, RouteParamModel param) { if (anno.getName().getName().equals(ANNOTATION_BODY)) { param.setParamType(ParamType.BODY); } else if (anno.getName().getName().equals(CONSTRAINT_NOTNULL)){ param.setMandatory(true); } else { LOGGER.warn("[controller]Annotation " + anno + " at line " + anno.getBeginLine() + " " + "is unknown!"); } } } }