Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit 7f3534f

Browse files
committed
Majour Refactor and README update with details
1 parent c8148fd commit 7f3534f

22 files changed

+322
-46
lines changed

README

Whitespace-only changes.

README.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
## Dynamic Property Loader ##
2+
3+
### Example Annotation Usage ###
4+
<pre>
5+
@ReloadableProperty("dynamicProperty.longValue")
6+
private long primitiveWithDefaultValue = 55;
7+
8+
@ReloadableProperty("dynamicProperty.substitutionValue")
9+
private String stringProperty;
10+
</pre>
11+
12+
### Example Properties File ###
13+
<pre>
14+
dynamicProperty.longValue=12345
15+
dynamicProperty.substitutionProperty=${dynamicProperty.substitutionValue}
16+
</pre>
17+
18+
### Example Spring XML Configuration ###
19+
* See _src/main/resources/spring-reloadableProperties.xml_ for example configuration
20+
* All main components can be extended or replaced if required
21+
22+
### How it Works ###
23+
When the spring Application Context is started an implementation of Springs PropertySourcesPlaceholderConfigurer is instantiated to perform additional logic when loading and setting values from a given set of properties files. (see:ReadablePropertySourcesPlaceholderConfigurer.class)
24+
25+
During the time of ApplicationContext start also a new instance of InstantiationAwareBeanPostProcessorAdapter.class is created that allows post bean processing.
26+
27+
Google Guava is used to implement a simple Publish & Subscribe (Pub-Sub) Pattern so that beans can be updated once created, i.e. a bean can subscribe to events. (see: EventBus)
28+
EventBus was chosen as it is a very easy and simplistic way to implement loosely couple object structure. (see: blog)
29+
30+
When each properties file resource is loaded a FileWatch.class (see:FaileWatch.class) is started and attached to the given resource, reporting on any java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY events from the host operating system
31+
32+
When an ENTRY_MODIFY event is fired firstly the resource changed is checked for property value changes then any bean subscribing to changes to the modified property has the specified field value updated with the new property
33+
34+
### Tests ###
35+
A set of integration and unit tests can be found in _src/test/java_ (tests) & _src/test/resources_ (test resources)
36+
37+
### TODO (Unfinished) ###
38+
* Assign a new thread to each resource directory specified (see: FileWatcherUnitTest.class for failing test @Ignore)
39+
40+
### Why? ###
41+
* Useful for web applications which often need configuration changes but you don't always want to restart the application before new properties are used.
42+
* Can be used to define several layers of properties which can aid in defining multiple application configurations e.g sandbox/development/testing/production.
43+
* A pet project of mine I have been intending to implement/test for a while
44+
* A test of the new Java 7 WatchService API
45+
* Another dive in Spring & general investigation of Google Guava's EventBus, a class which I believe is the extremely useful and easy to use
46+
* The project is aimed to be open to modification if required
47+
* Sample testing tools (CountDownLatch, Hamcrest-1.3, JMock-2.6.0-RC2)
48+
49+
### Future Changes ###
50+
* Ability to use Spring Expression language to map properties files
51+
* Support for Java 7 Data and Time classes
52+
* Add properties source of Database not just properties files
53+
54+
### Supported Property Type Conversions Available ###
55+
* Joda Time Library (2.1) - [link](http://joda-time.sourceforge.net/)
56+
* LocalDate.class
57+
* LocalTime.class
58+
* LocalDateTime.class
59+
* Period.class
60+
61+
62+
* Spring Supported (3.1.2-RELEASE)
63+
* String.class
64+
* boolean.class, Boolean.class
65+
* byte.class, Byte.class
66+
* char.class, Character.class
67+
* short.class, Short.class
68+
* int.class, Integer.class
69+
* long.class, Long.class
70+
* float.class, Float.class
71+
* double.class, Double.class
72+
73+
### Dependencies ###
74+
75+
#### Core ####
76+
* Java 7 SDK
77+
* Spring (3.1.2-RELEASE)
78+
* Google Guava (12.0)
79+
80+
#### Logging ####
81+
* logback (1.0.6)
82+
* slf4j (1.6.4)
83+
84+
#### Testing ####
85+
* juint (4.10)
86+
* jmock (2.6.0-RC2)
87+
* hamcrest-all (1.3)
88+
* spring-test (3.1.2-RELEASE)

pom.xml

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<version>0.0.1-SNAPSHOT</version>
77

88
<properties>
9-
<spring.version>3.1.1.RELEASE</spring.version>
9+
<spring.version>3.1.2.RELEASE</spring.version>
1010
</properties>
1111

1212
<dependencies>
@@ -43,21 +43,11 @@
4343
<artifactId>spring-context</artifactId>
4444
<version>${spring.version}</version>
4545
</dependency>
46-
<dependency>
47-
<groupId>org.springframework</groupId>
48-
<artifactId>spring-expression</artifactId>
49-
<version>${spring.version}</version>
50-
</dependency>
5146
<dependency>
5247
<groupId>org.springframework</groupId>
5348
<artifactId>spring-core</artifactId>
5449
<version>${spring.version}</version>
5550
</dependency>
56-
<dependency>
57-
<groupId>org.springframework</groupId>
58-
<artifactId>spring-beans</artifactId>
59-
<version>${spring.version}</version>
60-
</dependency>
6151

6252
<!-- TEST -->
6353
<dependency>
@@ -93,7 +83,7 @@
9383
<dependency>
9484
<groupId>org.springframework</groupId>
9585
<artifactId>spring-test</artifactId>
96-
<version>3.1.2.RELEASE</version>
86+
<version>${spring.version}</version>
9787
<scope>test</scope>
9888
</dependency>
9989

src/main/java/com/morgan/design/properties/bean/PropertyModifiedEvent.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public class PropertyModifiedEvent {
88
private final Object oldValue;
99
private final Object newValue;
1010

11-
public PropertyModifiedEvent(String propertyName, Object oldValue, Object newValue) {
11+
public PropertyModifiedEvent(final String propertyName, final Object oldValue, final Object newValue) {
1212
this.propertyName = propertyName;
1313
this.oldValue = oldValue;
1414
this.newValue = newValue;
@@ -25,16 +25,15 @@ public Object getOldValue() {
2525
public Object getNewValue() {
2626
return this.newValue;
2727
}
28-
2928
@Override
3029
public int hashCode() {
3130
return Objects.hashCode(this.propertyName, this.oldValue, this.newValue);
3231
}
3332

3433
@Override
35-
public boolean equals(Object object) {
34+
public boolean equals(final Object object) {
3635
if (object instanceof PropertyModifiedEvent) {
37-
PropertyModifiedEvent that = (PropertyModifiedEvent) object;
36+
final PropertyModifiedEvent that = (PropertyModifiedEvent) object;
3837
return Objects.equal(this.propertyName, that.propertyName) && Objects.equal(this.oldValue, that.oldValue)
3938
&& Objects.equal(this.newValue, that.newValue);
4039
}

src/main/java/com/morgan/design/properties/internal/GuavaPropertyChangedEventNotifier.java renamed to src/main/java/com/morgan/design/properties/event/GuavaPropertyChangedEventNotifier.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
package com.morgan.design.properties.internal;
1+
package com.morgan.design.properties.event;
22

33
import org.springframework.beans.factory.annotation.Autowired;
44
import org.springframework.beans.factory.annotation.Qualifier;
55
import org.springframework.stereotype.Component;
66

77
import com.google.common.eventbus.EventBus;
88
import com.morgan.design.properties.bean.PropertyModifiedEvent;
9+
import com.morgan.design.properties.internal.ReloadablePropertyPostProcessor;
910

1011
@Component
1112
public class GuavaPropertyChangedEventNotifier implements PropertyChangedEventNotifier {

src/main/java/com/morgan/design/properties/internal/PropertyChangedEventNotifier.java renamed to src/main/java/com/morgan/design/properties/event/PropertyChangedEventNotifier.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
package com.morgan.design.properties.internal;
1+
package com.morgan.design.properties.event;
22

33
import com.morgan.design.properties.bean.PropertyModifiedEvent;
4+
import com.morgan.design.properties.internal.ReloadablePropertyPostProcessor;
45

56
public interface PropertyChangedEventNotifier {
67

src/main/java/com/morgan/design/properties/internal/FileWatcher.java

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
44

5+
import java.io.IOException;
56
import java.nio.file.FileSystems;
67
import java.nio.file.Path;
78
import java.nio.file.Paths;
@@ -25,28 +26,32 @@ public interface EventPublisher {
2526
private final Resource[] locations;
2627
private final EventPublisher eventPublisher;
2728

28-
public FileWatcher(final Resource[] locations, final EventPublisher eventPublisher) {
29+
private boolean exit;
30+
private WatchService watchService;
31+
32+
public FileWatcher(final Resource[] locations, final EventPublisher eventPublisher) throws IOException {
2933
this.locations = locations;
3034
this.eventPublisher = eventPublisher;
35+
this.exit = false;
36+
this.watchService = FileSystems.getDefault()
37+
.newWatchService();
3138
}
3239

3340
@Override
3441
public void run() {
35-
while (true) {
42+
while (doNotExit()) {
3643
for (final Resource resource : this.locations) {
3744
try {
3845
final Path path = Paths.get(resource.getFile()
3946
.getParentFile()
4047
.toURI());
41-
final WatchService watchService = FileSystems.getDefault()
42-
.newWatchService();
4348

44-
final WatchKey basePathWatchKey = path.register(watchService, ENTRY_MODIFY);
49+
final WatchKey basePathWatchKey = path.register(this.watchService, ENTRY_MODIFY);
4550

4651
final String resourceName = resource.getFilename();
4752

4853
try {
49-
final WatchKey watchKey = watchService.take(); // Await modification
54+
final WatchKey watchKey = this.watchService.take(); // Await modification
5055

5156
for (final WatchEvent<?> event : basePathWatchKey.pollEvents()) {
5257
final Path watchedPath = (Path) watchKey.watchable();
@@ -73,11 +78,31 @@ public void run() {
7378
}
7479
catch (final Exception e) {
7580
log.error("Exception thrown when watching resources, fileName: " + resource.getFilename(), e);
81+
stop();
7682
}
7783
}
7884
}
7985
}
8086

87+
public void stop() {
88+
try {
89+
log.debug("Stopping file watcher");
90+
this.watchService.close();
91+
}
92+
catch (final IOException e) {
93+
log.error("Unable to stop file watcher", e);
94+
}
95+
this.exit = true;
96+
}
97+
98+
private boolean doNotExit() {
99+
return !this.exit;
100+
}
101+
102+
public boolean isRunning() {
103+
return this.exit == false;
104+
}
105+
81106
private boolean isSameTargetFile(final String resourceName, final Path target) {
82107
return target.getFileName()
83108
.toString()

src/main/java/com/morgan/design/properties/internal/ReadablePropertySourcesPlaceholderConfigurer.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
import org.springframework.core.io.support.PropertiesLoaderUtils;
1414

1515
import com.morgan.design.properties.bean.PropertyModifiedEvent;
16+
import com.morgan.design.properties.event.PropertyChangedEventNotifier;
1617
import com.morgan.design.properties.internal.FileWatcher.EventPublisher;
18+
import com.morgan.design.properties.resolver.PropertyResolver;
1719

1820
/**
1921
* Specialisation of {@link PropertySourcesPlaceholderConfigurer} that can react to changes in the resources specified. The watching process does not start by
@@ -81,9 +83,14 @@ public void startWatching() {
8183
if (null == this.eventNotifier) {
8284
throw new BeanInitializationException("Event bus not setup, you should not be calling this method...!");
8385
}
84-
// Here we actually create and set a FileWatcher to monitor the given locations
85-
Executors.newSingleThreadExecutor()
86-
.execute(new FileWatcher(this.locations, this));
86+
try {
87+
// Here we actually create and set a FileWatcher to monitor the given locations
88+
Executors.newSingleThreadExecutor()
89+
.execute(new FileWatcher(this.locations, this));
90+
}
91+
catch (final IOException e) {
92+
log.error("Unable to start properties file watcher", e);
93+
}
8794
}
8895

8996
public Object resolveProperty(final Object property) {

src/main/java/com/morgan/design/properties/internal/ReloadablePropertyPostProcessor.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.morgan.design.properties.bean.BeanPropertyHolder;
2525
import com.morgan.design.properties.bean.PropertyModifiedEvent;
2626
import com.morgan.design.properties.conversion.PropertyConversionService;
27+
import com.morgan.design.properties.event.PropertyChangedEventNotifier;
2728

2829
/**
2930
* <p>
@@ -75,7 +76,7 @@ public final void unregisterPropertyReloader() {
7576
}
7677

7778
/**
78-
* Utility method to registers the class for receiving events about property files being changed, setting up bean re-injection once triggered.
79+
* Utility method to register the class for receiving events about property files being changed, setting up bean re-injection once triggered.
7980
*/
8081
public final void registerPropertyReloader() {
8182
// Setup Guava event bus listener

src/main/java/com/morgan/design/properties/internal/PropertyResolver.java renamed to src/main/java/com/morgan/design/properties/resolver/PropertyResolver.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.morgan.design.properties.internal;
1+
package com.morgan.design.properties.resolver;
22

33
/**
44
* Interface to be apply any special property resolution techniques on the given object, see {@link SubstitutingPropertyResolver} for default implementation

src/main/java/com/morgan/design/properties/internal/SubstitutingPropertyResolver.java renamed to src/main/java/com/morgan/design/properties/resolver/SubstitutingPropertyResolver.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.morgan.design.properties.internal;
1+
package com.morgan.design.properties.resolver;
22

33
import org.slf4j.Logger;
44
import org.slf4j.LoggerFactory;

src/test/resources/spring/spring-defaultConfiguration.xml renamed to src/main/resources/spring/spring-defaultConfiguration.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@
1212
id="conversionService" />
1313

1414
<bean
15-
class="com.morgan.design.properties.internal.GuavaPropertyChangedEventNotifier"
15+
class="com.morgan.design.properties.event.GuavaPropertyChangedEventNotifier"
1616
autowire="constructor" id="eventNotifier">
1717
<constructor-arg ref="propertiesEventBus" />
1818
</bean>
1919

2020
<bean
21-
class="com.morgan.design.properties.internal.SubstitutingPropertyResolver"
21+
class="com.morgan.design.properties.resolver.SubstitutingPropertyResolver"
2222
id="propertyResolver" />
2323

2424
</beans>

src/main/resources/spring/spring-reloadableProperties.xml

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,21 @@ http://www.springframework.org/schema/beans http://www.springframework.org/schem
66
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
77
">
88

9-
<!-- Define event bus for use with GuavaPropertyChangedEventNotifier -->
10-
<bean class="com.google.common.eventbus.EventBus" id="propertiesEventBus" />
9+
<import resource="classpath:/spring/spring-defaultConfiguration.xml" />
1110

12-
<bean class="com.morgan.design.properties.conversion.DefaultPropertyConversionService" id="conversionService" />
13-
14-
<bean class="com.morgan.design.properties.internal.GuavaPropertyChangedEventNotifier" autowire="constructor" id="eventNotifier">
15-
<constructor-arg ref="propertiesEventBus" />
16-
</bean>
17-
18-
<bean class="com.morgan.design.properties.internal.SubstitutingPropertyResolver" id="propertyResolver" />
19-
20-
<bean class="com.morgan.design.properties.internal.ReloadablePropertyPostProcessor">
11+
<bean
12+
class="com.morgan.design.properties.internal.ReloadablePropertyPostProcessor">
2113
<constructor-arg ref="propertyConfigurator" />
2214
<constructor-arg ref="eventNotifier" />
2315
<constructor-arg ref="conversionService" />
2416
</bean>
2517

26-
<bean id="readablePlaceholder" class="com.morgan.design.properties.internal.ReadablePropertySourcesPlaceholderConfigurer">
18+
<bean id="propertyConfigurator"
19+
class="com.morgan.design.properties.internal.ReadablePropertySourcesPlaceholderConfigurer">
2720
<property name="ignoreUnresolvablePlaceholders" value="false" />
2821
<property name="ignoreResourceNotFound" value="true" />
22+
<constructor-arg ref="eventNotifier" />
23+
<constructor-arg ref="propertyResolver" />
2924
<property name="locations">
3025
<list>
3126
<value>classpath*:META-INF/*.properties</value>
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.morgan.design.properties.internal;
1+
package com.morgan.design.properties.event;
22

33
import org.jmock.Expectations;
44
import org.jmock.Mockery;
@@ -11,6 +11,7 @@
1111

1212
import com.google.common.eventbus.EventBus;
1313
import com.morgan.design.properties.bean.PropertyModifiedEvent;
14+
import com.morgan.design.properties.internal.ReloadablePropertyPostProcessor;
1415

1516
@RunWith(JMock.class)
1617
@SuppressWarnings("unqualified-field-access")

0 commit comments

Comments
 (0)