org.beangle.struts2.convention.config.ConventionPackageProvider Maven / Gradle / Ivy
/*
* Beangle, Agile Development Scaffold and Toolkits.
*
* Copyright © 2005, The Beangle Software.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*/
package org.beangle.struts2.convention.config;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.servlet.ServletContext;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.views.freemarker.FreemarkerManager;
import org.beangle.commons.collection.CollectUtils;
import org.beangle.commons.lang.ClassLoaders;
import org.beangle.commons.lang.Objects;
import org.beangle.commons.lang.Strings;
import org.beangle.commons.lang.time.Stopwatch;
import org.beangle.commons.text.i18n.TextBundleRegistry;
import org.beangle.struts2.annotation.Result;
import org.beangle.struts2.annotation.Results;
import org.beangle.struts2.convention.config.ActionFinder.ActionTest;
import org.beangle.struts2.convention.route.Action;
import org.beangle.struts2.convention.route.ActionBuilder;
import org.beangle.struts2.convention.route.Profile;
import org.beangle.struts2.convention.route.ProfileService;
import org.beangle.struts2.convention.route.ViewMapper;
import org.beangle.struts2.freemarker.TemplateFinderByLoader;
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.PackageProvider;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.config.entities.PackageConfig;
import com.opensymphony.xwork2.config.entities.ResultConfig;
import com.opensymphony.xwork2.config.entities.ResultTypeConfig;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.classloader.ReloadingClassLoader;
import com.opensymphony.xwork2.util.finder.ClassLoaderInterface;
import com.opensymphony.xwork2.util.finder.ClassLoaderInterfaceDelegate;
import freemarker.cache.TemplateLoader;
/**
*
* This class is a configuration provider for the XWork configuration system. This is really the
* only way to truly handle loading of the packages, actions and results correctly.
*
*/
public class ConventionPackageProvider implements PackageProvider {
private static final Logger logger = LoggerFactory.getLogger(ConventionPackageProvider.class);
private boolean devMode = false;
private final Configuration configuration;
private final FreemarkerManager freemarkerManager;
private final ViewMapper viewMapper;
private final ProfileService profileService;
private final ActionBuilder actionBuilder;
private final TextBundleRegistry registry;
private ReloadingClassLoader reloadingClassLoader;
private ActionFinder actionFinder;
private List actionPackages = CollectUtils.newArrayList();
/** [url- > classname] */
private Map primaryMappings = CollectUtils.newHashMap();
@Inject("beangle.convention.default.parent.package")
private String defaultParentPackage = "beangle";
@Inject("beangle.convention.action.suffix")
private String actionSuffix = "Action";
@Inject("beangle.i18n.resources")
private String defaultBundleNames;
private boolean reloadBundles = false;
private boolean preloadftl = true;
// Temperary use
private TemplateFinderByLoader templateFinder;
@Inject
public ConventionPackageProvider(Configuration configuration, ObjectFactory objectFactory,
FreemarkerManager freemarkerManager, ProfileService profileService, ActionBuilder actionBuilder,
TextBundleRegistry registry, ViewMapper viewMapper) throws Exception {
this.configuration = configuration;
actionFinder = (ActionFinder) objectFactory.buildBean(ContainerActionFinder.class,
new HashMap(0));
this.freemarkerManager = freemarkerManager;
this.profileService = profileService;
this.actionBuilder = actionBuilder;
this.registry = registry;
this.viewMapper = viewMapper;
}
public void init(Configuration configuration) throws ConfigurationException {
registry.addDefaults(Strings.split(defaultBundleNames));
registry.setReloadBundles(reloadBundles);
templateFinder = buildTemplateFinder();
Properties properties = new Properties();
URL url = ClassLoaders.getResource("struts.properties", getClass());
if (null != url) {
try {
properties.load(url.openStream());
} catch (IOException e) {
e.printStackTrace();
}
}
for (Object key : properties.keySet()) {
if (key.toString().startsWith("url.")) {
primaryMappings.put(Strings.substringAfter(key.toString(), "url."),
properties.getProperty(key.toString()));
}
}
}
public void loadPackages() throws ConfigurationException {
Stopwatch watch = new Stopwatch(true);
for (Profile profile : actionBuilder.getProfileService().getProfiles()) {
if (profile.isActionScan()) actionPackages.add(profile.getActionPattern());
}
if (actionPackages.isEmpty()) { return; }
initReloadClassLoader();
Map packageConfigs = new HashMap();
PackageConfig.Builder defaultPackageBuilder = new PackageConfig.Builder(
configuration.getPackageConfig(defaultParentPackage));
packageConfigs.put(defaultPackageBuilder.getName(), defaultPackageBuilder);
int newActions = 0;
int overrideActions = 0;
Map, String> actionNames = actionFinder.getActions(new ActionTest(actionSuffix, actionPackages));
Map> name2Actions = CollectUtils.newHashMap();
Map name2Packages = CollectUtils.newHashMap();
for (Map.Entry, String> entry : actionNames.entrySet()) {
Class> actionClass = entry.getKey();
Action action = actionBuilder.build(actionClass);
// build key
String key = null;
if (action.getNamespace().equals("/")) key = action.getNamespace() + action.getName();
else key = action.getNamespace() + "/" + action.getName();
// check primary action class
String primaryClassName = primaryMappings.get(key);
if (null != primaryClassName && !primaryClassName.equals(actionClass.getName())) continue;
// check action override relation
PackageConfig.Builder pcb = null;
Class> existAction = name2Actions.get(key);
if (null == existAction) pcb = buildPackageConfig(actionClass, action, packageConfigs);
else pcb = name2Packages.get(key);
ActionConfig.Builder acb = null;
String beanName = entry.getValue();
if (null == existAction) {
acb = createActionConfig(pcb, action, actionClass, beanName);
if (null != acb) newActions++;
} else {
if (!actionClass.isAssignableFrom(existAction)) {
acb = createActionConfig(pcb, action, actionClass, beanName);
if (null != acb) overrideActions++;
}
}
if (null != acb) {
name2Actions.put(key, actionClass);
name2Packages.put(key, pcb);
}
}
Set processedPackages = CollectUtils.newHashSet();
// Add the new actions to the configuration
for (PackageConfig.Builder builder : packageConfigs.values()) {
String packageName = builder.getName();
if (!processedPackages.contains(packageName)) {
configuration.removePackageConfig(packageName);
configuration.addPackageConfig(packageName, builder.build());
processedPackages.add(packageName);
}
}
templateFinder = null;
logger.info("Action scan completed,create {} new action(override {}) in {}.",
new Object[] { newActions, overrideActions, watch });
}
protected void initReloadClassLoader() {
if (isReloadEnabled() && reloadingClassLoader == null)
reloadingClassLoader = new ReloadingClassLoader(getClassLoader());
}
protected ClassLoader getClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
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 Objects.defaultIfNull(classLoaderInterface, new ClassLoaderInterfaceDelegate(getClassLoader()));
}
}
protected boolean isReloadEnabled() {
return devMode;
}
protected ActionConfig.Builder createActionConfig(PackageConfig.Builder pkgCfg, Action action,
Class> actionClass, String beanName) {
ActionConfig.Builder acb = null;
String actionName = action.getName();
// check action exists on that package (from XML config probably)
PackageConfig existedPkg = configuration.getPackageConfig(pkgCfg.getName());
if (null == existedPkg || null == existedPkg.getActionConfigs().get(actionName)) {
acb = new ActionConfig.Builder(pkgCfg.getName(), action.getName(), beanName);
acb.methodName("*");
acb.setStrictMethodInvocation(false);
acb.addResultConfigs(buildResultConfigs(actionClass, pkgCfg));
pkgCfg.addActionConfig(actionName, acb.build());
logger.debug("Add {}/{} for {} in {}",
new Object[] { pkgCfg.getNamespace(), actionName, actionClass.getName(), pkgCfg.getName() });
}
return acb;
}
protected boolean shouldGenerateResult(Method m) {
if (String.class.equals(m.getReturnType()) && m.getParameterTypes().length == 0
&& Modifier.isPublic(m.getModifiers()) && !Modifier.isStatic(m.getModifiers())) {
String name = m.getName().toLowerCase();
if (Strings.contains(name, "save") || Strings.contains(name, "remove")
|| Strings.contains(name, "export") || Strings.contains(name, "import")
|| Strings.contains(name, "execute") || Strings.contains(name, "toString")) { return false; }
return true;
}
return false;
}
/**
* generator default results by method name
*
* @param clazz
* @return
*/
protected List buildResultConfigs(Class> clazz, PackageConfig.Builder pcb) {
List configs = CollectUtils.newArrayList();
// load annotation results
Result[] results = new Result[0];
Results rs = clazz.getAnnotation(Results.class);
if (null == rs) {
org.beangle.struts2.annotation.Action an = clazz
.getAnnotation(org.beangle.struts2.annotation.Action.class);
if (null != an) results = an.results();
} else {
results = rs.value();
}
Set annotationResults = CollectUtils.newHashSet();
if (null != results) {
for (Result result : results) {
String resultType = result.type();
if (Strings.isEmpty(resultType)) resultType = "dispatcher";
ResultTypeConfig rtc = pcb.getResultType(resultType);
ResultConfig.Builder rcb = new ResultConfig.Builder(result.name(), rtc.getClassName());
if (null != rtc.getDefaultResultParam()) rcb.addParam(rtc.getDefaultResultParam(), result.location());
configs.add(rcb.build());
annotationResults.add(result.name());
}
}
// load ftl convension results
if (!preloadftl || null == profileService) return configs;
String extention = profileService.getProfile(clazz.getName()).getViewExtension();
if (!extention.equals("ftl")) return configs;
ResultTypeConfig rtc = pcb.getResultType("freemarker");
for (Method m : clazz.getMethods()) {
String methodName = m.getName();
if (!annotationResults.contains(methodName) && shouldGenerateResult(m)) {
String path = templateFinder.find(clazz, methodName, methodName, extention);
if (null != path) {
configs.add(new ResultConfig.Builder(m.getName(), rtc.getClassName())
.addParam(rtc.getDefaultResultParam(), path).build());
}
}
}
return configs;
}
protected PackageConfig.Builder buildPackageConfig(final Class> actionClass, Action action,
final Map packageConfigs) {
String actionPackage = actionClass.getPackage().getName();
PackageConfig.Builder pcb = null;
PackageConfig parent = null;
// find my package config builder and parent
while (null == pcb && null == parent && Strings.contains(actionPackage, '.')) {
pcb = packageConfigs.get(actionPackage);
if (null != pcb && !pcb.getNamespace().equals(action.getNamespace())) {
pcb = null;
}
parent = configuration.getPackageConfig(actionPackage);
actionPackage = Strings.substringBeforeLast(actionPackage, ".");
}
// try default
if (null == pcb) pcb = packageConfigs.get(defaultParentPackage);
if (null == parent) parent = configuration.getPackageConfig(defaultParentPackage);
if (!pcb.getNamespace().equals(action.getNamespace())) pcb = null;
if (null != pcb) {
packageConfigs.put(actionClass.getPackage().getName(), pcb);
} else {
// own package
String newPkg = actionClass.getPackage().getName();
if (parent.getName().equals(newPkg)) {
if (parent.getNamespace().equals(action.getNamespace())) pcb = new PackageConfig.Builder(parent);
else newPkg = actionClass.getName();
} else {
PackageConfig.Builder newly = packageConfigs.get(newPkg);
if (null != newly && !newly.getNamespace().equals(action.getNamespace()))
newPkg = actionClass.getName();
}
if (null == pcb) {
pcb = new PackageConfig.Builder(newPkg).namespace(action.getNamespace()).addParent(parent);
logger.debug("Created package config {} under {}", actionPackage, action.getNamespace());
}
pcb.strictMethodInvocation(false);
packageConfigs.put(newPkg, pcb);
}
return pcb;
}
@Inject(StrutsConstants.STRUTS_DEVMODE)
public void setDevMode(String mode) {
devMode = "true".equals(mode);
}
@Inject("beangle.i18n.reload")
public void setReloadBundles(String mode) {
reloadBundles = "true".equals(mode);
}
@Inject("beangle.convention.preloadftl")
public void setPreloadftl(String mode) {
preloadftl = "true".equals(mode);
}
public boolean needsReload() {
return devMode;
}
private TemplateFinderByLoader buildTemplateFinder() {
ServletContext sc = ServletActionContext.getServletContext();
TemplateLoader loader = freemarkerManager.getConfiguration(sc).getTemplateLoader();
return new TemplateFinderByLoader(loader, viewMapper);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy