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>


Reply via email to