blob: 8b81cb9f59ebc11239c56319b4fa8ff7bedf7e2b [file] [log] [blame]
/*
* $Id$
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.convention;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Actions;
import org.apache.struts2.convention.annotation.AnnotationTools;
import org.apache.struts2.convention.annotation.DefaultInterceptorRef;
import org.apache.struts2.convention.annotation.ExceptionMapping;
import org.apache.struts2.convention.annotation.ExceptionMappings;
import org.apache.struts2.convention.annotation.Namespace;
import org.apache.struts2.convention.annotation.Namespaces;
import org.apache.struts2.convention.annotation.ParentPackage;
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.ExceptionMappingConfig;
import com.opensymphony.xwork2.config.entities.InterceptorMapping;
import com.opensymphony.xwork2.config.entities.PackageConfig;
import com.opensymphony.xwork2.config.entities.ResultConfig;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.finder.ClassFinder;
import com.opensymphony.xwork2.util.finder.Test;
import com.opensymphony.xwork2.util.finder.UrlSet;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
/**
* <p>
* This class implements the ActionConfigBuilder interface.
* </p>
*/
public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
private static final Logger LOG = LoggerFactory.getLogger(PackageBasedActionConfigBuilder.class);
private final Configuration configuration;
private final ActionNameBuilder actionNameBuilder;
private final ResultMapBuilder resultMapBuilder;
private final InterceptorMapBuilder interceptorMapBuilder;
private final ObjectFactory objectFactory;
private final String defaultParentPackage;
private final boolean redirectToSlash;
private String[] actionPackages;
private String[] excludePackages;
private String[] packageLocators;
private String[] excludeJars;
private boolean disableJarScanning = true;
private boolean disableActionScanning = false;
private boolean disablePackageLocatorsScanning = false;
private String actionSuffix = "Action";
private boolean checkImplementsAction = true;
private boolean mapAllMatches = false;
/**
* Constructs actions based on a list of packages.
*
* @param configuration The XWork configuration that the new package configs and action configs
* are added to.
* @param actionNameBuilder The action name builder used to convert action class names to action
* names.
* @param resultMapBuilder The result map builder used to create ResultConfig mappings for each
* action.
* @param interceptorMapBuilder The interceptor map builder used to create InterceptorConfig mappings for each
* action.
* @param objectFactory The ObjectFactory used to create the actions and such.
* @param redirectToSlash A boolean parameter that controls whether or not this will create an
* action for indexes. If this is set to true, index actions are not created because
* the unknown handler will redirect from /foo to /foo/. The only action that is created
* is to the empty action in the namespace (e.g. the namespace /foo and the action "").
* @param defaultParentPackage The default parent package for all the configuration.
*/
@Inject
public PackageBasedActionConfigBuilder(Configuration configuration, ActionNameBuilder actionNameBuilder,
ResultMapBuilder resultMapBuilder, InterceptorMapBuilder interceptorMapBuilder, ObjectFactory objectFactory,
@Inject("struts.convention.redirect.to.slash") String redirectToSlash,
@Inject("struts.convention.default.parent.package") String defaultParentPackage) {
// Validate that the parameters are okay
this.configuration = configuration;
this.actionNameBuilder = actionNameBuilder;
this.resultMapBuilder = resultMapBuilder;
this.interceptorMapBuilder = interceptorMapBuilder;
this.objectFactory = objectFactory;
this.redirectToSlash = Boolean.parseBoolean(redirectToSlash);
if (LOG.isTraceEnabled()) {
LOG.trace("Setting action default parent package to [#0]", defaultParentPackage);
}
this.defaultParentPackage = defaultParentPackage;
}
/**
* @param disableActionScanning Disable scanning for actions
*/
@Inject(value = "struts.convention.action.disableScanning", required = false)
public void setDisableActionScanning(String disableActionScanning) {
this.disableActionScanning = "true".equals(disableActionScanning);
}
/**
* @param exlcudeJars Comma separated list of regular expressions of jars to be exluded.
* Ignored if "struts.convention.action.disableJarScanning" is true
*/
@Inject(value = "struts.convention.action.excludeJars", required = false)
public void setExcludeJars(String excludeJars) {
this.excludeJars = excludeJars.split("\\s*[,]\\s*");;
}
/**
* @param disableJarScanning Disable scanning jar files for actions
*/
@Inject(value = "struts.convention.action.disableJarScanning", required = false)
public void setDisableJarScanning(String disableJarScanning) {
this.disableJarScanning = "true".equals(disableJarScanning);
}
/**
* @param disableActionScanning If set to true, only the named packages will be scanned
*/
@Inject(value = "struts.convention.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 = "struts.convention.action.packages", required = false)
public void setActionPackages(String actionPackages) {
if (!StringTools.isTrimmedEmpty(actionPackages)) {
this.actionPackages = actionPackages.split("\\s*[,]\\s*");
}
}
/**
* @param actionPackages (Optional) Map classes that implement com.opensymphony.xwork2.Action
* as actions
*/
@Inject(value = "struts.convention.action.checkImplementsAction", 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 = "struts.convention.action.suffix", required = false)
public void setActionSuffix(String actionSuffix) {
if (!StringTools.isTrimmedEmpty(actionSuffix)) {
this.actionSuffix = actionSuffix;
}
}
/**
* @param excludePackages (Optional) A list of packages that should be skipped when building
* configuration.
*/
@Inject(value = "struts.convention.exclude.packages", required = false)
public void setExcludePackages(String excludePackages) {
if (!StringTools.isTrimmedEmpty(excludePackages)) {
this.excludePackages = excludePackages.split("\\s*[,]\\s*");
}
}
/**
* @param packageLocators (Optional) A list of names used to find action packages.
*/
@Inject(value = "struts.convention.package.locators", required = false)
public void setPackageLocators(String packageLocators) {
this.packageLocators = packageLocators.split("\\s*[,]\\s*");
}
/**
* @param packageLocators (Optional) Map actions that match the "*${Suffix}" pattern
* even if they don't have a default method. The mapping from
* the url to the action will be delegated the action mapper.
*/
@Inject(value = "struts.convention.action.mapAllMatches", required = false)
public void setMapAllMatches(String mapAllMatches) {
this.mapAllMatches = "true".equals(mapAllMatches);
}
/**
* Builds the action configurations by loading all classes in the packages specified by the
* property <b>struts.convention.action.packages</b> and then figuring out which classes implement Action
* or have Action in their name. Next, if this class is in a Java package that hasn't been
* inspected a new PackageConfig (XWork) is created for that Java package using the Java package
* name. This will contain all the ActionConfigs for all the Action classes that are discovered
* within that Java package. Next, each class is inspected for the {@link ParentPackage}
* annotation which is used to control the parent package for a specific action. Lastly, the
* {@link ResultMapBuilder} is used to create ResultConfig instances of the action.
*/
public void buildActionConfigs() {
if (!disableActionScanning ) {
if (actionPackages == null && packageLocators == null) {
throw new ConfigurationException("At least a list of action packages or action package locators " +
"must be given using one of the properties [struts.convention.action.packages] or " +
"[struts.convention.package.locators]");
}
if (LOG.isTraceEnabled()) {
LOG.trace("Loading action configurations");
if (actionPackages != null) {
LOG.trace("Actions being loaded from action packages " + Arrays.asList(actionPackages));
}
if (packageLocators != null) {
LOG.trace("Actions being loaded using package locators " + Arrays.asList(packageLocators));
}
if (excludePackages != null) {
LOG.trace("Excluding actions from packages " + Arrays.asList(excludePackages));
}
}
Set<Class> classes = findActions();
buildConfiguration(classes);
}
}
@SuppressWarnings("unchecked")
protected Set<Class> findActions() {
Set<Class> classes = new HashSet<Class>();
try {
if (actionPackages != null || (packageLocators != null && !disablePackageLocatorsScanning)) {
ClassFinder finder = new ClassFinder(getClassLoader(), buildUrlSet().getUrls(), true);
// named packages
if (actionPackages != null) {
for (String packageName : actionPackages) {
Test<ClassFinder.ClassInfo> test = getPackageFinderTest(packageName);
classes.addAll(finder.findClasses(test));
}
}
//package locators
if (packageLocators != null && !disablePackageLocatorsScanning) {
for (String packageLocator : packageLocators) {
Test<ClassFinder.ClassInfo> test = getPackageLocatorTest(packageLocator);
classes.addAll(finder.findClasses(test));
}
}
}
} catch (Exception ex) {
if (LOG.isErrorEnabled())
LOG.error("Unable to scan named packages", ex);
}
return classes;
}
private UrlSet buildUrlSet() throws IOException {
UrlSet urlSet = new UrlSet(getClassLoader());
urlSet = urlSet.exclude(ClassLoader.getSystemClassLoader().getParent());
urlSet = urlSet.excludeJavaExtDirs();
urlSet = urlSet.excludeJavaEndorsedDirs();
urlSet = urlSet.excludeJavaHome();
urlSet = urlSet.excludePaths(System.getProperty("sun.boot.class.path", ""));
urlSet = urlSet.exclude(".*/JavaVM.framework/.*");
if (disableJarScanning) {
urlSet = urlSet.exclude(".*?jar(!/)?");
} else if (excludeJars != null) {
for (String pattern : excludeJars) {
urlSet = urlSet.exclude(pattern.trim());
}
}
return urlSet;
}
private ClassLoader getClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
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 = classPackageName.equals(packageName) || classPackageName.startsWith(strictPackageName);
boolean nameMatches = classInfo.getName().endsWith(actionSuffix);
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 Test<ClassFinder.ClassInfo> getPackageLocatorTest(final String packageLocator) {
return new Test<ClassFinder.ClassInfo>() {
public boolean test(ClassFinder.ClassInfo classInfo) {
String packageName = classInfo.getPackageName();
if (packageName.length() > 0) {
String[] splitted = packageName.split("\\.");
boolean packageMatches = StringTools.contains(splitted, packageLocator, false);
boolean nameMatches = classInfo.getName().endsWith(actionSuffix);
try {
return packageMatches && (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;
}
} else
return false;
}
};
}
@SuppressWarnings("unchecked")
protected void buildConfiguration(Set<Class> classes) {
Map<String, PackageConfig.Builder> packageConfigs = new HashMap<String, PackageConfig.Builder>();
for (Class<?> actionClass : classes) {
// Skip all interfaces, enums, annotations, and abstract classes
if (actionClass.isAnnotation() || actionClass.isInterface() || actionClass.isEnum() ||
(actionClass.getModifiers() & Modifier.ABSTRACT) != 0) {
continue;
}
// Tell the ObjectFactory about this class
try {
objectFactory.getClassInstance(actionClass.getName());
} catch (ClassNotFoundException e) {
// Impossible
new Throwable().printStackTrace();
System.exit(1);
}
// Determine the action package
String actionPackage = actionClass.getPackage().getName();
if (LOG.isDebugEnabled()) {
LOG.debug("Processing class [#0] in package [#1]", actionClass.getName(), actionPackage);
}
// Determine the default namespace and action name
List<String> namespaces = determineActionNamespace(actionClass);
for (String namespace : namespaces) {
String defaultActionName = determineActionName(actionClass);
String defaultActionMethod = "execute";
PackageConfig.Builder defaultPackageConfig = getPackageConfig(packageConfigs, namespace,
actionPackage, actionClass, null);
// Verify that the annotations have no errors and also determine if the default action
// configuration should still be built or not.
Map<String, List<Action>> map = getActionAnnotations(actionClass);
Set<String> actionNames = new HashSet<String>();
if (!map.containsKey(defaultActionMethod) && ReflectionTools.containsMethod(actionClass, defaultActionMethod)) {
boolean found = false;
for (String method : map.keySet()) {
List<Action> actions = map.get(method);
for (Action action : actions) {
// Check if there are duplicate action names in the annotations.
String actionName = action.value().equals(Action.DEFAULT_VALUE) ? defaultActionName : action.value();
if (actionNames.contains(actionName)) {
throw new ConfigurationException("The action class [" + actionClass +
"] contains two methods with an action name annotation whose value " +
"is the same (they both might be empty as well).");
} else {
actionNames.add(actionName);
}
// Check this annotation is the default action
if (action.value().equals(Action.DEFAULT_VALUE)) {
found = true;
}
}
}
// Build the default
if (!found) {
createActionConfig(defaultPackageConfig, actionClass, defaultActionName, defaultActionMethod, null);
}
}
// Build the actions for the annotations
for (String method : map.keySet()) {
List<Action> actions = map.get(method);
for (Action action : actions) {
PackageConfig.Builder pkgCfg = defaultPackageConfig;
if (action.value().contains("/")) {
pkgCfg = getPackageConfig(packageConfigs, namespace, actionPackage,
actionClass, action);
}
createActionConfig(pkgCfg, actionClass, defaultActionName, method, action);
}
}
// some actions will not have any @Action or a default method, like the rest actions
// where the action mapper is the one that finds the right method at runtime
if (map.isEmpty() && mapAllMatches) {
Action actionAnnotation = actionClass.getAnnotation(Action.class);
createActionConfig(defaultPackageConfig, actionClass, defaultActionName, null, actionAnnotation);
}
}
}
buildIndexActions(packageConfigs);
// Add the new actions to the configuration
Set<String> packageNames = packageConfigs.keySet();
for (String packageName : packageNames) {
configuration.addPackageConfig(packageName, packageConfigs.get(packageName).build());
}
}
/**
* Determines the namespace(s) for the action based on the action class. If there is a {@link Namespace}
* annotation on the class (including parent classes) or on the package that the class is in, than
* it is used. Otherwise, the Java package name that the class is in is used in conjunction with
* either the <b>struts.convention.action.packages</b> or <b>struts.convention.package.locators</b>
* configuration values. These are used to determine which part of the Java package name should
* be converted into the namespace for the XWork PackageConfig.
*
* @param actionClass The action class.
* @return The namespace or an empty string.
*/
protected List<String> determineActionNamespace(Class<?> actionClass) {
List<String> namespaces = new ArrayList<String>();
// Check if there is a class or package level annotation for the namespace
//single namespace
Namespace namespaceAnnotation = AnnotationTools.findAnnotation(actionClass, Namespace.class);
if (namespaceAnnotation != null) {
if (LOG.isTraceEnabled()) {
LOG.trace("Using non-default action namespace from Namespace annotation of [#0]", namespaceAnnotation.value());
}
namespaces.add(namespaceAnnotation.value());
}
//multiple annotations
Namespaces namespacesAnnotation = AnnotationTools.findAnnotation(actionClass, Namespaces.class);
if (namespacesAnnotation != null) {
if (LOG.isTraceEnabled()) {
StringBuilder sb = new StringBuilder();
for (Namespace namespace : namespacesAnnotation.value())
sb.append(namespace.value()).append(",");
sb.deleteCharAt(sb.length() - 1);
LOG.trace("Using non-default action namespaces from Namespaces annotation of [#0]", sb.toString());
}
for (Namespace namespace : namespacesAnnotation.value())
namespaces.add(namespace.value());
}
//don't use default if there are annotations
if (!namespaces.isEmpty())
return namespaces;
String pkg = actionClass.getPackage().getName();
String pkgPart = null;
if (actionPackages != null) {
for (String actionPackage : actionPackages) {
if (pkg.startsWith(actionPackage)) {
pkgPart = actionClass.getName().substring(actionPackage.length() + 1);
}
}
}
if (pkgPart == null && packageLocators != null) {
for (String packageLocator : packageLocators) {
int index = pkg.lastIndexOf(packageLocator);
// This ensures that the match is at the end, beginning or has a dot on each side of it
if (index >= 0 && (index + packageLocator.length() == pkg.length() || index == 0 ||
(pkg.charAt(index - 1) == '.' && pkg.charAt(index + packageLocator.length()) == '.'))) {
pkgPart = actionClass.getName().substring(index + packageLocator.length() + 1);
}
}
}
if (pkgPart != null) {
final int indexOfDot = pkgPart.lastIndexOf('.');
if (indexOfDot >= 0) {
String convertedNamespace = actionNameBuilder.build(pkgPart.substring(0, indexOfDot));
namespaces.add("/" + convertedNamespace.replace('.', '/'));
return namespaces;
}
}
namespaces.add("");
return namespaces;
}
/**
* Converts the class name into an action name using the ActionNameBuilder.
*
* @param actionClass The action class.
* @return The action name.
*/
protected String determineActionName(Class<?> actionClass) {
String actionName = actionNameBuilder.build(actionClass.getSimpleName());
if (LOG.isTraceEnabled()) {
LOG.trace("Got actionName for class [#0] of [#1]", actionClass.toString(), actionName);
}
return actionName;
}
/**
* Locates all of the {@link Actions} and {@link Action} annotations on methods within the Action
* class and its parent classes.
*
* @param actionClass The action class.
* @return The list of annotations or an empty list if there are none.
*/
protected Map<String, List<Action>> getActionAnnotations(Class<?> actionClass) {
Method[] methods = actionClass.getMethods();
Map<String, List<Action>> map = new HashMap<String, List<Action>>();
for (Method method : methods) {
Actions actionsAnnotation = method.getAnnotation(Actions.class);
if (actionsAnnotation != null) {
Action[] actionArray = actionsAnnotation.value();
boolean valuelessSeen = false;
List<Action> actions = new ArrayList<Action>();
for (Action ann : actionArray) {
if (ann.value().equals(Action.DEFAULT_VALUE) && !valuelessSeen) {
valuelessSeen = true;
} else if (ann.value().equals(Action.DEFAULT_VALUE)) {
throw new ConfigurationException("You may only add a single Action " +
"annotation that has no value parameter.");
}
actions.add(ann);
}
map.put(method.getName(), actions);
} else {
Action ann = method.getAnnotation(Action.class);
if (ann != null) {
map.put(method.getName(), Arrays.asList(ann));
}
}
}
return map;
}
/**
* Creates a single ActionConfig object.
*
* @param pkgCfg The package the action configuration instance will belong to.
* @param actionClass The action class.
* @param actionName The name of the action.
* @param actionMethod The method that the annotation was on (if the annotation is not null) or
* the default method (execute).
* @param annotation The ActionName annotation that might override the action name and possibly
*/
protected void createActionConfig(PackageConfig.Builder pkgCfg, Class<?> actionClass, String actionName,
String actionMethod, Action annotation) {
if (annotation != null) {
actionName = annotation.value() != null && annotation.value().equals(Action.DEFAULT_VALUE) ?
actionName : annotation.value();
actionName = StringTools.lastToken(actionName, "/");
}
ActionConfig.Builder actionConfig = new ActionConfig.Builder(pkgCfg.getName(),
actionName, actionClass.getName());
actionConfig.methodName(actionMethod);
if (LOG.isTraceEnabled()) {
LOG.trace("Creating action config for class [#0], name [#1] and package name [#2] in namespace [#3]",
actionClass.toString(), actionName, pkgCfg.getName(), pkgCfg.getNamespace());
}
//build interceptors
List<InterceptorMapping> interceptors = interceptorMapBuilder.build(actionClass, pkgCfg, actionName, annotation);
actionConfig.addInterceptors(interceptors);
//build results
Map<String, ResultConfig> results = resultMapBuilder.build(actionClass, annotation, actionName, pkgCfg.build());
actionConfig.addResultConfigs(results);
//add params
if (annotation != null)
actionConfig.addParams(StringTools.createParameterMap(annotation.params()));
//add exception mappings from annotation
if (annotation != null && annotation.exceptionMappings() != null)
actionConfig.addExceptionMappings(buildExceptionMappings(annotation.exceptionMappings(), actionName));
//add exception mapping from class
ExceptionMappings exceptionMappings = actionClass.getAnnotation(ExceptionMappings.class);
if (exceptionMappings != null)
actionConfig.addExceptionMappings(buildExceptionMappings(exceptionMappings.value(), actionName));
//add
pkgCfg.addActionConfig(actionName, actionConfig.build());
}
private List<ExceptionMappingConfig> buildExceptionMappings(ExceptionMapping[] exceptions, String actionName) {
List<ExceptionMappingConfig> exceptionMappings = new ArrayList<ExceptionMappingConfig>();
for (ExceptionMapping exceptionMapping : exceptions) {
if (LOG.isTraceEnabled())
LOG.trace("Mapping exception [#0] to result [#1] for action [#2]", exceptionMapping.exception(),
exceptionMapping.result(), actionName);
ExceptionMappingConfig.Builder builder = new ExceptionMappingConfig.Builder(null, exceptionMapping
.exception(), exceptionMapping.result());
if (exceptionMapping.params() != null)
builder.addParams(StringTools.createParameterMap(exceptionMapping.params()));
exceptionMappings.add(builder.build());
}
return exceptionMappings;
}
private PackageConfig.Builder getPackageConfig(final Map<String, PackageConfig.Builder> packageConfigs,
String actionNamespace, final String actionPackage, final Class<?> actionClass,
Action action) {
if (action != null && !action.value().equals(Action.DEFAULT_VALUE)) {
if (LOG.isTraceEnabled()) {
LOG.trace("Using non-default action namespace from the Action annotation of [#0]", action.value());
}
actionNamespace = StringTools.upToLastToken(action.value(), "/");
}
// Next grab the parent annotation from the class
ParentPackage parent = AnnotationTools.findAnnotation(actionClass, ParentPackage.class);
String parentName = null;
if (parent != null) {
if (LOG.isTraceEnabled()) {
LOG.trace("Using non-default parent package from annotation of [#0]", parent.value());
}
parentName = parent.value();
}
// Finally use the default
if (parentName == null) {
parentName = defaultParentPackage;
}
if (parentName == null) {
throw new ConfigurationException("Unable to determine the parent XWork package for the action class [" +
actionClass.getName() + "]");
}
PackageConfig parentPkg = configuration.getPackageConfig(parentName);
if (parentPkg == null) {
throw new ConfigurationException("Unable to locate parent package [" + parentName + "]");
}
// Grab based on package-namespace and if it exists, we need to ensure the existing one has
// the correct parent package. If not, we need to create a new package config
String name = actionPackage + "#" + parentPkg.getName() + "#" + actionNamespace;
PackageConfig.Builder pkgConfig = packageConfigs.get(name);
if (pkgConfig == null) {
pkgConfig = new PackageConfig.Builder(name).namespace(actionNamespace).addParent(parentPkg);
packageConfigs.put(name, pkgConfig);
//check for @DefaultInterceptorRef in the package
DefaultInterceptorRef defaultInterceptorRef = AnnotationTools.findAnnotation(actionClass, DefaultInterceptorRef.class);
if (defaultInterceptorRef != null) {
pkgConfig.defaultInterceptorRef(defaultInterceptorRef.value());
if (LOG.isTraceEnabled())
LOG.trace("Setting [#0] as the default interceptor ref for [#1]", defaultInterceptorRef.value(), pkgConfig.getName());
}
}
if (LOG.isTraceEnabled()) {
LOG.trace("Created package config named [#0] with a namespace [#1]", name, actionNamespace);
}
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 void buildIndexActions(Map<String, PackageConfig.Builder> packageConfigs) {
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);
}
// Step #1
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;
}
// Step #2
if (!redirectToSlash) {
int lastSlash = namespace.lastIndexOf('/');
if (lastSlash >= 0) {
String parentAction = namespace.substring(lastSlash + 1);
String parentNamespace = namespace.substring(0, lastSlash);
PackageConfig.Builder parent = byNamespace.get(parentNamespace);
if (parent == null || parent.build().getAllActionConfigs().get(parentAction) == null) {
if (parent == null) {
parent = new PackageConfig.Builder(parentNamespace).namespace(parentNamespace).
addParents(pkgConfig.build().getParents());
packageConfigs.put(parentNamespace, parent);
}
if (parent.build().getAllActionConfigs().get(parentAction) == null) {
parent.addActionConfig(parentAction, indexActionConfig);
}
} else if (LOG.isTraceEnabled()) {
LOG.trace("The parent namespace [#0] already contains " +
"an action [#1]", parentNamespace, parentAction);
}
}
}
// Step #3
if (pkgConfig.build().getAllActionConfigs().get("") == null) {
if (LOG.isTraceEnabled()) {
LOG.trace("Creating index ActionConfig with an action name of [] for the action " +
"class [#0]", indexActionConfig.getClassName());
}
pkgConfig.addActionConfig("", indexActionConfig);
}
}
}
}