org.beangle.struts2.convention.config.SmartActionConfigBuilder.java Source code

Java tutorial

Introduction

Here is the source code for org.beangle.struts2.convention.config.SmartActionConfigBuilder.java

Source

/* Copyright c 2005-2012.
 * Licensed under GNU  LESSER General Public License, Version 3.
 * http://www.gnu.org/licenses
 */
package org.beangle.struts2.convention.config;

import static org.apache.commons.lang.StringUtils.substringBeforeLast;

import java.io.IOException;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.struts2.StrutsException;
import org.beangle.commons.collection.CollectUtils;
import org.beangle.struts2.convention.factory.BeanNameFinder;
import org.beangle.struts2.convention.factory.DefaultBeanNameFinder;
import org.beangle.struts2.convention.factory.SpringBeanNameFinder;
import org.beangle.struts2.convention.route.Action;
import org.beangle.struts2.convention.route.ActionBuilder;
import org.beangle.struts2.convention.route.Profile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.config.Configuration;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.config.entities.PackageConfig;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.spring.SpringObjectFactory;
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;

/**
 * <p>
 * This class implements the ActionConfigBuilder interface.
 * </p>
 */
public class SmartActionConfigBuilder implements ActionConfigBuilder {
    private static final Logger logger = LoggerFactory.getLogger(SmartActionConfigBuilder.class);
    private final Configuration configuration;
    private final ObjectFactory objectFactory;
    private final String defaultParentPackage;

    private String[] actionPackages = null;
    private String actionSuffix = "Action";
    private boolean checkImplementsAction = true;
    private boolean devMode = false;

    private ReloadingClassLoader reloadingClassLoader;
    private BeanNameFinder beanNameFinder = new DefaultBeanNameFinder();

    @Inject
    protected ActionBuilder actionBuilder;

    @Inject
    public SmartActionConfigBuilder(Configuration configuration, Container container, ObjectFactory objectFactory) {
        this.configuration = configuration;
        this.objectFactory = objectFactory;
        this.defaultParentPackage = "beangle";
        if (objectFactory instanceof SpringObjectFactory) {
            beanNameFinder = new SpringBeanNameFinder();
            SpringObjectFactory sf = (SpringObjectFactory) objectFactory;
            sf.autoWireBean(beanNameFinder);
        }
    }

    protected void initReloadClassLoader() {
        if (isReloadEnabled() && reloadingClassLoader == null)
            reloadingClassLoader = new ReloadingClassLoader(getClassLoader());
    }

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

    public void buildActionConfigs() {
        long start = System.currentTimeMillis();
        logger.info("Action scan starting....");
        List<String> packages = CollectUtils.newArrayList();
        for (Profile profile : actionBuilder.getProfileService().getProfiles()) {
            if (profile.isActionScan()) {
                packages.add(profile.getActionPattern());
            }
        }
        if (packages.isEmpty()) {
            return;
        }
        actionPackages = new String[packages.size()];
        packages.toArray(actionPackages);
        // setup reload class loader based on dev settings
        initReloadClassLoader();
        @SuppressWarnings("rawtypes")
        Set<Class> classes = findActions();
        int newActions = buildConfiguration(classes);
        logger.info("Action scan completely,create {} action in {} ms", newActions,
                System.currentTimeMillis() - start);
    }

    protected ClassLoaderInterface getClassLoaderInterface() {
        if (isReloadEnabled())
            return new ClassLoaderInterfaceDelegate(reloadingClassLoader);
        else {
            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 boolean isReloadEnabled() {
        return devMode;
    }

    @SuppressWarnings("rawtypes")
    protected Set<Class> findActions() {
        Set<Class> classes = CollectUtils.newHashSet();
        try {
            @SuppressWarnings("serial")
            Set<String> jarProtocols = new HashSet<String>() {
                @Override
                public boolean contains(Object o) {
                    return !ObjectUtils.equals(o, "file");
                }
            };
            //ClassFinder(classLoaderInterface, Collection<URL> urls, boolean extractBaseInterfaces, Set<String> protocols, Test<String> classNameFilter)
            //         ClassFinder finder = new ClassFinder(getClassLoaderInterface(), buildUrls(), false, jarProtocols);
            ClassFinder finder = new ClassFinder(getClassLoaderInterface(), buildUrls(), false, jarProtocols,
                    new Test<String>() {
                        public boolean test(String t) {
                            return true;
                        }
                    });
            for (String packageName : actionPackages) {
                Test<ClassFinder.ClassInfo> test = getPackageFinderTest(packageName);
                classes.addAll(finder.findClasses(test));
            }
        } catch (Exception ex) {
            logger.error("Unable to scan named packages", ex);
        }
        return classes;
    }

    protected Test<ClassFinder.ClassInfo> getPackageFinderTest(final String packageName) {
        // so "my.package" does not match "my.package2.test"
        // final String strictPackageName = packageName + ".";
        return new Test<ClassFinder.ClassInfo>() {
            public boolean test(ClassFinder.ClassInfo classInfo) {
                String classPackageName = classInfo.getPackageName();
                boolean inPackage = Profile.isInPackage(packageName, classPackageName);
                boolean nameMatches = classInfo.getName().endsWith(actionSuffix);
                try {
                    return inPackage && (nameMatches || (checkImplementsAction
                            && com.opensymphony.xwork2.Action.class.isAssignableFrom(classInfo.get())));
                } catch (ClassNotFoundException ex) {
                    logger.error("Unable to load class " + classInfo.getName(), ex);
                    return false;
                }
            }
        };
    }

    private Set<URL> buildUrls() {
        Set<URL> urls = CollectUtils.newHashSet();
        Enumeration<URL> em;
        ClassLoaderInterface classloader = getClassLoaderInterface();
        try {
            em = classloader.getResources("struts-plugin.xml");
            while (em.hasMoreElements()) {
                URL url = em.nextElement();
                urls.add(new URL(substringBeforeLast(url.toExternalForm(), "struts-plugin.xml")));
            }
            em = classloader.getResources("struts.xml");
            while (em.hasMoreElements()) {
                URL url = em.nextElement();
                urls.add(new URL(substringBeforeLast(url.toExternalForm(), "struts.xml")));
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        logger.info("build action from {}", urls);
        return urls;
    }

    @SuppressWarnings("rawtypes")
    protected int buildConfiguration(Set<Class> classes) {
        Map<String, PackageConfig.Builder> packageConfigs = new HashMap<String, PackageConfig.Builder>();
        int createCount = 0;
        for (Class<?> actionClass : classes) {
            // Skip classes that can't be instantiated
            if (cannotInstantiate(actionClass)) {
                logger.debug("Class {} did not pass the instantiation test and will be ignored",
                        actionClass.getName());
                continue;
            }
            try {
                objectFactory.getClassInstance(actionClass.getName());
            } catch (ClassNotFoundException e) {
                logger.error("Object Factory was unable to load class {}", actionClass.getName());
                throw new StrutsException("Object Factory was unable to load class " + actionClass.getName(), e);
            }
            String[] beanNames = beanNameFinder.getBeanNames(actionClass);
            switch (beanNames.length) {
            case 0:
                logger.warn("Cannot find bean definition for {}.", actionClass);
                break;
            case 1:
                Profile profile = actionBuilder.getProfileService().getProfile(actionClass.getName());
                Action action = actionBuilder.build(actionClass.getName());
                PackageConfig.Builder packageConfig = getPackageConfig(profile, packageConfigs, action,
                        actionClass);
                if (createActionConfig(packageConfig, action, actionClass, beanNames[0])) {
                    createCount++;
                }
                break;
            default:
                logger.warn("Duplicated action definition for {}", actionClass);
            }
        }
        createCount += buildIndexActions(packageConfigs);
        // Add the new actions to the configuration
        Set<String> packageNames = packageConfigs.keySet();
        for (String packageName : packageNames) {
            configuration.removePackageConfig(packageName);
            configuration.addPackageConfig(packageName, packageConfigs.get(packageName).build());
        }
        return createCount;
    }

    /**
     * Interfaces, enums, annotations, and abstract classes cannot be
     * instantiated.
     * 
     * @param actionClass
     *            class to check
     * @return returns true if the class cannot be instantiated or should be
     *         ignored
     */
    protected boolean cannotInstantiate(Class<?> actionClass) {
        return actionClass.isAnnotation() || actionClass.isInterface() || actionClass.isEnum()
                || (actionClass.getModifiers() & Modifier.ABSTRACT) != 0 || actionClass.isAnonymousClass();
    }

    protected boolean createActionConfig(PackageConfig.Builder pkgCfg, Action action, Class<?> actionClass,
            String beanName) {
        ActionConfig.Builder actionConfig = new ActionConfig.Builder(pkgCfg.getName(), action.getName(), beanName);
        actionConfig.methodName(action.getMethod());
        String actionName = action.getName();
        // check action exists on that package (from XML config probably)
        PackageConfig existingPkg = configuration.getPackageConfig(pkgCfg.getName());
        boolean create = true;
        if (existingPkg != null) {
            ActionConfig existed = existingPkg.getActionConfigs().get(actionName);
            create = (null == existed);
        }
        if (create) {
            pkgCfg.addActionConfig(actionName, actionConfig.build());
            logger.debug("Add {}/{} for {} in {}",
                    new Object[] { pkgCfg.getNamespace(), actionName, actionClass.getName(), pkgCfg.getName() });
        }
        return create;
    }

    protected PackageConfig.Builder getPackageConfig(Profile profile,
            final Map<String, PackageConfig.Builder> packageConfigs, Action action, final Class<?> actionClass) {
        // 
        String actionPkg = actionClass.getPackage().getName();
        PackageConfig parentPkg = null;
        while (StringUtils.contains(actionPkg, '.')) {
            parentPkg = configuration.getPackageConfig(actionPkg);
            if (null != parentPkg) {
                break;
            } else {
                actionPkg = StringUtils.substringBeforeLast(actionPkg, ".");
            }
        }
        if (null == parentPkg) {
            actionPkg = defaultParentPackage;
            parentPkg = configuration.getPackageConfig(actionPkg);
        }
        if (parentPkg == null) {
            throw new ConfigurationException(
                    "Unable to locate parent package [" + actionClass.getPackage().getName() + "]");
        }
        String actionPackage = actionClass.getPackage().getName();
        PackageConfig.Builder pkgConfig = packageConfigs.get(actionPackage);
        if (pkgConfig == null) {
            PackageConfig myPkg = configuration.getPackageConfig(actionPackage);
            if (null != myPkg) {
                pkgConfig = new PackageConfig.Builder(myPkg);
            } else {
                pkgConfig = new PackageConfig.Builder(actionPackage).namespace(action.getNamespace())
                        .addParent(parentPkg);
                logger.debug("Created package config named {} with a namespace {}", actionPackage,
                        action.getNamespace());
            }
            packageConfigs.put(actionPackage, pkgConfig);
        }
        return pkgConfig;
    }

    /**
     * Determine all the index handling actions and results based on this logic:
     * 1. Loop over all the namespaces such as /foo and see if it has an action
     * named index 2. If an action doesn't exists in the parent namespace of the
     * same name, create an action in the parent namespace of the same name as
     * the namespace that points to the index action in the namespace. e.g. /foo
     * -> /foo/index 3. Create the action in the namespace for empty string if
     * it doesn't exist. e.g. /foo/ the action is "" and the namespace is /foo
     * 
     * @param packageConfigs
     *            Used to store the actions.
     */
    protected int buildIndexActions(Map<String, PackageConfig.Builder> packageConfigs) {
        int createCount = 0;
        Map<String, PackageConfig.Builder> byNamespace = new HashMap<String, PackageConfig.Builder>();
        Collection<PackageConfig.Builder> values = packageConfigs.values();
        for (PackageConfig.Builder packageConfig : values) {
            byNamespace.put(packageConfig.getNamespace(), packageConfig);
        }
        Set<String> namespaces = byNamespace.keySet();
        for (String namespace : namespaces) {
            // First see if the namespace has an index action
            PackageConfig.Builder pkgConfig = byNamespace.get(namespace);
            ActionConfig indexActionConfig = pkgConfig.build().getAllActionConfigs().get("index");
            if (indexActionConfig == null) {
                continue;
            }
            if (pkgConfig.build().getAllActionConfigs().get("") == null) {
                logger.debug("Creating index ActionConfig with an action name of [] for the action class {}",
                        indexActionConfig.getClassName());
                pkgConfig.addActionConfig("", indexActionConfig);
                createCount++;
            }
        }
        return createCount;
    }

    public void destroy() {
        // loadedFileUrls.clear();
    }

    public boolean needsReload() {
        return devMode;
    }

    public ActionBuilder getActionBuilder() {
        return actionBuilder;
    }

    public void setActionBuilder(ActionBuilder actionNameBuilder) {
        this.actionBuilder = actionNameBuilder;
    }

}