Author: musachy Date: Sun Dec 21 10:30:18 2008 New Revision: 728469 URL: http://svn.apache.org/viewvc?rev=728469&view=rev Log: WW-2931 Make convention plugin reload the configuration when a class that contains actions changes in the file system
Added: struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/ClasspathPackageProvider.java struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/ struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/FileResourceStore.java (with props) struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/ReloadingClassLoader.java (with props) struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/ResourceStore.java (with props) struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/ResourceStoreClassLoader.java (with props) Modified: struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/ActionConfigBuilder.java struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/ClasspathConfigurationProvider.java struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/PackageBasedActionConfigBuilder.java struts/struts2/trunk/plugins/convention/src/main/resources/struts-plugin.xml Modified: struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/ActionConfigBuilder.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/ActionConfigBuilder.java?rev=728469&r1=728468&r2=728469&view=diff ============================================================================== --- struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/ActionConfigBuilder.java (original) +++ struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/ActionConfigBuilder.java Sun Dec 21 10:30:18 2008 @@ -35,4 +35,8 @@ * via XWork dependency injetion. */ void buildActionConfigs(); + + boolean needsReload(); + + void destroy(); } \ No newline at end of file Modified: struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/ClasspathConfigurationProvider.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/ClasspathConfigurationProvider.java?rev=728469&r1=728468&r2=728469&view=diff ============================================================================== --- struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/ClasspathConfigurationProvider.java (original) +++ struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/ClasspathConfigurationProvider.java Sun Dec 21 10:30:18 2008 @@ -26,56 +26,83 @@ 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) { } } \ No newline at end of file Added: struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/ClasspathPackageProvider.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/ClasspathPackageProvider.java?rev=728469&view=auto ============================================================================== --- struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/ClasspathPackageProvider.java (added) +++ struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/ClasspathPackageProvider.java Sun Dec 21 10:30:18 2008 @@ -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(); + } +} Modified: struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/PackageBasedActionConfigBuilder.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/PackageBasedActionConfigBuilder.java?rev=728469&r1=728468&r2=728469&view=diff ============================================================================== --- struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/PackageBasedActionConfigBuilder.java (original) +++ struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/PackageBasedActionConfigBuilder.java Sun Dec 21 10:30:18 2008 @@ -31,6 +31,7 @@ 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.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.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 @@ 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 @@ 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 @@ */ @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 @@ } /** - * @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 @@ } /** - * @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 @@ } /** - * @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 @@ } /** - * @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 @@ } /** - * @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 @@ } /** - * @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 @@ * {...@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 @@ } } + 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ * 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 @@ /** * 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 @@ * 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 @@ 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 @@ /** * 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 @@ // 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 @@ } 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 @@ 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 @@ * * 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 @@ 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 @@ } } 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 @@ 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; + } } Added: struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/FileResourceStore.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/FileResourceStore.java?rev=728469&view=auto ============================================================================== --- struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/FileResourceStore.java (added) +++ struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/FileResourceStore.java Sun Dec 21 10:30:18 2008 @@ -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(); + } + + +} Propchange: struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/FileResourceStore.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/FileResourceStore.java ------------------------------------------------------------------------------ svn:keywords = Id Added: struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/ReloadingClassLoader.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/ReloadingClassLoader.java?rev=728469&view=auto ============================================================================== --- struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/ReloadingClassLoader.java (added) +++ struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/ReloadingClassLoader.java Sun Dec 21 10:30:18 2008 @@ -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); + } +} + Propchange: struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/ReloadingClassLoader.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/ReloadingClassLoader.java ------------------------------------------------------------------------------ svn:keywords = Id Added: struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/ResourceStore.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/ResourceStore.java?rev=728469&view=auto ============================================================================== --- struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/ResourceStore.java (added) +++ struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/ResourceStore.java Sun Dec 21 10:30:18 2008 @@ -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); +} + Propchange: struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/ResourceStore.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/ResourceStore.java ------------------------------------------------------------------------------ svn:keywords = Id Added: struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/ResourceStoreClassLoader.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/ResourceStoreClassLoader.java?rev=728469&view=auto ============================================================================== --- struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/ResourceStoreClassLoader.java (added) +++ struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/ResourceStoreClassLoader.java Sun Dec 21 10:30:18 2008 @@ -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; + } +} Propchange: struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/ResourceStoreClassLoader.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: struts/struts2/trunk/plugins/convention/src/main/java/org/apache/struts2/convention/classloader/ResourceStoreClassLoader.java ------------------------------------------------------------------------------ svn:keywords = Id Modified: struts/struts2/trunk/plugins/convention/src/main/resources/struts-plugin.xml URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/convention/src/main/resources/struts-plugin.xml?rev=728469&r1=728468&r2=728469&view=diff ============================================================================== --- struts/struts2/trunk/plugins/convention/src/main/resources/struts-plugin.xml (original) +++ struts/struts2/trunk/plugins/convention/src/main/resources/struts-plugin.xml Sun Dec 21 10:30:18 2008 @@ -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>