├── .gitignore
├── LICENSE
├── README.md
├── pom.xml
└── src
├── main
├── java
│ └── com
│ │ └── morgan
│ │ └── design
│ │ ├── properties
│ │ ├── ReloadableProperty.java
│ │ ├── bean
│ │ │ ├── BeanPropertyHolder.java
│ │ │ └── PropertyModifiedEvent.java
│ │ ├── conversion
│ │ │ ├── CustomEditorsRegistrar.java
│ │ │ ├── DefaultPropertyConversionService.java
│ │ │ └── PropertyConversionService.java
│ │ ├── event
│ │ │ ├── GuavaPropertyChangedEventNotifier.java
│ │ │ └── PropertyChangedEventNotifier.java
│ │ ├── internal
│ │ │ ├── PropertiesWatcher.java
│ │ │ ├── ReadablePropertySourcesPlaceholderConfigurer.java
│ │ │ └── ReloadablePropertyPostProcessor.java
│ │ └── resolver
│ │ │ ├── PropertyResolver.java
│ │ │ └── SubstitutingPropertyResolver.java
│ │ └── util
│ │ └── JodaUtils.java
└── resources
│ ├── app.properties
│ ├── logback.xml
│ └── spring
│ ├── spring-defaultConfiguration.xml
│ └── spring-reloadableProperties.xml
└── test
├── java
└── com
│ └── morgan
│ └── design
│ ├── properties
│ ├── conversion
│ │ └── DefaultPropertyConversionServiceUnitTest.java
│ ├── event
│ │ └── GuavaPropertyChangedEventNotifierUnitTest.java
│ ├── internal
│ │ ├── FailingReloadablePropertyPostProcessorIntTest.java
│ │ ├── PropertiesWatcherUnitTest.java
│ │ ├── ReloadablePropertyPostProcessorIntTest.java
│ │ └── UpdatingReloadablePropertyPostProcessorIntTest.java
│ ├── resolver
│ │ └── SubstitutingPropertyResolverUnitTest.java
│ └── testBeans
│ │ ├── AutowiredPropertyBean.java
│ │ ├── BadValue.java
│ │ ├── FinalFieldBean.java
│ │ ├── MissingProperty.java
│ │ └── ReloadingAutowiredPropertyBean.java
│ └── util
│ └── JodaUtilsUnitTest.java
└── resources
├── logback-test.xml
├── spring
├── spring-badValue.xml
├── spring-finalFieldBean.xml
├── spring-missingProperty.xml
├── spring-reloadablePropertyPostProcessorIntTest.xml
└── spring-reloading-reloadablePropertyPostProcessorIntTest.xml
├── test-files
├── different_fileWatcher.properties
├── example.properties
├── fileWatcher.properties
└── reloading.properties
└── test-files2
└── fileWatcher2.properties
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 |
3 | # Package Files #
4 | *.jar
5 | *.war
6 | *.ear
7 |
8 | # Eclipse #
9 | .settings
10 | .project
11 | .checkstyle
12 | .springbeans
13 | .classpath
14 | target/*
15 | /target
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) [2014] [James Morgan (Morgan-Design)]
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Reloadable Properties Annotation ##
2 |
3 | A simple utilty which allows object fields to be set from properties files via a @ReloadableProperty annotation.
4 | These properties also auto reload if the given properties file changes during runtime.
5 |
6 | ### Example Annotation Usage ###
7 |
8 | @ReloadableProperty("dynamicProperty.longValue")
9 | private long primitiveWithDefaultValue = 55;
10 |
11 | @ReloadableProperty("dynamicProperty.substitutionValue")
12 | private String stringProperty;
13 |
14 | @ReloadableProperty("dynamicProperty.compoiteStringValue")
15 | private String compsiteStringProperty;
16 |
17 |
18 | ### Example Properties File ###
19 |
20 | dynamicProperty.longValue=12345
21 | dynamicProperty.substitutionProperty=${dynamicProperty.substitutionValue}
22 | dynamicProperty.compoiteStringValue=Hello, ${dynamicProperty.baseStringValue}!
23 |
24 |
25 | ### Example Spring XML Configuration ###
26 | * See [spring-reloadableProperties.xml](https://github.com/jamesemorgan/ReloadablePropertiesAnnotation/blob/master/src/main/resources/spring/spring-reloadableProperties.xml) for example configuration
27 | * All main components can be extended or replaced if required
28 |
29 | ### How it Works ###
30 | When Spring starts an Application Context an implementation of Springs [PropertySourcesPlaceholderConfigurer](http://static.springsource.org/spring/docs/3.1.x/javadoc-api/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.html) is instantiated to perform additional logic when loading and setting values from a given set of properties files. (see: [ReadablePropertySourcesPlaceholderConfigurer](https://github.com/jamesemorgan/ReloadablePropertiesAnnotation/blob/master/src/main/java/com/morgan/design/properties/internal/ReadablePropertySourcesPlaceholderConfigurer.java))
31 |
32 | During the instantiation phasae of an Application Context a new instance of [InstantiationAwareBeanPostProcessorAdapter](http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessorAdapter.html) is also created which allows post bean processing to occur.
33 |
34 | 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 property change events. (see: [EventBus](http://code.google.com/p/guava-libraries/wiki/EventBusExplained))
35 | EventBus was chosen as it is a very easy and simplistic way to implement loosely couple object structure. (see: [blog](http://codingjunkie.net/guava-eventbus/))
36 |
37 | When each properties file resource is loaded a [PropertiesWatcher](https://github.com/jamesemorgan/ReloadablePropertiesAnnotation/blob/master/src/main/java/com/morgan/design/properties/internal/PropertiesWatcher.java) is started and attached to the given resource set, reporting on any [java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY](http://docs.oracle.com/javase/7/docs/api/java/nio/file/StandardWatchEventKinds.html#ENTRY_MODIFY) events from the host operating system
38 |
39 | 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. Once the filed value is updated no other operations are performed on the object.
40 |
41 | Each resource specified starts a new thread per parent directory i.e. two properties files in the same directory requires only one ResourceWatcher thread, three properties files in three different directories will start three threads.
42 |
43 | ### Tests ###
44 | A set of integration and unit tests can be found in _src/test/java_ (tests) & _src/test/resources_ (test resources)
45 |
46 | ### TODO (Unfinished) ###
47 | * Update test method names
48 | * Creation of any test utilities or helper classes
49 |
50 | ### Why? ###
51 | * Useful for web applications which often need configuration changes but you don't always want to restart the application before new properties are used.
52 | * Can be used to define several layers of properties which can aid in defining multiple application configurations e.g sandbox/development/testing/production.
53 | * A pet project of mine I have been intending to implement for a while
54 | * A test of the new Java 7 WatchService API
55 | * Another dive in Spring & general investigation of Google Guava's EventBus
56 | * The project is aimed to be open to modification if required
57 | * Sample testing tools (CountDownLatch, Hamcrest-1.3, JMock-2.6.0-RC2)
58 |
59 | ### Future Changes ###
60 | * Ability to use Spring Expression language to map properties files
61 | * Support for Java 7 Date and Time classes
62 | * Include the ability to define a database driven properties source not just properties files
63 | * Implement error recovery inside PropertiesWatcher.class, including better thread recovery
64 | * Ability to perform additional re-bind logic when a property is changed, i.e. if a class has an open DB connection which needs to be re-established using newly set properties.
65 | * Replace callback Properties EventHandler with Guava EventBus
66 | * Ability to configure usage via spring's @Configuration
67 |
68 | ### Contributions ###
69 | * Thank you [normanatashbar](https://github.com/normanatashbar) for adding composite string replacement
70 | * Thank you [shiva2991](https://github.com/normanatashbar) for adding java.util.Date type conversion.
71 |
72 | ### Supported Property Type Conversions Available ###
73 | * LocalDate.class
74 | * LocalTime.class
75 | * LocalDateTime.class
76 | * Period.class
77 |
78 |
79 | * Spring Supported (3.1.2-RELEASE)
80 | * String.class
81 | * Date.class
82 | * boolean.class, Boolean.class
83 | * byte.class, Byte.class
84 | * char.class, Character.class
85 | * short.class, Short.class
86 | * int.class, Integer.class
87 | * long.class,Long.class
88 | * float.class, Float.class
89 | * double.class, Double.class
90 |
91 | ### Dependencies ###
92 |
93 | #### Core ####
94 | * Java 7 SDK
95 | * Spring (3.2.5-RELEASE)
96 | * Google Guava (14.0.1)
97 | * Joda Time Library (2.1) - [link](http://joda-time.sourceforge.net/)
98 |
99 | #### Logging ####
100 | * logback (1.0.13)
101 | * slf4j (1.7.5)
102 |
103 | #### Testing ####
104 | * juint (4.11)
105 | * jmock (2.6.0)
106 | * hamcrest-all (1.3)
107 | * spring-test (3.2.5-RELEASE)
108 |
109 |
110 | [](https://bitdeli.com/free "Bitdeli Badge")
111 |
112 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | com.morgan.design
5 | ReloadablePropertiesAnnotation
6 | 0.0.2-SNAPSHOT
7 |
8 |
9 | 3.2.5.RELEASE
10 | 1.0.13
11 |
12 |
13 |
14 |
15 |
16 | com.google.guava
17 | guava
18 | 14.0.1
19 |
20 |
21 | joda-time
22 | joda-time
23 | 2.3
24 |
25 |
26 | org.slf4j
27 | slf4j-api
28 | 1.7.5
29 |
30 |
31 | ch.qos.logback
32 | logback-core
33 | ${logback.version}
34 |
35 |
36 | ch.qos.logback
37 | logback-classic
38 | ${logback.version}
39 |
40 |
41 |
42 |
43 | org.springframework
44 | spring-context
45 | ${spring.version}
46 |
47 |
48 | org.springframework
49 | spring-core
50 | ${spring.version}
51 |
52 |
53 |
54 |
55 | junit
56 | junit
57 | test
58 | 4.11
59 |
60 |
61 | org.hamcrest
62 | hamcrest-all
63 | test
64 | 1.3
65 |
66 |
67 | org.jmock
68 | jmock
69 | 2.6.0
70 | test
71 |
72 |
73 | org.jmock
74 | jmock-legacy
75 | 2.6.0
76 | test
77 |
78 |
79 | org.jmock
80 | jmock-junit4
81 | 2.6.0
82 | test
83 |
84 |
85 | org.springframework
86 | spring-test
87 | ${spring.version}
88 | test
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | org.apache.maven.plugins
97 | maven-eclipse-plugin
98 | 2.6
99 |
100 | true
101 | true
102 |
103 |
104 |
105 | org.apache.maven.plugins
106 | maven-clean-plugin
107 | 2.4.1
108 |
109 |
110 | org.apache.maven.plugins
111 | maven-compiler-plugin
112 | 2.3.2
113 |
114 |
115 | compile
116 | compile
117 |
118 | compile
119 |
120 |
121 |
122 | unit-test-compile
123 |
124 | testCompile
125 |
126 |
127 |
128 |
129 | 1.7
130 | 1.7
131 |
132 |
133 |
134 | org.apache.maven.plugins
135 | maven-jar-plugin
136 | 2.4
137 |
138 |
139 | **/logback.xml
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 | central
149 | Maven Plugin Repository
150 | http://repo1.maven.org/maven2
151 |
152 | true
153 |
154 |
155 | true
156 |
157 |
158 |
159 |
160 |
161 |
162 |
--------------------------------------------------------------------------------
/src/main/java/com/morgan/design/properties/ReloadableProperty.java:
--------------------------------------------------------------------------------
1 | package com.morgan.design.properties;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * Marks a field to be set from the given property value, the specified property will reset the field if changed during runtime.
10 | *
11 | * @author James Morgan
12 | */
13 | @Retention(RetentionPolicy.RUNTIME)
14 | @Target(ElementType.FIELD)
15 | public @interface ReloadableProperty {
16 |
17 | String value();
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/morgan/design/properties/bean/BeanPropertyHolder.java:
--------------------------------------------------------------------------------
1 | package com.morgan.design.properties.bean;
2 |
3 | import java.lang.reflect.Field;
4 |
5 | import com.google.common.base.Objects;
6 |
7 | public class BeanPropertyHolder {
8 |
9 | private final Object bean;
10 | private final Field field;
11 |
12 | public BeanPropertyHolder(Object bean, Field field) {
13 | this.bean = bean;
14 | this.field = field;
15 | }
16 |
17 | public Object getBean() {
18 | return this.bean;
19 | }
20 |
21 | public Field getField() {
22 | return this.field;
23 | }
24 |
25 | @Override
26 | public int hashCode() {
27 | return Objects.hashCode(this.bean, this.field);
28 | }
29 |
30 | @Override
31 | public boolean equals(Object object) {
32 | if (object instanceof BeanPropertyHolder) {
33 | BeanPropertyHolder that = (BeanPropertyHolder) object;
34 | return Objects.equal(this.bean, that.bean) && Objects.equal(this.field, that.field);
35 | }
36 | return false;
37 | }
38 |
39 | @Override
40 | public String toString() {
41 | return Objects.toStringHelper(this)
42 | .add("bean", this.bean)
43 | .add("field", this.field)
44 | .toString();
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/com/morgan/design/properties/bean/PropertyModifiedEvent.java:
--------------------------------------------------------------------------------
1 | package com.morgan.design.properties.bean;
2 |
3 | import com.google.common.base.Objects;
4 |
5 | public class PropertyModifiedEvent {
6 |
7 | private final String propertyName;
8 | private final Object oldValue;
9 | private final Object newValue;
10 |
11 | public PropertyModifiedEvent(final String propertyName, final Object oldValue, final Object newValue) {
12 | this.propertyName = propertyName;
13 | this.oldValue = oldValue;
14 | this.newValue = newValue;
15 | }
16 |
17 | public String getPropertyName() {
18 | return this.propertyName;
19 | }
20 |
21 | public Object getOldValue() {
22 | return this.oldValue;
23 | }
24 |
25 | public Object getNewValue() {
26 | return this.newValue;
27 | }
28 | @Override
29 | public int hashCode() {
30 | return Objects.hashCode(this.propertyName, this.oldValue, this.newValue);
31 | }
32 |
33 | @Override
34 | public boolean equals(final Object object) {
35 | if (object instanceof PropertyModifiedEvent) {
36 | final PropertyModifiedEvent that = (PropertyModifiedEvent) object;
37 | return Objects.equal(this.propertyName, that.propertyName) && Objects.equal(this.oldValue, that.oldValue)
38 | && Objects.equal(this.newValue, that.newValue);
39 | }
40 | return false;
41 | }
42 |
43 | @Override
44 | public String toString() {
45 | return Objects.toStringHelper(this)
46 | .add("propertyName", this.propertyName)
47 | .add("oldValue", this.oldValue)
48 | .add("newValue", this.newValue)
49 | .toString();
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/com/morgan/design/properties/conversion/CustomEditorsRegistrar.java:
--------------------------------------------------------------------------------
1 | package com.morgan.design.properties.conversion;
2 |
3 | import java.beans.PropertyEditor;
4 | import java.util.Map;
5 |
6 | import org.springframework.beans.PropertyEditorRegistrar;
7 | import org.springframework.beans.PropertyEditorRegistry;
8 |
9 | public class CustomEditorsRegistrar implements PropertyEditorRegistrar {
10 |
11 | private Map, PropertyEditor> extraEditors;
12 |
13 | public CustomEditorsRegistrar(Map, PropertyEditor> extraEditors)
14 | {
15 | this.extraEditors = extraEditors;
16 | }
17 |
18 | @Override
19 | public void registerCustomEditors(PropertyEditorRegistry registry) {
20 | for (Map.Entry, PropertyEditor> entry : extraEditors.entrySet()) {
21 | registry.registerCustomEditor(entry.getKey(), entry.getValue());
22 | }
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/morgan/design/properties/conversion/DefaultPropertyConversionService.java:
--------------------------------------------------------------------------------
1 | package com.morgan.design.properties.conversion;
2 |
3 | import java.lang.reflect.Field;
4 | import java.util.Map;
5 |
6 | import javax.annotation.PostConstruct;
7 |
8 | import org.joda.time.LocalDate;
9 | import org.joda.time.LocalDateTime;
10 | import org.joda.time.LocalTime;
11 | import org.joda.time.Period;
12 | import org.springframework.beans.SimpleTypeConverter;
13 | import org.springframework.beans.TypeConverter;
14 | import org.springframework.beans.factory.BeanInitializationException;
15 | import org.springframework.beans.factory.annotation.Autowired;
16 | import org.springframework.beans.factory.config.ConfigurableBeanFactory;
17 | import org.springframework.stereotype.Component;
18 |
19 | import com.google.common.base.Function;
20 | import com.google.common.base.Functions;
21 | import com.google.common.collect.Maps;
22 | import com.morgan.design.util.JodaUtils;
23 |
24 | /**
25 | * Default implementation of {@link PropertyConversionService}, attempting to convert an object otherwise utilising {@link SimpleTypeConverter} if no matching
26 | * converter is found.
27 | *
28 | * @author James Morgan
29 | */
30 | @Component
31 | public class DefaultPropertyConversionService implements PropertyConversionService {
32 |
33 | @Autowired
34 | private ConfigurableBeanFactory configurableBeanFactory;
35 | private static TypeConverter DEFAULT;
36 | private static Map, Function