Skip to content

Commit 0383f11

Browse files
committed
first thoughts on type-based configuration; added annotation, accessor method, and spec; relates to #12;
1 parent 4a2dd1f commit 0383f11

File tree

4 files changed

+642
-0
lines changed

4 files changed

+642
-0
lines changed
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
/*
2+
* Copyright (c) 2016-2018 Contributors to the Eclipse Foundation
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* You may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
package javax.config;
21+
22+
import javax.config.inject.ConfigProperty;
23+
import javax.enterprise.util.Nonbinding;
24+
import javax.inject.Qualifier;
25+
import java.lang.annotation.Retention;
26+
import java.lang.annotation.Target;
27+
import java.util.concurrent.TimeUnit;
28+
29+
import static java.lang.annotation.ElementType.*;
30+
import static java.lang.annotation.RetentionPolicy.RUNTIME;
31+
32+
/**
33+
* Specifies a configuration type as composed configuration.
34+
* Composed configuration types comprise multiple, potentially hierarchical, configuration values.
35+
*
36+
* <h2>Examples</h2>
37+
*
38+
* <h3>Composed, coherent configuration</h3>
39+
* <p>
40+
* The following example defines a coherent server socket configuration.
41+
*
42+
* <pre>
43+
* &#064;ComposedConfig
44+
* public class SocketConfig {
45+
*
46+
* &#064;ConfigProperty(name = "name")
47+
* private String name;
48+
*
49+
* &#064;ConfigProperty(name = "protocol", defaultValue = "http")
50+
* private String protocolName;
51+
*
52+
* &#064;ConfigProperty(name = "port")
53+
* private int port;
54+
*
55+
* // getters & setters
56+
* }
57+
* </pre>
58+
* <p>
59+
* The {@code SocketConfig} configuration can be retrieved like any other configured value, by programmatic lookup, or dependency injection:
60+
*
61+
* <pre>
62+
* public class SomeBean {
63+
*
64+
* &#064;Inject
65+
* &#064;ConfigProperty(name = "server.socket")
66+
* private SocketConfig socketConfig;
67+
*
68+
* }
69+
* </pre>
70+
* <p>
71+
* The example will resolve the configuration values as follows, provided by the corresponding property keys:
72+
*
73+
* <pre>
74+
* server.socket.name
75+
* server.socket.protocol
76+
* server.socket.port
77+
* </pre>
78+
*
79+
* <h3>Implicit property resolution</h3>
80+
* <p>
81+
* It's possible to omit the individual {@link ConfigProperty} annotations on the fields of the composed type.
82+
* In this case the composed property keys are derived from the field names:
83+
* <p>
84+
*
85+
* <pre>
86+
* public class SomeBean {
87+
*
88+
* &#064;Inject
89+
* &#064;ConfigProperty(name = "server.socket")
90+
* private SocketConfig socketConfig;
91+
*
92+
* &#064;ComposedConfig
93+
* public static class SocketConfig {
94+
*
95+
* private String name;
96+
* private String protocol;
97+
* private int port;
98+
*
99+
* // getters & setters
100+
* }
101+
* }
102+
* </pre>
103+
*
104+
* <p>
105+
* This example will result in the same configuration resolution as in the previous example, apart from the default value for {@code server.socket.protocol}.
106+
* <p>
107+
* If the property keys differ from the field names, they can be overridden individually via the {@link ConfigProperty} annotation.
108+
* The same is true for default values, as seen before.
109+
* <p>
110+
* The {@link ConfigProperty} annotation can be annotated on fields as well as on methods.
111+
* The latter is useful if interfaces instead of classes are defined as composed types.
112+
* Per default, methods are not implicitly taken to resolve composed configuration properties.
113+
* If both fields and methods are annotated within a single type, methods take precedence.
114+
* <p>
115+
* See the following example for a composed interface configuration type.
116+
* <p>
117+
*
118+
* <pre>
119+
* &#064;ComposedConfig
120+
* public interface SocketConfig {
121+
*
122+
* &#064;ConfigProperty(name = "name")
123+
* String name();
124+
*
125+
* &#064;ConfigProperty(name = "protocol", defaultValue = "http")
126+
* String protocolName();
127+
*
128+
* &#064;ConfigProperty(name = "port")
129+
* int getPort();
130+
* }
131+
* </pre>
132+
* <p>
133+
* This example will result in the same configuration as before.
134+
*
135+
* <h3>Hierarchical type resolution</h3>
136+
* <p>
137+
* The configuration properties of composed types are resolved hierarchically.
138+
* That is, properties in composed types are implicitly considered as possible composed types themselves, as well.
139+
* This allows developers to define complex configuration structures without repeating annotations.
140+
* <p>
141+
* The types of composed properties are therefore resolved as possible composed configuration types, if no built-in, custom, or implicit converters are defined.
142+
* <p>
143+
* The hierarchical resolution works both for implicitly resolved fields and explicitly annotated members.
144+
*
145+
* <pre>
146+
* &#064;ComposedConfig
147+
* public class ServerConfig {
148+
*
149+
* private String host;
150+
* private SocketConfig socket;
151+
*
152+
* // getters & setters
153+
*
154+
* public static class SocketConfig {
155+
* private String name;
156+
* private String protocol;
157+
* private int port;
158+
*
159+
* // getters & setters
160+
* }
161+
*
162+
* }
163+
* </pre>
164+
* <p>
165+
* If a {@code ServerConfig} configuration type is retrieved, the property keys are resolved as follows:
166+
*
167+
* <pre>
168+
* public class SomeBean {
169+
*
170+
* &#064;Inject
171+
* &#064;ConfigProperty(name = "server")
172+
* private ServerConfig serverConfig;
173+
*
174+
* }
175+
* </pre>
176+
* <p>
177+
* This leads to:
178+
*
179+
* <pre>
180+
* server.host
181+
* server.socket.name
182+
* server.socket.protocol
183+
* server.socket.port
184+
* </pre>
185+
* <p>
186+
* The property keys are resolved by the field names, or the names defined in {@link ConfigProperty}, respectively, and combined via dot ({@code .}).
187+
* <p>
188+
* The example above is congruent with annotating {@code SocketConfig} with {@link ComposedConfig}, as well.
189+
*
190+
* <h3>Collection resolution</h3>
191+
* <p>
192+
* Composed configuration types also resolve collections and array types.
193+
*
194+
* <pre>
195+
* &#064;ComposedConfig
196+
* public class MultiSocketServerConfig {
197+
*
198+
* private String[] hosts;
199+
* private List<SocketConfig> sockets;
200+
*
201+
* // getters & setters
202+
*
203+
* public static class SocketConfig {
204+
* private String name;
205+
* private String protocol;
206+
* private int port;
207+
*
208+
* // getters & setters
209+
* }
210+
*
211+
* }
212+
* </pre>
213+
* <p>
214+
* If the {@code MultiSocketServerConfig} type is resolved by key {@code alternative-server}, it results in the following:
215+
*
216+
* <pre>
217+
* server.hosts.0
218+
* server.hosts.1
219+
*
220+
* server.sockets.0.name
221+
* server.sockets.0.protocol
222+
* server.sockets.1.name
223+
* </pre>
224+
* <p>
225+
* Element types of collections and arrays are resolved by an implicit zero-based index, which is part of the resulting, combined property key.
226+
* <p>
227+
* This collection resolution works for array types, and types that are assignable to {@link java.util.Collection}.
228+
* For unordered collection types, e.g. {@link java.util.Set}, the order in which the configured elements will be retrieved is non-deterministic, despite the (zero-based) indexed key names.
229+
* <p>
230+
* Similar to singular sub-types, the element types within the collection or array are resolved by potentially existent converters, and resolved recursively if no built-in, custom, or implicit converters are defined.
231+
*
232+
* @author <a href="mailto:[email protected]">Sebastian Daschner</a>
233+
*/
234+
@Qualifier
235+
@Retention(RUNTIME)
236+
@Target({METHOD, FIELD, PARAMETER, TYPE})
237+
public @interface ComposedConfig {
238+
239+
/**
240+
* Only valid for injection of dynamically readable values, e.g. {@code Provider<String>}!
241+
*
242+
* @return {@code TimeUnit} for {@link #cacheFor()}
243+
*/
244+
@Nonbinding
245+
TimeUnit cacheTimeUnit() default TimeUnit.SECONDS;
246+
247+
/**
248+
* Only valid for injection of dynamically readable values, e.g. {@code Provider<String>}!
249+
*
250+
* @return how long should dynamic values be locally cached. Measured in {@link #cacheTimeUnit()}.
251+
*/
252+
@Nonbinding
253+
long cacheFor() default 0L;
254+
}

api/src/main/java/javax/config/ConfigAccessor.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,17 @@ public interface ConfigAccessor<T> {
9696
*/
9797
ConfigAccessor<T> useConverter(Converter<T> converter);
9898

99+
/**
100+
* Considers the type of the configuration entry a composed type.
101+
* This method enables developers to define the look-up of composed type in scenarios where they cannot annotate
102+
* the composed type with {@link ComposedConfig}. This is the programmatic equivalent of injecting a composed type
103+
* with qualifier {@link ComposedConfig} at the injection point.
104+
*
105+
* @return This builder
106+
* @see ComposedConfig
107+
*/
108+
ConfigAccessor<T> asComposed();
109+
99110
/**
100111
* Sets the default value to use in case the resolution returns null.
101112
* @param value the default value

0 commit comments

Comments
 (0)