Package com.google.code.rees.scope.struts2

Source Code of com.google.code.rees.scope.struts2.StrutsActionProvider

package com.google.code.rees.scope.struts2;

import java.io.IOException;
import java.net.URL;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.struts2.StrutsConstants;

import com.google.code.rees.scope.ActionProvider;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.TextParseUtil;
import com.opensymphony.xwork2.util.classloader.ReloadingClassLoader;
import com.opensymphony.xwork2.util.finder.ClassFinder;
import com.opensymphony.xwork2.util.finder.ClassLoaderInterface;
import com.opensymphony.xwork2.util.finder.ClassLoaderInterfaceDelegate;
import com.opensymphony.xwork2.util.finder.Test;
import com.opensymphony.xwork2.util.finder.UrlSet;
import com.opensymphony.xwork2.util.finder.ClassFinder.ClassInfo;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;

/**
*
* @author rees.byars with code from the struts2 convention package
*
*/
public class StrutsActionProvider implements ActionProvider {
 
  private static final long serialVersionUID = 6728107973559862449L;

  private static final Logger LOG = LoggerFactory
      .getLogger(StrutsActionProvider.class);

  private Set<Class<?>> actionClasses;
  private String[] actionPackages;
  private String[] packageLocators;
  private String[] includeJars;
  private boolean disablePackageLocatorsScanning;
  private boolean checkImplementsAction;
  private String packageLocatorsBasePackage;
  private String actionSuffix;
  private Set<String> fileProtocols;
  private boolean devMode;
  private ReloadingClassLoader reloadingClassLoader;
  private boolean reload;
  private boolean excludeParentClassLoader;
  private boolean requireFollowsConvention;

  public Set<Class<?>> getActionClasses() {
    if (actionClasses == null) {
      initReloadClassLoader();
      actionClasses = this.findActions();
    }
    return this.actionClasses;
  }

  @Inject(StrutsConstants.STRUTS_DEVMODE)
  public void setDevMode(String mode) {
    this.devMode = "true".equals(mode);
  }

  /**
   * Reload configuration when classes change. Defaults to "false" and should
   * not be used in production.
   */
  @Inject(ConventionConstants.RELOAD_CLASSES)
  public void setReload(String reload) {
    this.reload = "true".equals(reload);
  }

  /**
   * Exclude URLs found by the parent class loader. Defaults to "true", set to
   * true for JBoss
   */
  @Inject(ConventionConstants.EXCLUDE_PARENT_CLASS_LOADER)
  public void setExcludeParentClassLoader(String exclude) {
    this.excludeParentClassLoader = "true".equals(exclude);
  }

  /**
   * File URLs whose protocol are in these list will be processed as jars
   * containing classes
   *
   * @param fileProtocols
   *            Comma separated list of file protocols that will be considered
   *            as jar files and scanned
   */
  @Inject(ConventionConstants.FILE_PROTOCOLS)
  public void setFileProtocols(String fileProtocols) {
    if (StringUtils.isNotBlank(fileProtocols)) {
      this.fileProtocols = TextParseUtil
          .commaDelimitedStringToSet(fileProtocols);
    }
  }

  /**
   * @param includeJars
   *            Comma separated list of regular expressions of jars to be
   *            included.
   */
  @Inject(value = ConventionConstants.INCLUDE_JARS, required = false)
  public void setIncludeJars(String includeJars) {
    if (StringUtils.isNotEmpty(includeJars))
      this.includeJars = includeJars.split("\\s*[,]\\s*");
  }

  /**
   * @param disablePackageLocatorsScanning
   *            If set to true, only the named packages will be scanned
   */
  @Inject(value = ConventionConstants.PACKAGE_LOCATORS_DISABLE, required = false)
  public void setDisablePackageLocatorsScanning(
      String disablePackageLocatorsScanning) {
    this.disablePackageLocatorsScanning = "true"
        .equals(disablePackageLocatorsScanning);
  }

  /**
   * @param actionPackages
   *            (Optional) An optional list of action packages that this
   *            should create configuration for.
   */
  @Inject(value = ConventionConstants.ACTION_PACKAGES, required = false)
  public void setActionPackages(String actionPackages) {
    if (StringUtils.isNotBlank(actionPackages)) {
      this.actionPackages = actionPackages.split("\\s*[,]\\s*");
    }
  }

  /**
   * @param checkImplementsAction
   *            (Optional) Map classes that implement
   *            com.opensymphony.xwork2.Action as actions
   */
  @Inject(value = ConventionConstants.CHECK_IMPLEMENTS_ACTION, required = false)
  public void setCheckImplementsAction(String checkImplementsAction) {
    this.checkImplementsAction = "true".equals(checkImplementsAction);
  }

  /**
   * @param actionSuffix
   *            (Optional) Classes that end with these value will be mapped as
   *            actions (defaults to "Action")
   */
  @Inject(value = ConventionConstants.ACTION_SUFFIX, required = false)
  public void setActionSuffix(String actionSuffix) {
    if (StringUtils.isNotBlank(actionSuffix)) {
      this.actionSuffix = actionSuffix;
    }
  }

  /**
   * @param packageLocators
   *            (Optional) A list of names used to find action packages.
   */
  @Inject(value = ConventionConstants.PACKAGE_LOCATORS, required = false)
  public void setPackageLocators(String packageLocators) {
    this.packageLocators = packageLocators.split("\\s*[,]\\s*");
  }

  /**
   * @param packageLocatorsBasePackage
   *            (Optional) If set, only packages that start with this name
   *            will be scanned for actions.
   */
  @Inject(value = ConventionConstants.PACKAGE_LOCATORS_BASE_PACKAGE, required = false)
  public void setPackageLocatorsBase(String packageLocatorsBasePackage) {
    this.packageLocatorsBasePackage = packageLocatorsBasePackage;
  }
 
  /**
   * @param requireFollowsConvention
   *            If true, only classes that follow the convention for action classes will
   *            be scanned for the scope annotations.
   */
  @Inject(value = StrutsScopeConstants.REQUIRE_FOLLOWS_CONVENTION)
  public void setRequireFollowsConvention(String requireFollowsConvention) {
    this.requireFollowsConvention = "true".equals(requireFollowsConvention);
  }

  /**
   * Note that we can't include the test for {@link #actionSuffix} here
   * because a class is included if its name ends in {@link #actionSuffix} OR
   * it implements {@link com.opensymphony.xwork2.Action}. Since the whole
   * goal is to avoid loading the class if we don't have to, the (actionSuffix
   * || implements Action) test will have to remain until later. See
   * {@link #getActionClassTest()} for the test performed on the loaded
   * {@link ClassInfo} structure.
   *
   * @param className
   *            the name of the class to test
   * @return true if the specified class should be included in the
   *         package-based action scan
   */
  protected boolean includeClassNameInActionScan(String className) {

    String classPackageName = StringUtils.substringBeforeLast(className,
        ".");

    if (actionPackages != null) {
      for (String packageName : actionPackages) {
        String strictPackageName = packageName + ".";
        if (classPackageName.equals(packageName)
            || classPackageName.startsWith(strictPackageName))
          return true;
      }
    }

    if (packageLocators != null && !disablePackageLocatorsScanning) {
      for (String packageLocator : packageLocators) {
        if (classPackageName.length() > 0
            && (packageLocatorsBasePackage == null || classPackageName
                .startsWith(packageLocatorsBasePackage))) {
          String[] splitted = classPackageName.split("\\.");

          if (contains(splitted, packageLocator, false))
            return true;
        }
      }
    }

    return false;
  }

  /**
   * Construct a {@link Test} object that determines if a specified class name
   * should be included in the package scan based on the clazz's package name.
   * Note that the goal is to avoid loading the class, so the test should only
   * rely on information in the class name itself. The default implementation
   * is to return the result of {@link #includeClassNameInActionScan(String)}.
   *
   * @return a {@link Test} object that returns true if the specified class
   *         name should be included in the package scan
   */
  protected Test<String> getClassPackageTest() {
    return new Test<String>() {
      public boolean test(String className) {
        return includeClassNameInActionScan(className);
      }
    };
  }

  /**
   * Construct a {@link Test} Object that determines if a specified class
   * should be included in the package scan based on the full
   * {@link ClassInfo} of the class. At this point, the class has been loaded,
   * so it's ok to perform tests such as checking annotations or looking at
   * interfaces or super-classes of the specified class.
   *
   * @return a {@link Test} object that returns true if the specified class
   *         should be included in the package scan
   */
  protected Test<ClassFinder.ClassInfo> getActionClassTest() {
    return new Test<ClassFinder.ClassInfo>() {
      public boolean test(ClassFinder.ClassInfo classInfo) {

        // Why do we call includeClassNameInActionScan here, when it's
        // already been called to in the initial call to ClassFinder?
        // When some action class passes our package filter in that
        // step,
        // ClassFinder automatically includes parent classes of that
        // action,
        // such as com.opensymphony.xwork2.ActionSupport. We repeat the
        // package filter here to filter out such results.
        boolean inPackage = includeClassNameInActionScan(classInfo
            .getName());
        boolean nameMatches = classInfo.getName().endsWith(actionSuffix) || !requireFollowsConvention;

        try {
          return inPackage
              && (nameMatches || (checkImplementsAction && com.opensymphony.xwork2.Action.class
                  .isAssignableFrom(classInfo.get())));
        } catch (ClassNotFoundException ex) {
          if (LOG.isErrorEnabled())
            LOG.error("Unable to load class [#0]", ex,
                classInfo.getName());
          return false;
        }
      }
    };
  }

  protected Set<Class<?>> findActions() {
    Set<Class<?>> classes = new HashSet<Class<?>>();
    try {
      if (actionPackages != null
          || (packageLocators != null && !disablePackageLocatorsScanning)) {

        // By default, ClassFinder scans EVERY class in the specified
        // url set, which can produce spurious warnings for non-action
        // classes that can't be loaded. We pass a package filter that
        // only considers classes that match the action packages
        // specified by the user
        Test<String> classPackageTest = getClassPackageTest();
        ClassFinder finder = new ClassFinder(getClassLoaderInterface(),
            buildUrlSet().getUrls(), true, this.fileProtocols,
            classPackageTest);

        Test<ClassFinder.ClassInfo> test = getActionClassTest();
        for (Class<?> clazz : finder.findClasses(test)) {
          classes.add(clazz);
        }
      }
    } catch (Exception ex) {
      if (LOG.isErrorEnabled())
        LOG.error("Unable to scan named packages", ex);
    }

    return classes;
  }

  protected boolean isReloadEnabled() {
    return devMode && reload;
  }

  protected void initReloadClassLoader() {
    // when the configuration is reloaded, a new classloader will be setup
    if (isReloadEnabled() && reloadingClassLoader == null)
      reloadingClassLoader = new ReloadingClassLoader(getClassLoader());
  }

  protected ClassLoaderInterface getClassLoaderInterface() {
    if (isReloadEnabled())
      return new ClassLoaderInterfaceDelegate(this.reloadingClassLoader);
    else {
      /*
       * if there is a ClassLoaderInterface in the context, use it,
       * otherwise default to the default ClassLoaderInterface (a wrapper
       * around the current thread classloader) using this, other plugins
       * (like OSGi) can plugin their own classloader for a while and it
       * will be used by Convention (it cannot be a bean, as Convention is
       * likely to be called multiple times, and it needs to use the
       * default ClassLoaderInterface during normal startup)
       */
      ClassLoaderInterface classLoaderInterface = null;
      ActionContext ctx = ActionContext.getContext();
      if (ctx != null)
        classLoaderInterface = (ClassLoaderInterface) ctx
            .get(ClassLoaderInterface.CLASS_LOADER_INTERFACE);

      return (ClassLoaderInterface) ObjectUtils.defaultIfNull(
          classLoaderInterface, new ClassLoaderInterfaceDelegate(
              getClassLoader()));
    }
  }

  protected ClassLoader getClassLoader() {
    return Thread.currentThread().getContextClassLoader();
  }

  private UrlSet buildUrlSet() throws IOException {
    ClassLoaderInterface classLoaderInterface = getClassLoaderInterface();
    UrlSet urlSet = new UrlSet(classLoaderInterface, this.fileProtocols);

    // excluding the urls found by the parent class loader is desired, but
    // fails in JBoss (all urls are removed)
    if (excludeParentClassLoader) {
      // exclude parent of classloaders
      ClassLoaderInterface parent = classLoaderInterface.getParent();
      // if reload is enabled, we need to step up one level, otherwise the
      // UrlSet will be empty
      // this happens because the parent of the realoding class loader is
      // the web app classloader
      if (parent != null && isReloadEnabled())
        parent = parent.getParent();

      if (parent != null)
        urlSet = urlSet.exclude(parent);

      try {
        // This may fail in some sandboxes, ie GAE
        ClassLoader systemClassLoader = ClassLoader
            .getSystemClassLoader();
        urlSet = urlSet.exclude(new ClassLoaderInterfaceDelegate(
            systemClassLoader.getParent()));

      } catch (SecurityException e) {
        if (LOG.isWarnEnabled())
          LOG.warn("Could not get the system classloader due to security constraints, there may be improper urls left to scan");
      }
    }

    // try to find classes dirs inside war files
    urlSet = urlSet.includeClassesUrl(classLoaderInterface);

    urlSet = urlSet.excludeJavaExtDirs();
    urlSet = urlSet.excludeJavaEndorsedDirs();
    try {
      urlSet = urlSet.excludeJavaHome();
    } catch (NullPointerException e) {
      // This happens in GAE since the sandbox contains no java.home
      // directory
      if (LOG.isWarnEnabled())
        LOG.warn("Could not exclude JAVA_HOME, is this a sandbox jvm?");
    }
    urlSet = urlSet.excludePaths(System.getProperty("sun.boot.class.path",
        ""));
    urlSet = urlSet.exclude(".*/JavaVM.framework/.*");

    if (includeJars == null) {
      urlSet = urlSet.exclude(".*?\\.jar(!/|/)?");
    } else {
      // jar urls regexes were specified
      List<URL> rawIncludedUrls = urlSet.getUrls();
      Set<URL> includeUrls = new HashSet<URL>();
      boolean[] patternUsed = new boolean[includeJars.length];

      for (URL url : rawIncludedUrls) {
        if (fileProtocols.contains(url.getProtocol())) {
          // it is a jar file, make sure it macthes at least a url
          // regex
          for (int i = 0; i < includeJars.length; i++) {
            String includeJar = includeJars[i];
            if (Pattern.matches(includeJar, url.toExternalForm())) {
              includeUrls.add(url);
              patternUsed[i] = true;
              break;
            }
          }
        } else {
          // it is not a jar
          includeUrls.add(url);
        }
      }

      if (LOG.isWarnEnabled()) {
        for (int i = 0; i < patternUsed.length; i++) {
          if (!patternUsed[i]) {
            LOG.warn(
                "The includeJars pattern [#0] did not match any jars in the classpath",
                includeJars[i]);
          }
        }
      }
      return new UrlSet(includeUrls);
    }

    return urlSet;
  }

  public static boolean contains(String[] strings, String value, boolean ignoreCase) {
    if (strings != null) {
      for (String string : strings) {
        if ((string.equals(value))
            || ((ignoreCase) && (string.equalsIgnoreCase(value)))) {
          return true;
        }
      }
    }
    return false;
  }

}
TOP

Related Classes of com.google.code.rees.scope.struts2.StrutsActionProvider

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.