WW-2931 Make convention plugin reload the configuration when a class that contains actions changes in the file system

git-svn-id: https://svn.apache.org/repos/asf/struts/struts2/trunk@728469 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Musachy Barroso
2008-12-21 18:30:18 +00:00
parent 6103113e9b
commit 254df5ef9b
9 changed files with 577 additions and 83 deletions
@@ -35,4 +35,8 @@ public interface ActionConfigBuilder {
* via XWork dependency injetion.
*/
void buildActionConfigs();
boolean needsReload();
void destroy();
}
@@ -26,56 +26,83 @@ import com.opensymphony.xwork2.config.ConfigurationProvider;
import com.opensymphony.xwork2.inject.ContainerBuilder;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.location.LocatableProperties;
import org.apache.struts2.dispatcher.DispatcherListener;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.StrutsConstants;
/**
* <p>
* 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. This doesn't contain any
* logic and instead delegates to the configured instance of the
* {@link ActionConfigBuilder} interface.
* Xwork will only reload configurations, if one ContainerProvider needs reloading, that's all this class does
* </p>
*/
public class ClasspathConfigurationProvider implements ConfigurationProvider {
public class ClasspathConfigurationProvider implements ConfigurationProvider, DispatcherListener {
private ActionConfigBuilder actionConfigBuilder;
private boolean devMode;
private boolean reload;
private boolean listeningToDispatcher;
@Inject
public ClasspathConfigurationProvider(ActionConfigBuilder actionConfigBuilder) {
this.actionConfigBuilder = actionConfigBuilder;
}
@Inject(StrutsConstants.STRUTS_DEVMODE)
public void setDevMode(String mode) {
this.devMode = "true".equals(mode);
}
@Inject("struts.convention.classes.reload")
public void setReload(String reload) {
this.reload = "true".equals(reload);
}
/**
* Not used.
*/
public void destroy() {
if (this.listeningToDispatcher)
Dispatcher.removeDispatcherListener(this);
actionConfigBuilder.destroy();
}
/**
* Not used.
*/
public void init(Configuration configuration) {
if (devMode && reload && !listeningToDispatcher) {
//this is the only way I found to be able to get added to to ConfigurationProvider list
//listening to events in Dispatcher
listeningToDispatcher = true;
Dispatcher.addDispatcherListener(this);
}
}
/**
* Does nothing.
*/
public void register(ContainerBuilder containerBuilder, LocatableProperties locatableProperties)
throws ConfigurationException {
throws ConfigurationException {
}
/**
* Loads the packages using the {@link ActionConfigBuilder}.
*
* @throws ConfigurationException
* @throws ConfigurationException
*/
public void loadPackages() throws ConfigurationException {
actionConfigBuilder.buildActionConfigs();
}
/**
* @return Always false.
* @return Always false.
*/
public boolean needsReload() {
return false;
return devMode && reload ? actionConfigBuilder.needsReload() : false;
}
public void dispatcherInitialized(Dispatcher du) {
du.getConfigurationManager().addContainerProvider(this);
}
public void dispatcherDestroyed(Dispatcher du) {
}
}
@@ -0,0 +1,54 @@
/*
* $Id: ClasspathConfigurationProvider.java 655902 2008-05-13 15:15:12Z bpontarelli $
*
* 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 com.opensymphony.xwork2.config.PackageProvider;
import com.opensymphony.xwork2.config.Configuration;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.inject.Inject;
/**
* <p>
* 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. This doesn't contain any
* logic and instead delegates to the configured instance of the
* {@link ActionConfigBuilder} interface.
* </p>
*/
public class ClasspathPackageProvider implements PackageProvider {
private ActionConfigBuilder actionConfigBuilder;
@Inject
public ClasspathPackageProvider(ActionConfigBuilder actionConfigBuilder) {
this.actionConfigBuilder = actionConfigBuilder;
}
public void init(Configuration configuration) throws ConfigurationException {
}
public boolean needsReload() {
return actionConfigBuilder.needsReload();
}
public void loadPackages() throws ConfigurationException {
actionConfigBuilder.buildActionConfigs();
}
}
@@ -31,6 +31,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.net.URL;
import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Actions;
@@ -41,6 +42,8 @@ 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 org.apache.struts2.convention.classloader.ReloadingClassLoader;
import org.apache.struts2.StrutsConstants;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.config.Configuration;
@@ -56,6 +59,7 @@ 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;
import com.opensymphony.xwork2.util.FileManager;
/**
* <p>
@@ -82,30 +86,34 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
private String actionSuffix = "Action";
private boolean checkImplementsAction = true;
private boolean mapAllMatches = false;
private Set<String> loadedFileUrls = new HashSet<String>();
private boolean devMode;
private ReloadingClassLoader reloadingClassLoader;
private boolean reload;
/**
* 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.
* @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) {
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;
@@ -122,6 +130,16 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
this.defaultParentPackage = defaultParentPackage;
}
@Inject(StrutsConstants.STRUTS_DEVMODE)
public void setDevMode(String mode) {
this.devMode = "true".equals(mode);
}
@Inject("struts.convention.classes.reload")
public void setReload(String reload) {
this.reload = "true".equals(reload);
}
/**
* @param disableActionScanning Disable scanning for actions
*/
@@ -136,7 +154,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
*/
@Inject(value = "struts.convention.action.excludeJars", required = false)
public void setExcludeJars(String excludeJars) {
this.excludeJars = excludeJars.split("\\s*[,]\\s*");;
this.excludeJars = excludeJars.split("\\s*[,]\\s*");
}
/**
@@ -156,8 +174,8 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
}
/**
* @param actionPackages (Optional) An optional list of action packages that this should create
* configuration for.
* @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) {
@@ -167,8 +185,8 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
}
/**
* @param actionPackages (Optional) Map classes that implement com.opensymphony.xwork2.Action
* as actions
* @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) {
@@ -176,8 +194,8 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
}
/**
* @param actionSuffix (Optional) Classes that end with these value will be mapped as actions
* (defaults to "Action")
* @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) {
@@ -187,8 +205,8 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
}
/**
* @param excludePackages (Optional) A list of packages that should be skipped when building
* configuration.
* @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) {
@@ -198,7 +216,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
}
/**
* @param packageLocators (Optional) A list of names used to find action packages.
* @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) {
@@ -206,24 +224,29 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
}
/**
* @param packageLocatorsBasePackage (Optional) If set, only packages that start with this
* name will be scanned for actions.
* @param packageLocatorsBasePackage (Optional) If set, only packages that start with this
* name will be scanned for actions.
*/
@Inject(value = "struts.convention.package.locators.basePackage", required = false)
public void setPackageLocatorsBase(String packageLocatorsBasePackage) {
this.packageLocatorsBasePackage = packageLocatorsBasePackage;
}
}
/**
* @param mapAllMatches (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.
* @param mapAllMatches (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);
this.mapAllMatches = "true".equals(mapAllMatches);
}
protected void initReloadClassLoader() {
//when the configuration is reloaded, a new classloader will be setup
if (isReloadEnabled() && reloadingClassLoader == null)
reloadingClassLoader = new ReloadingClassLoader(getClassLoader());
}
/**
* 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
@@ -235,11 +258,14 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
* {@link ResultMapBuilder} is used to create ResultConfig instances of the action.
*/
public void buildActionConfigs() {
if (!disableActionScanning ) {
//setup reload class loader based on dev settings
initReloadClassLoader();
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]");
"must be given using one of the properties [struts.convention.action.packages] or " +
"[struts.convention.package.locators]");
}
if (LOG.isTraceEnabled()) {
@@ -260,12 +286,20 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
}
}
protected ClassLoader getClassLoaderForFinder() {
return isReloadEnabled() ? this.reloadingClassLoader : getClassLoader();
}
protected boolean isReloadEnabled() {
return devMode && reload;
}
@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);
ClassFinder finder = new ClassFinder(getClassLoaderForFinder(), buildUrlSet().getUrls(), true);
// named packages
if (actionPackages != null) {
@@ -326,7 +360,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
boolean nameMatches = classInfo.getName().endsWith(actionSuffix);
try {
return inPackage && (nameMatches || (checkImplementsAction && com.opensymphony.xwork2.Action.class.isAssignableFrom(classInfo.get())));
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());
@@ -347,7 +381,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
boolean nameMatches = classInfo.getName().endsWith(actionSuffix);
try {
return packageMatches && (nameMatches || (checkImplementsAction && com.opensymphony.xwork2.Action.class.isAssignableFrom(classInfo.get())));
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());
@@ -391,7 +425,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
String defaultActionName = determineActionName(actionClass);
String defaultActionMethod = "execute";
PackageConfig.Builder defaultPackageConfig = getPackageConfig(packageConfigs, namespace,
actionPackage, actionClass, null);
actionPackage, actionClass, null);
// Verify that the annotations have no errors and also determine if the default action
// configuration should still be built or not.
@@ -407,8 +441,8 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
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).");
"] contains two methods with an action name annotation whose value " +
"is the same (they both might be empty as well).");
} else {
actionNames.add(actionName);
}
@@ -433,7 +467,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
PackageConfig.Builder pkgCfg = defaultPackageConfig;
if (action.value().contains("/")) {
pkgCfg = getPackageConfig(packageConfigs, namespace, actionPackage,
actionClass, action);
actionClass, action);
}
createActionConfig(pkgCfg, actionClass, defaultActionName, method, action);
@@ -466,8 +500,8 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
* 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.
* @param actionClass The action class.
* @return The namespace or an empty string.
*/
protected List<String> determineActionNamespace(Class<?> actionClass) {
List<String> namespaces = new ArrayList<String>();
@@ -540,8 +574,8 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
/**
* Converts the class name into an action name using the ActionNameBuilder.
*
* @param actionClass The action class.
* @return The action name.
* @param actionClass The action class.
* @return The action name.
*/
protected String determineActionName(Class<?> actionClass) {
String actionName = actionNameBuilder.build(actionClass.getSimpleName());
@@ -556,8 +590,8 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
* 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.
* @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();
@@ -573,7 +607,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
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.");
"annotation that has no value parameter.");
}
actions.add(ann);
@@ -594,23 +628,23 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
/**
* 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
* @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) {
String actionMethod, Action annotation) {
if (annotation != null) {
actionName = annotation.value() != null && annotation.value().equals(Action.DEFAULT_VALUE) ?
actionName : annotation.value();
actionName : annotation.value();
actionName = StringTools.lastToken(actionName, "/");
}
ActionConfig.Builder actionConfig = new ActionConfig.Builder(pkgCfg.getName(),
actionName, actionClass.getName());
actionName, actionClass.getName());
actionConfig.methodName(actionMethod);
if (LOG.isDebugEnabled()) {
@@ -648,7 +682,14 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
// there is a package already with that name, check action
ActionConfig existingActionConfig = existingPkg.getActionConfigs().get(actionName);
if (existingActionConfig != null && LOG.isWarnEnabled())
LOG.warn("Duplicated action definition in package [#0] with name [#1]. First definition was loaded from [#3]", pkgCfg.getName(), actionName, existingActionConfig.getLocation().toString());
LOG.warn("Duplicated action definition in package [#0] with name [#1].", pkgCfg.getName(), actionName);
}
//watch class file
if (isReloadEnabled()) {
URL classFile = actionClass.getResource(actionClass.getSimpleName() + ".class");
FileManager.loadFile(classFile, false);
loadedFileUrls.add(classFile.toString());
}
}
@@ -670,8 +711,8 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
}
private PackageConfig.Builder getPackageConfig(final Map<String, PackageConfig.Builder> packageConfigs,
String actionNamespace, final String actionPackage, final Class<?> actionClass,
Action action) {
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());
@@ -697,7 +738,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
if (parentName == null) {
throw new ConfigurationException("Unable to determine the parent XWork package for the action class [" +
actionClass.getName() + "]");
actionClass.getName() + "]");
}
PackageConfig parentPkg = configuration.getPackageConfig(parentName);
@@ -735,12 +776,12 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
*
* 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
* 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
* the action is "" and the namespace is /foo
*
* @param packageConfigs Used to store the actions.
* @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>();
@@ -769,7 +810,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
if (parent == null || parent.build().getAllActionConfigs().get(parentAction) == null) {
if (parent == null) {
parent = new PackageConfig.Builder(parentNamespace).namespace(parentNamespace).
addParents(pkgConfig.build().getParents());
addParents(pkgConfig.build().getParents());
packageConfigs.put(parentNamespace, parent);
}
@@ -778,7 +819,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
}
} else if (LOG.isTraceEnabled()) {
LOG.trace("The parent namespace [#0] already contains " +
"an action [#1]", parentNamespace, parentAction);
"an action [#1]", parentNamespace, parentAction);
}
}
}
@@ -787,11 +828,30 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
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());
"class [#0]", indexActionConfig.getClassName());
}
pkgConfig.addActionConfig("", indexActionConfig);
}
}
}
public void destroy() {
loadedFileUrls.clear();
}
public boolean needsReload() {
if (devMode && reload) {
for (String url : loadedFileUrls) {
if (FileManager.fileNeedsReloading(url)) {
if (LOG.isDebugEnabled())
LOG.debug("File [#0] changed, configuration will be reloaded", url);
return true;
}
}
return false;
} else
return false;
}
}
@@ -0,0 +1,83 @@
/*
* $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.classloader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Reads a class from disk
* class taken from Apache JCI
*/
public final class FileResourceStore implements ResourceStore {
private final File root;
public FileResourceStore(final File pFile) {
root = pFile;
}
public byte[] read(final String pResourceName) {
FileInputStream fis = null;
try {
File file = getFile(pResourceName);
byte[] data = new byte[(int) file.length()];
fis = new FileInputStream(file);
fis.read(data);
return data;
} catch (Exception e) {
return null;
} finally {
closeQuietly(fis);
}
}
public void write(final String pResourceName, final byte[] pData) {
}
private void closeQuietly(InputStream is) {
try {
if (is != null)
is.close();
} catch (IOException e) {
}
}
public void remove(final String pResourceName) {
getFile(pResourceName).delete();
}
private File getFile(final String pResourceName) {
final String fileName = pResourceName.replace('/', File.separatorChar);
return new File(root, fileName);
}
public String toString() {
return this.getClass().getName() + root.toString();
}
}
@@ -0,0 +1,140 @@
/*
* $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.classloader;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import java.io.InputStream;
import java.io.File;
import java.net.URL;
import java.net.URISyntaxException;
import org.apache.struts2.StrutsException;
/**
* The ReloadingClassLoader uses a delegation mechansim to allow
* classes to be reloaded. That means that loadClass calls may
* return different results if the class was change in the underlying
* ResoruceStore.
* <p/>
* class taken from Apache JCI
*/
public class ReloadingClassLoader extends ClassLoader {
private static final Logger LOG = LoggerFactory.getLogger(ReloadingClassLoader.class);
private final ClassLoader parent;
private ResourceStore[] stores;
private ClassLoader delegate;
public ReloadingClassLoader(final ClassLoader pParent) {
super(pParent);
parent = pParent;
URL root = pParent.getResource("/");
try {
stores = new ResourceStore[]{new FileResourceStore(new File(root.toURI()))};
} catch (URISyntaxException e) {
throw new StrutsException("Unable to start the reloadable class loader, consider setting 'struts.convention.classes.reload' to false", e);
}
delegate = new ResourceStoreClassLoader(parent, stores);
}
public boolean addResourceStore(final ResourceStore pStore) {
try {
final int n = stores.length;
final ResourceStore[] newStores = new ResourceStore[n + 1];
System.arraycopy(stores, 0, newStores, 1, n);
newStores[0] = pStore;
stores = newStores;
delegate = new ResourceStoreClassLoader(parent, stores);
return true;
} catch (final RuntimeException e) {
LOG.error("could not add resource store " + pStore);
}
return false;
}
public boolean removeResourceStore(final ResourceStore pStore) {
final int n = stores.length;
int i = 0;
// FIXME: this should be improved with a Map
// find the pStore and index position with var i
while ((i < n) && (stores[i] != pStore)) {
i++;
}
// pStore was not found
if (i == n) {
return false;
}
// if stores length > 1 then array copy old values, else create new empty store
final ResourceStore[] newStores = new ResourceStore[n - 1];
if (i > 0) {
System.arraycopy(stores, 0, newStores, 0, i);
}
if (i < n - 1) {
System.arraycopy(stores, i + 1, newStores, i, (n - i - 1));
}
stores = newStores;
delegate = new ResourceStoreClassLoader(parent, stores);
return true;
}
public void reload() {
if (LOG.isTraceEnabled())
LOG.trace("Reloading class loader");
delegate = new ResourceStoreClassLoader(parent, stores);
}
public void clearAssertionStatus() {
delegate.clearAssertionStatus();
}
public URL getResource(String name) {
return delegate.getResource(name);
}
public InputStream getResourceAsStream(String name) {
return delegate.getResourceAsStream(name);
}
public Class loadClass(String name) throws ClassNotFoundException {
return delegate.loadClass(name);
}
public void setClassAssertionStatus(String className, boolean enabled) {
delegate.setClassAssertionStatus(className, enabled);
}
public void setDefaultAssertionStatus(boolean enabled) {
delegate.setDefaultAssertionStatus(enabled);
}
public void setPackageAssertionStatus(String packageName, boolean enabled) {
delegate.setPackageAssertionStatus(packageName, enabled);
}
}
@@ -0,0 +1,35 @@
/*
* $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.classloader;
/**
* *interface taken from Apache JCI
*/
public interface ResourceStore {
void write(final String pResourceName, final byte[] pResourceData);
byte[] read(final String pResourceName);
//FIXME: return the result of the remove
void remove(final String pResourceName);
}
@@ -0,0 +1,88 @@
/*
* $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.classloader;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
/**
* class taken from Apache JCI
*/
public final class ResourceStoreClassLoader extends ClassLoader {
private static final Logger LOG = LoggerFactory.getLogger(ResourceStoreClassLoader.class);
private final ResourceStore[] stores;
public ResourceStoreClassLoader(final ClassLoader pParent, final ResourceStore[] pStores) {
super(pParent);
stores = new ResourceStore[pStores.length];
System.arraycopy(pStores, 0, stores, 0, stores.length);
}
private Class fastFindClass(final String name) {
if (stores != null) {
for (int i = 0; i < stores.length; i++) {
final ResourceStore store = stores[i];
final byte[] clazzBytes = store.read(name.replace('.', '/') + ".class");
if (clazzBytes != null) {
return defineClass(name, clazzBytes, 0, clazzBytes.length);
}
}
}
return null;
}
protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class clazz = findLoadedClass(name);
if (clazz == null) {
clazz = fastFindClass(name);
if (clazz == null) {
final ClassLoader parent = getParent();
if (parent != null) {
clazz = parent.loadClass(name);
} else {
throw new ClassNotFoundException(name);
}
}
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
protected Class findClass(final String name) throws ClassNotFoundException {
final Class clazz = fastFindClass(name);
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
}
}
@@ -35,7 +35,8 @@
<bean type="org.apache.struts2.convention.InterceptorMapBuilder" class="org.apache.struts2.convention.DefaultInterceptorMapBuilder"/>
<bean type="org.apache.struts2.convention.ConventionsService" class="org.apache.struts2.convention.ConventionsServiceImpl"/>
<bean type="com.opensymphony.xwork2.config.PackageProvider" class="org.apache.struts2.convention.ClasspathConfigurationProvider"/>
<bean type="com.opensymphony.xwork2.config.PackageProvider" name="convention.packageProvider" class="org.apache.struts2.convention.ClasspathPackageProvider"/>
<bean type="com.opensymphony.xwork2.config.PackageProvider" name="convention.containerProvider" class="org.apache.struts2.convention.ClasspathConfigurationProvider"/>
<constant name="struts.convention.result.path" value="/WEB-INF/content/"/>
<constant name="struts.convention.result.flatLayout" value="true"/>
@@ -55,6 +56,8 @@
<constant name="struts.convention.redirect.to.slash" value="true"/>
<constant name="struts.mapper.alwaysSelectFullNamespace" value="true"/>
<constant name="struts.convention.classes.reload" value="false" />
<package name="convention-default" extends="struts-default">
</package>
</struts>