Merge branch 'master' of https://github.com/apache/deltaspike
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/main/java/org/apache/deltaspike/core/impl/config/TypedResolverImpl.java b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/TypedResolverImpl.java
index cd981cf..8997a1e 100644
--- a/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/TypedResolverImpl.java
+++ b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/TypedResolverImpl.java
@@ -550,14 +550,23 @@
break;
}
- String variableValue = new TypedResolverImpl<String>(this.config, varName)
+ try
+ {
+ String variableValue = new TypedResolverImpl<String>(this.config, varName)
.withCurrentProjectStage(this.projectStageAware)
.evaluateVariables(true)
.getValue();
- if (variableValue != null)
+ if (variableValue != null)
+ {
+ value = value.replace("${" + varName + "}", variableValue);
+ }
+ }
+ catch (StackOverflowError soe)
{
- value = value.replace("${" + varName + "}", variableValue);
+ // just log out
+ LOG.severe("Recursive variable resolution detected for " + varName);
+ throw soe;
}
startVar++;
}
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/parent/pom.xml b/deltaspike/parent/pom.xml
index e9f0253..a4b4ff0 100644
--- a/deltaspike/parent/pom.xml
+++ b/deltaspike/parent/pom.xml
@@ -73,7 +73,7 @@
<mojarra1.version>1.2_14</mojarra1.version>
<!-- used for some bytecode proxy stuff. Shaded in -->
- <asm.version>7.2</asm.version>
+ <asm.version>8.0.1</asm.version>
<!-- Geronimo specs -->
<geronimo-annotation_1.2_spec.version>1.0</geronimo-annotation_1.2_spec.version>
diff --git a/site/src/main/asciidoc/articles.adoc b/site/src/main/asciidoc/articles.adoc
index e9e65e9..392492a 100644
--- a/site/src/main/asciidoc/articles.adoc
+++ b/site/src/main/asciidoc/articles.adoc
@@ -27,3 +27,6 @@
* http://rmannibucau.wordpress.com/2013/11/20/deltaspike-data-repositories-with-dtos/[DeltaSpike Data: repositories with DTOs!]
* http://jaxenter.com/introducing-apache-deltaspike-42925.html[Closing the Gaps: Introducing Apache Deltaspike]
* http://jsfcorner.blogspot.com.au/2013/01/deltaspike-jsf-message-system.html[DeltaSpike JSF message system]
+* https://medium.com/danieldiasjava/simplificando-persistencia-de-dados-com-apache-deltaspike-data-6fd27bb2d821[Simplificando persistência de Dados com Apache DeltaSpike Data - PT-BR]
+* https://medium.com/danieldiasjava/conhecendo-apache-deltaspike-configuration-a24516468a9b[Conhecendo Apache DeltaSpike: Configuration - PT-BR]
+* https://medium.com/danieldiasjava/conhecendo-apache-deltaspike-injecting-resources-fa4e5585c2ea[Conhecendo Apache DeltaSpike: Injecting Resources - PT-BR]
\ No newline at end of file
diff --git a/site/src/main/asciidoc/external.adoc b/site/src/main/asciidoc/external.adoc
index 150bae0..94edd7a 100644
--- a/site/src/main/asciidoc/external.adoc
+++ b/site/src/main/asciidoc/external.adoc
@@ -74,6 +74,14 @@
**Video:** https://www.youtube.com/watch?v=3McmEi3cs_s
+=== Simplificando la persistencia de datos con Apache DeltaSpike Data (Java User Group de Nicaragua)
+
+**Slide:** https://speakerdeck.com/danieldiasjava/simplificando-la-persistencia-de-datos-con-apache-deltaspike-data
+
+**Video:** https://youtu.be/djM51tlJuLs
+
+**Example:** https://github.com/Daniel-Dos/danieldiasjava-palestras/tree/master/JUGNicaragua
+
== Examples
=== IdeaFork (full)
@@ -150,6 +158,26 @@
**Source code:** https://github.com/SouJava-Rio/soujava-rio-labs/tree/master/jax-rs-samples/Jax-rs-deltaspike-angular2-4/jax-rs-sample-cdi-deltaspike-data
+=== [spanish] Oracle Helidon with DeltaSpike-Data
+Simple CRUD example based on Helidon and DeltaSpike-data
+
+**Source code:** https://github.com/Daniel-Dos/danieldiasjava-palestras/tree/master/JUGNicaragua
+
+=== [pt-BR] Eclipse vertx with DeltaSpike-Data
+Simple example based on vertx and DeltaSpike-data
+
+**Source code:** https://github.com/Daniel-Dos/DanielDiasjava-Blog/tree/master/Projeto-Cloud/vertx
+
+=== [pt-BR] Eclipse vertx with DeltaSpike-Data
+Simple example based on vertx and DeltaSpike-data
+
+**Source code:** https://github.com/Daniel-Dos/DanielDiasjava-Blog/tree/master/Projeto-Cloud/vertx
+
+=== [pt-BR] Javalin with DeltaSpike-Data
+Simple example based on Javalin and DeltaSpike-data
+
+**Source code:** https://github.com/Daniel-Dos/DanielDiasjava-Blog/tree/master/Projeto-Cloud/javalin
+
== Magazines
=== [pt-BR] Brazilian Java Magazine