Merge pull request #106 from Daniel-Dos/DELTASPIKE-1404

DELTASPIKE-1404 - add some links for example and articles.
diff --git a/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/api/config/base/CoreBaseConfig.java b/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/api/config/base/CoreBaseConfig.java
index e988f57..313bb9b 100644
--- a/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/api/config/base/CoreBaseConfig.java
+++ b/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/api/config/base/CoreBaseConfig.java
@@ -26,6 +26,11 @@
 {
     interface BeanManagerIntegration
     {
+        /**
+         * Whether to use CDI.current() if available.
+         * This might fail in some CDI containers in some situations.
+         * Try switching to 'false' to enforce the 'old' DeltaSpike BeanManager lookup.
+         */
         Boolean DELEGATE_LOOKUP =
                 ConfigResolver.resolve("deltaspike.bean-manager.delegate_lookup")
                         .as(Boolean.class)
diff --git a/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/api/provider/BeanManagerProvider.java b/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/api/provider/BeanManagerProvider.java
index 117f13f..5897b59 100644
--- a/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/api/provider/BeanManagerProvider.java
+++ b/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/api/provider/BeanManagerProvider.java
@@ -186,7 +186,8 @@
         setBeanManagerProvider(this);
 
         // CDI#current delegation enabled, skip everything
-        if (CDI_CURRENT_METHOD != null && CDI_CURRENT_BEAN_MANAGER_METHOD != null)
+        if (CDI_CURRENT_METHOD != null && CDI_CURRENT_BEAN_MANAGER_METHOD != null &&
+            resolveBeanManagerViaStaticHelper() != null)
         {
             return;
         }
@@ -208,7 +209,11 @@
         // CDI#current delegation enabled, skip everything
         if (CDI_CURRENT_METHOD != null && CDI_CURRENT_BEAN_MANAGER_METHOD != null)
         {
-            return resolveBeanManagerViaStaticHelper();
+            BeanManager bm = resolveBeanManagerViaStaticHelper();
+            if (bm != null)
+            {
+                return bm;
+            }
         }
 
         BeanManagerInfo bmi = getBeanManagerInfo(ClassUtils.getClassLoader(null));
@@ -274,7 +279,8 @@
     public void cleanupFinalBeanManagers(@Observes AfterDeploymentValidation adv)
     {
         // CDI#current delegation enabled, skip everything
-        if (CDI_CURRENT_METHOD != null && CDI_CURRENT_BEAN_MANAGER_METHOD != null)
+        if (CDI_CURRENT_METHOD != null && CDI_CURRENT_BEAN_MANAGER_METHOD != null &&
+            resolveBeanManagerViaStaticHelper() != null)
         {
             return;
         }
diff --git a/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/util/ClassDeactivationUtils.java b/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/util/ClassDeactivationUtils.java
index 3f6477d..b6555e7 100644
--- a/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/util/ClassDeactivationUtils.java
+++ b/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/util/ClassDeactivationUtils.java
@@ -64,6 +64,17 @@
     }
 
     /**
+     * Flush the caches to prevent ClassLoader leaks.
+     * This is called internally by DeltaSpike.
+     * Users do not have to explicitly call this method.
+     * Does not have side effects as the cache is idempotent anyway.
+     */
+    public static void clearCache()
+    {
+        classDeactivatorMap.clear();
+    }
+
+    /**
      * Evaluates if the given {@link Deactivatable} is active.
      *
      * @param targetClass {@link Deactivatable} under test.
diff --git a/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/ConfigurationExtension.java b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/ConfigurationExtension.java
index 164dc16..3b7fcad 100644
--- a/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/ConfigurationExtension.java
+++ b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/ConfigurationExtension.java
@@ -386,6 +386,9 @@
 
         ConfigResolver.freeConfigSources();
         detectedParentPropertyFileConfigs.remove(ClassUtils.getClassLoader(null));
+
+        // we also free the ClassDeactivationUtils cache
+        ClassDeactivationUtils.clearCache();
     }
 
     /**
diff --git a/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/PropertiesConfigSource.java b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/PropertiesConfigSource.java
index a2749d8..142d8b7 100644
--- a/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/PropertiesConfigSource.java
+++ b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/PropertiesConfigSource.java
@@ -23,7 +23,7 @@
 import java.util.Properties;
 
 /**
- * Base class for configuration sources based on {@link Properties} object.
+ * Base class for configuration sources based on a fixed {@link Properties} object.
  */
 public abstract class PropertiesConfigSource extends BaseConfigSource
 {
@@ -50,7 +50,7 @@
     @Override
     public Map<String, String> getProperties()
     {
-        Map<String,String> result = new HashMap<String, String>();
+        Map<String,String> result = new HashMap<String, String>(properties.size());
         for (String propertyName : properties.stringPropertyNames())
         {
             result.put(propertyName, properties.getProperty(propertyName));
diff --git a/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/PropertyFileConfigSource.java b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/PropertyFileConfigSource.java
index 5f15c6f..d549ab6 100644
--- a/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/PropertyFileConfigSource.java
+++ b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/PropertyFileConfigSource.java
@@ -19,30 +19,248 @@
 package org.apache.deltaspike.core.impl.config;
 
 import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import java.util.logging.Level;
 
+import org.apache.deltaspike.core.api.config.ConfigResolver;
 import org.apache.deltaspike.core.util.PropertyFileUtils;
 
 /**
  * {@link org.apache.deltaspike.core.spi.config.ConfigSource} which uses
- * <i>META-INF/apache-deltaspike.properties</i> for the lookup
+ * a fixed property file for the lookup.
+ *
+ * If the property file has a 'file://' protocol, we are able to pick up
+ * changes during runtime when the underlying property file changes.
+ * This does not make sense for property files in JARs, but makes perfect sense
+ * whenever a property file URL is directly on the file system.
  */
-class PropertyFileConfigSource extends PropertiesConfigSource
+public class PropertyFileConfigSource extends BaseConfigSource
 {
-    private String fileName;
+    /**
+     * The name of a property which can be defined inside the property file
+     * to define the amount of seconds after which the property file should
+     * be tested for changes again.
+     * Note that the test is performed by storing the lastChanged attribute of the
+     * underlying file.
+     *
+     * By default the time after which we look for changes is {@link #RELOAD_PERIOD_DEFAULT}.
+     * This can be changed by explicitly adding a property with the name defined in {@link #RELOAD_PERIOD}
+     * which contains the number of seconds after which we try to reload again.
+     * A zero or negative value means no dynamic reloading.
+     * <pre>
+     * # look for changes after 60 seconds
+     * deltaspike_reload=60
+     * </pre>
+     * Whether the file got changed is determined by the lastModifiedDate of the underlying file.
+     * <p>
+     * You can disable the whole reloading with a negative reload time, e.g.
+     * <pre>
+     * deltaspike_reload=-1
+     * </pre>
+     */
+    public static final String RELOAD_PERIOD = "deltaspike_reload";
+    public static final int RELOAD_PERIOD_DEFAULT = 300;
 
-    PropertyFileConfigSource(URL propertyFileUrl)
+    private final ConfigResolver.ConfigHelper configHelper;
+
+    /**
+     * currently loaded config properties.
+     */
+    private Map<String, String> properties;
+
+    private final URL propertyFileUrl;
+    private String filePath;
+
+    private int reloadAllSeconds = RELOAD_PERIOD_DEFAULT;
+    private Instant fileLastModified = null;
+
+    /**
+     * Reload after that time in seconds.
+     */
+    private int reloadAfterSec;
+
+    private Consumer<Set<String>> reportAttributeChange;
+
+    public PropertyFileConfigSource(URL propertyFileUrl)
     {
-        super(PropertyFileUtils.loadProperties(propertyFileUrl));
-        fileName = propertyFileUrl.toExternalForm();
+        this.propertyFileUrl = propertyFileUrl;
+        filePath = propertyFileUrl.toExternalForm();
+
+        this.properties = toMap(PropertyFileUtils.loadProperties(propertyFileUrl));
+
+        if (isFile(propertyFileUrl))
+        {
+
+            calculateReloadTime();
+            if (reloadAllSeconds < 0 )
+            {
+                configHelper = null;
+            }
+            else
+            {
+                fileLastModified = getLastModified();
+                configHelper = ConfigResolver.getConfigProvider().getHelper();
+                reloadAfterSec = getNowSeconds() + reloadAllSeconds;
+            }
+        }
+        else
+        {
+            configHelper = null;
+        }
+
         initOrdinal(100);
     }
 
+    private void calculateReloadTime()
+    {
+        final String reloadPeriod = properties.get(RELOAD_PERIOD);
+        if (reloadPeriod != null)
+        {
+            try
+            {
+                int reload = Integer.parseInt(reloadPeriod);
+                if (reload < 0)
+                {
+                    fileLastModified = null;
+                    log.info("Disable dynamic reloading for ConfigSource " + filePath);
+                }
+                else
+                {
+                    reloadAllSeconds = reload;
+                }
+            }
+            catch (NumberFormatException nfe)
+            {
+                log.warning("Wrong value for " + RELOAD_PERIOD + " property: " + reloadPeriod +
+                    ". Must be numeric in seconds. Using default " + RELOAD_PERIOD_DEFAULT);
+                reloadAllSeconds = RELOAD_PERIOD_DEFAULT;
+            }
+        }
+    }
+
+    protected Map<String, String> toMap(Properties properties)
+    {
+        Map<String,String> result = new HashMap<>(properties.size());
+        for (String propertyName : properties.stringPropertyNames())
+        {
+            result.put(propertyName, properties.getProperty(propertyName));
+        }
+
+        return Collections.unmodifiableMap(result);
+    }
+
+    @Override
+    public Map<String, String> getProperties()
+    {
+        if (needsReload())
+        {
+            reloadProperties();
+        }
+
+        return properties;
+    }
+
+    @Override
+    public String getPropertyValue(String key)
+    {
+        if (needsReload())
+        {
+            reloadProperties();
+        }
+
+        return properties.get(key);
+    }
+
+    private boolean needsReload()
+    {
+        if (fileLastModified != null && getNowSeconds() > reloadAfterSec)
+        {
+            final Instant newLastModified = getLastModified();
+            if (newLastModified != null && newLastModified.isAfter(fileLastModified))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private synchronized void reloadProperties()
+    {
+        // another thread might have already updated the properties.
+        if (needsReload())
+        {
+            final Map<String, String> newProps = toMap(PropertyFileUtils.loadProperties(propertyFileUrl));
+
+            final Set<String> modfiedAttributes = configHelper.diffConfig(properties, newProps);
+            if (!modfiedAttributes.isEmpty())
+            {
+                reportAttributeChange.accept(modfiedAttributes);
+            }
+
+            this.properties = newProps;
+
+            fileLastModified = getLastModified();
+
+            calculateReloadTime();
+            reloadAfterSec = getNowSeconds() + reloadAllSeconds;
+        }
+    }
+
+    private int getNowSeconds()
+    {
+        // this might overrun all 100 years or so.
+        // I think we can live with a faster reload all 100 years
+        // if we can spare needing to deal with atomic updates ;)
+        return (int) TimeUnit.NANOSECONDS.toSeconds(System.nanoTime());
+    }
+
+    private Instant getLastModified()
+    {
+        try
+        {
+            return Files.getLastModifiedTime(Paths.get(propertyFileUrl.toURI())).toInstant();
+        }
+        catch (Exception e)
+        {
+            log.log(Level.WARNING,
+                "Cannot dynamically reload property file {0}. Not able to read last modified date", filePath);
+            return null;
+        }
+    }
+
+    private boolean isFile(URL propertyFileUrl)
+    {
+        return "file".equalsIgnoreCase(propertyFileUrl.getProtocol());
+    }
+
     /**
      * {@inheritDoc}
      */
     @Override
     public String getConfigName()
     {
-        return fileName;
+        return filePath;
+    }
+
+    @Override
+    public void setOnAttributeChange(Consumer<Set<String>> reportAttributeChange)
+    {
+        this.reportAttributeChange = reportAttributeChange;
+    }
+
+    @Override
+    public boolean isScannable()
+    {
+        return true;
     }
 }
diff --git a/deltaspike/core/impl/src/test/java/org/apache/deltaspike/test/core/api/config/propertyconfigsource/BaseTestConfigProperty.java b/deltaspike/core/impl/src/test/java/org/apache/deltaspike/test/core/api/config/propertyconfigsource/BaseTestConfigProperty.java
index 61ababe..4362d76 100644
--- a/deltaspike/core/impl/src/test/java/org/apache/deltaspike/test/core/api/config/propertyconfigsource/BaseTestConfigProperty.java
+++ b/deltaspike/core/impl/src/test/java/org/apache/deltaspike/test/core/api/config/propertyconfigsource/BaseTestConfigProperty.java
@@ -20,12 +20,20 @@
 
 import javax.inject.Inject;
 
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.util.Collections;
+
+import org.apache.deltaspike.core.api.config.ConfigResolver;
+import org.apache.deltaspike.core.impl.config.PropertyFileConfigSource;
 import org.junit.Assert;
 import org.junit.Test;
 
 public class BaseTestConfigProperty
 {
     protected final static String CONFIG_FILE_NAME = "myconfig.properties";
+    protected static final String CONFIG_VALUE = "deltaspike.dynamic.reloadable.config.value";
 
     @Inject
     private MyBean myBean;
@@ -44,6 +52,33 @@
         Assert.assertEquals(8589934592l, myBean.getLongConfig());
         Assert.assertEquals(-1.1f, myBean.getFloatConfig(), 0);
         Assert.assertEquals(4e40, myBean.getDoubleConfig(), 0);
+    }
 
+    @Test
+    public void testDynamicReload() throws Exception
+    {
+        File prop = File.createTempFile("deltaspike-test", ".properties");
+        try (BufferedWriter bw = new BufferedWriter(new FileWriter(prop)))
+        {
+            bw.write(CONFIG_VALUE + "=1\ndeltaspike_reload=1\n");
+            bw.flush();
+        }
+        prop.deleteOnExit();
+
+        final PropertyFileConfigSource dynamicReloadConfigSource = new PropertyFileConfigSource(prop.toURI().toURL());
+        ConfigResolver.addConfigSources(Collections.singletonList(dynamicReloadConfigSource));
+
+        Assert.assertEquals("1", ConfigResolver.getPropertyValue(CONFIG_VALUE));
+
+        // we need to take care of file system granularity
+        Thread.sleep(2100L);
+
+        try (BufferedWriter bw = new BufferedWriter(new FileWriter(prop)))
+        {
+            bw.write(CONFIG_VALUE + "=2\ndeltaspike_reload=1\n");
+            bw.flush();
+        }
+
+        Assert.assertEquals("2", ConfigResolver.getPropertyValue(CONFIG_VALUE));
     }
 }
diff --git a/deltaspike/core/impl/src/test/java/org/apache/deltaspike/test/core/impl/activation/ClassDeactivationWarFileTest.java b/deltaspike/core/impl/src/test/java/org/apache/deltaspike/test/core/impl/activation/ClassDeactivationWarFileTest.java
index f2b32cc..76f95c4 100644
--- a/deltaspike/core/impl/src/test/java/org/apache/deltaspike/test/core/impl/activation/ClassDeactivationWarFileTest.java
+++ b/deltaspike/core/impl/src/test/java/org/apache/deltaspike/test/core/impl/activation/ClassDeactivationWarFileTest.java
@@ -35,7 +35,7 @@
 public class ClassDeactivationWarFileTest extends ClassDeactivationTest
 {
     /**
-     *X TODO creating a WebArchive is only a workaround because JavaArchive cannot contain other archives.
+     * NOTE: creating a WebArchive is only a workaround because JavaArchive cannot contain other archives.
      */
     @Deployment
     public static WebArchive deploy()
diff --git a/deltaspike/dist/full/pom.xml b/deltaspike/dist/full/pom.xml
index b97c244..c266a87 100644
--- a/deltaspike/dist/full/pom.xml
+++ b/deltaspike/dist/full/pom.xml
@@ -235,7 +235,9 @@
     <build>
         <plugins>
             <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-assembly-plugin</artifactId>
+                <version>3.2.0</version>
                 <executions>
                     <execution>
                         <id>assemble</id>
diff --git a/deltaspike/modules/test-control/impl/pom.xml b/deltaspike/modules/test-control/impl/pom.xml
index 5c12ddd..1c24e07 100644
--- a/deltaspike/modules/test-control/impl/pom.xml
+++ b/deltaspike/modules/test-control/impl/pom.xml
@@ -190,6 +190,22 @@
 
                 <!-- The remaining bits of this profile are located under deltaspike/parent/code/pom.xml -->
             </dependencies>
+
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <configuration>
+                            <!--
+                                Weld2 behaves weird when it comes to excluding @Alternative classes
+                                Those tests work fine with Weld1, Weld3 and all OWB versions, but not with Weld2
+                            -->
+                            <skip>true</skip>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
         </profile>
         <profile>
             <id>Weld3</id>
diff --git a/deltaspike/parent/code/pom.xml b/deltaspike/parent/code/pom.xml
index d1afb08..4a10e2b 100644
--- a/deltaspike/parent/code/pom.xml
+++ b/deltaspike/parent/code/pom.xml
@@ -41,7 +41,7 @@
         <version.activation>1.1.1</version.activation>
         <version.annotation>1.3.2</version.annotation>
         <bval.artifactId>bval-jsr303</bval.artifactId>
-		<validation.artifactId>geronimo-validation_1.0_spec</validation.artifactId>
+        <validation.artifactId>geronimo-validation_1.0_spec</validation.artifactId>
         <jboss.as.version>7.1.1.Final</jboss.as.version>
         <wildfly.version>10.1.0.Final</wildfly.version>
         <wildfly.arquillian.version>1.0.2.Final</wildfly.arquillian.version>
@@ -72,7 +72,7 @@
             <version>${project.version}</version>
             <scope>test</scope>
         </dependency>
-		
+        
         <!-- Preparation for Java 9+, include a dependency on the JAXB APIs -->
         <dependency>
             <groupId>javax.xml.bind</groupId>
@@ -183,13 +183,13 @@
                 <jdk>[1.9,)</jdk>
             </activation>
             <properties>
-				<!-- Need newer implementations to be able to run on Java9+ -->
+                <!-- Need newer implementations to be able to run on Java9+ -->
                 <bval.artifactId>bval-jsr</bval.artifactId>
-				<validation.artifactId>geronimo-validation_1.1_spec</validation.artifactId>
-				<hibernate.validator.version>6.0.17.Final</hibernate.validator.version>
+                <validation.artifactId>geronimo-validation_1.1_spec</validation.artifactId>
+                <hibernate.validator.version>6.0.17.Final</hibernate.validator.version>
             </properties>
         </profile>
-		
+        
         <profile>
             <!-- use this profile to compile and test DeltaSpike with Apache OpenWebBeans -->
             <id>OWB</id>
@@ -260,10 +260,10 @@
                     <scope>test</scope>
                 </dependency>
 
-				<dependency>
-					<groupId>org.apache.geronimo.specs</groupId>
-					<artifactId>${validation.artifactId}</artifactId>
-				</dependency>
+                <dependency>
+                    <groupId>org.apache.geronimo.specs</groupId>
+                    <artifactId>${validation.artifactId}</artifactId>
+                </dependency>
                 <dependency>
                     <groupId>org.apache.bval</groupId>
                     <artifactId>${bval.artifactId}</artifactId>
@@ -360,10 +360,10 @@
                     <scope>test</scope>
                 </dependency>
 
-				<dependency>
-					<groupId>org.apache.geronimo.specs</groupId>
-					<artifactId>${validation.artifactId}</artifactId>
-				</dependency>
+                <dependency>
+                    <groupId>org.apache.geronimo.specs</groupId>
+                    <artifactId>${validation.artifactId}</artifactId>
+                </dependency>
                 <dependency>
                     <groupId>org.apache.bval</groupId>
                     <artifactId>${bval.artifactId}</artifactId>
@@ -466,10 +466,10 @@
                     <scope>test</scope>
                 </dependency>
 
-				<dependency>
-					<groupId>org.apache.geronimo.specs</groupId>
-					<artifactId>${validation.artifactId}</artifactId>
-				</dependency>
+                <dependency>
+                    <groupId>org.apache.geronimo.specs</groupId>
+                    <artifactId>${validation.artifactId}</artifactId>
+                </dependency>
                 <dependency>
                     <groupId>org.apache.bval</groupId>
                     <artifactId>${bval.artifactId}</artifactId>
@@ -695,7 +695,7 @@
 
             <properties>
                 <!-- Actual Weld version used with this profile -->
-                <weld.version>3.0.4.Final</weld.version>
+                <weld.version>3.1.3.Final</weld.version>
                 <cdicontainer.version>weld-${weld.version}</cdicontainer.version>
             </properties>
             
@@ -753,7 +753,11 @@
                     <groupId>org.jboss.weld</groupId>
                     <artifactId>weld-api</artifactId>
                 </dependency>
-                
+                <dependency>
+                    <groupId>org.jboss.weld</groupId>
+                    <artifactId>weld-spi</artifactId>
+                </dependency>
+
                 <!--Other than Weld dependencies-->
                 <dependency>
                     <groupId>javax.el</groupId>
diff --git a/deltaspike/parent/pom.xml b/deltaspike/parent/pom.xml
index 3341a73..e9f0253 100644
--- a/deltaspike/parent/pom.xml
+++ b/deltaspike/parent/pom.xml
@@ -100,7 +100,7 @@
         <maven.dependency.plugin.version>2.4</maven.dependency.plugin.version>
         <maven.checkstyle.plugin.version>2.16</maven.checkstyle.plugin.version>
         <maven.compiler.plugin.version>3.8.1</maven.compiler.plugin.version>
-        <maven.assembly.plugin.version>2.4</maven.assembly.plugin.version>
+        <maven.assembly.plugin.version>3.2.0</maven.assembly.plugin.version>
         <maven.artifact.version>3.0</maven.artifact.version>
 
         <hamcrest.version>1.3</hamcrest.version>
diff --git a/pom.xml b/pom.xml
index 62982b1..75b91a0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache</groupId>
         <artifactId>apache</artifactId>
-        <version>21</version>
+        <version>23</version>
     </parent>
 
     <!--