Package jodd.madvoc.component

Source Code of jodd.madvoc.component.ActionMethodParser

// Copyright (c) 2003-2014, Jodd Team (jodd.org). All Rights Reserved.

package jodd.madvoc.component;

import jodd.madvoc.ActionNames;
import jodd.madvoc.MadvocException;
import jodd.madvoc.MadvocUtil;
import jodd.madvoc.RootPackages;
import jodd.madvoc.ScopeData;
import jodd.madvoc.ScopeType;
import jodd.madvoc.filter.ActionFilter;
import jodd.madvoc.injector.Target;
import jodd.madvoc.interceptor.ActionInterceptor;
import jodd.madvoc.meta.ActionAnnotationData;
import jodd.madvoc.meta.ActionAnnotation;
import jodd.madvoc.meta.FilteredBy;
import jodd.madvoc.meta.InterceptedBy;
import jodd.madvoc.meta.MadvocAction;
import jodd.madvoc.meta.Action;
import jodd.madvoc.ActionConfig;
import jodd.madvoc.ActionDef;
import jodd.madvoc.path.ActionNamingStrategy;
import jodd.madvoc.result.ActionResult;
import jodd.util.ArraysUtil;
import jodd.util.ClassLoaderUtil;
import jodd.util.StringUtil;
import jodd.util.StringPool;
import jodd.petite.meta.PetiteInject;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;

/**
* Creates {@link ActionConfig action configurations} from action java method.
* Reads all annotations and builds action path (i.e. configuration).
* <p>
* Invoked only during registration, so performance is not critical.
*/
public class ActionMethodParser {

  @PetiteInject
  protected ContextInjectorComponent contextInjectorComponent;

  @PetiteInject
  protected ActionsManager actionsManager;

  @PetiteInject
  protected InterceptorsManager interceptorsManager;

  @PetiteInject
  protected FiltersManager filtersManager;

  @PetiteInject
  protected MadvocConfig madvocConfig;

  @PetiteInject
  protected ScopeDataResolver scopeDataResolver;

  @PetiteInject
  protected ActionMethodParamNameResolver actionMethodParamNameResolver;

  /**
   * Parses action class and method and creates {@link jodd.madvoc.ActionDef parsed action definition}.
   */
  public ActionDef parseActionDef(final Class<?> actionClass, final Method actionMethod) {

    ActionAnnotationData annotationData = detectActionAnnotationData(actionMethod);

    final ActionNames actionNames = new ActionNames();    // collector for all action names

    readPackageActionPath(actionNames, actionClass);

    readClassActionPath(actionNames, actionClass);

    readMethodActionPath(actionNames, actionMethod.getName(), annotationData);

    readMethodExtension(actionNames, annotationData);

    readMethodHttpMethod(actionNames, annotationData);

    final Class<? extends ActionNamingStrategy> actionPathNamingStrategy = parseMethodNamingStrategy(annotationData);

    ActionNamingStrategy namingStrategy;

    try {
      namingStrategy = actionPathNamingStrategy.newInstance();

      contextInjectorComponent.injectContext(new Target(namingStrategy));
    } catch (Exception ex) {
      throw new MadvocException(ex);
    }

    return namingStrategy.buildActionDef(actionClass, actionMethod, actionNames);
  }

  /**
   * Parses java action method annotation and returns its action configuration.
   *
   * @param actionClass action class
   * @param actionMethod action method
   * @param actionDef optional action def, usually <code>null</code> so to be parsed
   */
  public ActionConfig parse(final Class<?> actionClass, final Method actionMethod, ActionDef actionDef) {

    // interceptors
    ActionInterceptor[] actionInterceptors = parseActionInterceptors(actionClass, actionMethod);

    // filters
    ActionFilter[] actionFilters = parseActionFilters(actionClass, actionMethod);

    // build action definition when not provided
    if (actionDef == null) {
      actionDef = parseActionDef(actionClass, actionMethod);
    }

    ActionAnnotationData annotationData = detectActionAnnotationData(actionMethod);

    detectAndRegisterAlias(annotationData, actionDef);

    final boolean async = parseMethodAsyncFlag(annotationData);

    final Class<? extends ActionResult> actionResult = parseActionResult(annotationData);

    return createActionConfig(
        actionClass, actionMethod,
        actionResult,
        actionFilters, actionInterceptors,
        actionDef,
        async);
  }

  /**
   * Detects {@link jodd.madvoc.meta.ActionAnnotationData}.
   */
  protected ActionAnnotationData detectActionAnnotationData(Method actionMethod) {
    ActionAnnotationData annotationData = null;
    for (ActionAnnotation actionAnnotation : madvocConfig.getActionAnnotationInstances()) {
      annotationData = actionAnnotation.readAnnotationData(actionMethod);
      if (annotationData != null) {
        break;
      }
    }
    return annotationData;
  }

  /**
   * Detects if alias is defined in annotation and registers it if so.
   */
  protected void detectAndRegisterAlias(ActionAnnotationData annotationData, ActionDef actionDef) {
    final String alias = parseMethodAlias(annotationData);

    if (alias != null) {
      String aliasPath = StringUtil.cutToIndexOf(actionDef.getActionPath(), StringPool.HASH);
      actionsManager.registerPathAlias(alias, aliasPath);
    }
  }

  protected Class<? extends ActionResult> parseActionResult(ActionAnnotationData annotationData) {
    if (annotationData == null) {
      return null;
    }

    Class<? extends ActionResult> actionResult = annotationData.getResult();

    if (actionResult == ActionResult.class) {
      return null;
    }

    return actionResult;
  }

  protected ActionInterceptor[] parseActionInterceptors(final Class<?> actionClass, final Method actionMethod) {
    Class<? extends ActionInterceptor>[] interceptorClasses = readActionInterceptors(actionMethod);
    if (interceptorClasses == null) {
      interceptorClasses = readActionInterceptors(actionClass);
    }
    if (interceptorClasses == null) {
      interceptorClasses = madvocConfig.getDefaultInterceptors();
    }

    return interceptorsManager.resolveAll(interceptorClasses);
  }

  protected ActionFilter[] parseActionFilters(Class<?> actionClass, Method actionMethod) {
    Class<? extends ActionFilter>[] filterClasses = readActionFilters(actionMethod);
    if (filterClasses == null) {
      filterClasses = readActionFilters(actionClass);
    }
    if (filterClasses == null) {
      filterClasses = madvocConfig.getDefaultFilters();
    }

    return filtersManager.resolveAll(filterClasses);
  }

  // ---------------------------------------------------------------- interceptors

  /**
   * Reads class or method annotation for action interceptors.
   */
  protected Class<? extends ActionInterceptor>[] readActionInterceptors(AnnotatedElement actionClassOrMethod) {
    Class<? extends ActionInterceptor>[] result = null;
    InterceptedBy interceptedBy = actionClassOrMethod.getAnnotation(InterceptedBy.class);
    if (interceptedBy != null) {
      result = interceptedBy.value();
      if (result.length == 0) {
        result = null;
      }
    }
    return result;
  }

  // ---------------------------------------------------------------- filters

  /**
   * Reads class or method annotation for action filters.
   */
  protected Class<? extends ActionFilter>[] readActionFilters(AnnotatedElement actionClassOrMethod) {
    Class<? extends ActionFilter>[] result = null;
    FilteredBy filteredBy = actionClassOrMethod.getAnnotation(FilteredBy.class);
    if (filteredBy != null) {
      result = filteredBy.value();
      if (result.length == 0) {
        result = null;
      }
    }
    return result;
  }

  // ---------------------------------------------------------------- readers

  /**
   * Reads action path for package. It can be used only if root package is set in
   * {@link MadvocConfig madvoc configuration}.
   * If annotation is not set on package-level, class package will be used for
   * package action path part.
   */
  protected void readPackageActionPath(ActionNames actionNames, Class actionClass) {

    Package actionPackage = actionClass.getPackage();
    String actionPackageName = actionPackage.getName();

    final RootPackages rootPackages = madvocConfig.getRootPackages();

    String packagePath = rootPackages.getPackageActionPath(actionPackageName);

    if (packagePath == null) {

      packagePath = rootPackages.findPackagePathForActionPackage(actionPackageName);

      String rootPackage = null;

      if (packagePath != null) {
        rootPackage = rootPackages.findRootPackageForActionPath(packagePath);
      }

      // try locating marker class
      {
        String packageName = actionPackageName;
        String madvocRootPackageClassName = madvocConfig.getMadvocRootPackageClassName();

        if (madvocRootPackageClassName != null) {
          while (true) {
            String className = packageName + '.' + madvocRootPackageClassName;
            try {
              Class<?> madvocRootPackageClass = ClassLoaderUtil.loadClass(className, actionClass.getClassLoader());

              // class found, find the mapping
              String mapping = StringPool.EMPTY;
              MadvocAction madvocAction = madvocRootPackageClass.getAnnotation(MadvocAction.class);

              if (madvocAction != null) {
                mapping = madvocAction.value();
              }

              // register root package - so not to lookup twice
              madvocConfig.getRootPackages().addRootPackage(packageName, mapping);

              // repeat lookup
              packagePath = rootPackages.findPackagePathForActionPackage(actionPackageName);

              break;
            } catch (ClassNotFoundException ignore) {

              // continue
              int dotNdx = packageName.lastIndexOf('.');
              if (dotNdx == -1) {
                break;
              }

              packageName = packageName.substring(0, dotNdx);

              if (rootPackage != null) {
                // don't go beyond found root package
                if (packageName.equals(rootPackage)) {
                  break;
                }
              }
            }
          }
        }
      }

      rootPackages.registerPackageActionPath(actionPackageName, packagePath);
    }


    // read package-level annotation

    MadvocAction madvocActionAnnotation = actionPackage.getAnnotation(MadvocAction.class);

    String packageActionPath = madvocActionAnnotation != null ? madvocActionAnnotation.value().trim() : null;

    if (StringUtil.isEmpty(packageActionPath)) {
      packageActionPath = null;
    }

    // package-level annotation overrides everything
    // if not set, resolve value
    if (packageActionPath == null) {
      // no package-level annotation
      if (packagePath == null) {
        // no root package path, just return
        return;
      }
      packageActionPath = packagePath;
    }

    actionNames.setPackageNames(
      StringUtil.stripChar(packagePath, '/'),
      StringUtil.surround(packageActionPath, StringPool.SLASH)
    );
  }

  /**
   * Reads action path from class. If the class is annotated with {@link MadvocAction} annotation,
   * class action path will be read from annotation value. Otherwise, action class path will be built from the
   * class name. This is done by removing the package name and the last contained word
   * (if there is more then one) from the class name. Such name is finally uncapitalized.
   * <p>
   * If this method returns <code>null</code> class will be ignored.
   */
  protected void readClassActionPath(ActionNames actionNames, Class actionClass) {
    // read annotation
    MadvocAction madvocActionAnnotation = ((Class<?>)actionClass).getAnnotation(MadvocAction.class);
    String classActionPath = madvocActionAnnotation != null ? madvocActionAnnotation.value().trim() : null;
    if (StringUtil.isEmpty(classActionPath)) {
      classActionPath = null;
    }

    String name = actionClass.getSimpleName();
    name = StringUtil.uncapitalize(name);
    name = MadvocUtil.stripLastCamelWord(name);

    if (classActionPath == null) {
      classActionPath = name;
    }

    actionNames.setClassNames(name, classActionPath);
  }

  /**
   * Reads action method. Returns <code>null</code> if action method is {@link Action#NONE}
   * or if it is equals to {@link MadvocConfig#getDefaultActionMethodNames() default action names}.
   */
  protected void readMethodActionPath(ActionNames actionNames, String methodName, ActionAnnotationData annotationData) {
    // read annotation
    String methodActionPath = annotationData != null ? annotationData.getValue() : null;

    if (methodActionPath == null) {
      methodActionPath = methodName;
    } else {
      if (methodActionPath.equals(Action.NONE)) {
        return;
      }
    }

    // check for defaults
    for (String path : madvocConfig.getDefaultActionMethodNames()) {
      if (methodActionPath.equals(path)) {
        methodActionPath = null;
        break;
      }
    }

    actionNames.setMethodNames(methodName, methodActionPath);
  }

  /**
   * Reads method's extension.
   */
  protected void readMethodExtension(ActionNames actionNames, ActionAnnotationData annotationData) {
    String extension = madvocConfig.getDefaultExtension();
    if (annotationData != null) {
      String annExtension = annotationData.getExtension();
      if (annExtension != null) {
        if (annExtension.equals(Action.NONE)) {
          extension = null;
        } else {
          extension = annExtension;
        }
      }
    }
    actionNames.setExtension(extension);
  }

  /**
   * Reads method's alias value.
   */
  protected String parseMethodAlias(ActionAnnotationData annotationData) {
    String alias = null;
    if (annotationData != null) {
      alias = annotationData.getAlias();
    }
    return alias;
  }

  /**
   * Reads method's http method.
   */
  private void readMethodHttpMethod(ActionNames actionNames, ActionAnnotationData annotationData) {
    String method = null;
    if (annotationData != null) {
      method = annotationData.getMethod();
    }

    actionNames.setHttpMethod(method);
  }

  /**
   * Reads method's async flag.
   */
  private boolean parseMethodAsyncFlag(ActionAnnotationData annotationData) {
    boolean sync = false;
    if (annotationData != null) {
      sync = annotationData.isAsync();
    }
    return sync;
  }

  /**
   * Reads method's action path naming strategy.
   */
  @SuppressWarnings("unchecked")
  private Class<? extends ActionNamingStrategy> parseMethodNamingStrategy(ActionAnnotationData annotationData) {
    Class<? extends ActionNamingStrategy> actionNamingStrategyClass = null;

    if (annotationData != null) {
      actionNamingStrategyClass = annotationData.getPath();

      if (actionNamingStrategyClass == ActionNamingStrategy.class) {
        actionNamingStrategyClass = null;
      }
    }

    if (actionNamingStrategyClass == null) {
      actionNamingStrategyClass = madvocConfig.getDefaultNamingStrategy();
    }

    return actionNamingStrategyClass;
  }

  // ---------------------------------------------------------------- create action configuration

  /**
   * Creates new instance of action configuration.
   * Initialize caches.
   */
  public ActionConfig createActionConfig(
      Class actionClass,
      Method actionClassMethod,
      Class<? extends ActionResult> actionResult,
      ActionFilter[] filters,
      ActionInterceptor[] interceptors,
      ActionDef actionDef,
      boolean async)
  {

    // 1) find ins and outs

    Class[] paramTypes = actionClassMethod.getParameterTypes();
    ActionConfig.MethodParam[] params = new ActionConfig.MethodParam[paramTypes.length];

    Annotation[][] paramAnns = actionClassMethod.getParameterAnnotations();
    String[] methodParamNames = null;

    // expand arguments array with action itself, on first position
    Class[] types = ArraysUtil.insert(paramTypes, actionClass, 0);

    ScopeData[][] allScopeData = new ScopeData[ScopeType.values().length][];

    // for all elements: action and method arguments...
    for (int i = 0; i < types.length; i++) {
      Class type = types[i];

      ScopeData[] scopeData = null;

      if (i > 0) {
        // lazy init to postpone bytecode usage, when method has no arguments
        if (methodParamNames == null) {
          methodParamNames = actionMethodParamNameResolver.resolveParamNames(actionClassMethod);
        }

        int paramIndex = i - 1;

        String paramName = methodParamNames[paramIndex];

        scopeData = scopeDataResolver.resolveScopeData(paramName, type, paramAnns[paramIndex]);

        params[paramIndex] = new ActionConfig.MethodParam(
            paramTypes[paramIndex], paramName, scopeDataResolver.detectAnnotationType(paramAnns[paramIndex]));
      }

      if (scopeData == null) {
        // read annotations inside the type for all scope types
        scopeData = scopeDataResolver.resolveScopeData(type);
      }

      if (scopeData == null) {
        continue;
      }

      // for all scope types... merge
      for (int j = 0; j < ScopeType.values().length; j++) {
        if (allScopeData[j] == null) {
          allScopeData[j] = new ScopeData[types.length];
        }
        allScopeData[j][i] = scopeData[j];
      }
    }

    return new ActionConfig(
        actionClass,
        actionClassMethod,
        filters,
        interceptors,
        actionDef,
        actionResult,
        async,
        allScopeData,
        params);
  }

}
TOP

Related Classes of jodd.madvoc.component.ActionMethodParser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.