Java tutorial
/* 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; } }