Skip to content

Commit 895cc7b

Browse files
authored
Merge pull request #237 from adrienlauer/full-config-dump
Configuration dump now displays inner properties
2 parents 6bb27c5 + 5d13e87 commit 895cc7b

File tree

12 files changed

+163
-50
lines changed

12 files changed

+163
-50
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# Version 3.3.1 (2017-09-15)
2+
3+
* [new] Configuration dump (config tool) now dumps inner properties for maps, collections, arrays and complex objects.
4+
* [new] Add `isRemembered()` on `SecuritySupport`.
5+
16
# Version 3.3.0 (2017-07-31)
27

38
* [new] Print a default banner at startup in case of missing custom `banner.txt`.

core/src/main/java/org/seedstack/seed/core/internal/configuration/tool/ConfigTool.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010
import io.nuun.kernel.api.plugin.InitState;
1111
import io.nuun.kernel.api.plugin.context.InitContext;
1212
import io.nuun.kernel.api.plugin.request.ClasspathScanRequest;
13+
import org.seedstack.coffig.Coffig;
1314
import org.seedstack.coffig.Config;
1415
import org.seedstack.seed.SeedException;
1516
import org.seedstack.seed.cli.CliArgs;
17+
import org.seedstack.seed.core.SeedRuntime;
1618
import org.seedstack.seed.core.internal.AbstractSeedTool;
1719
import org.seedstack.seed.core.internal.CoreErrorCode;
1820

@@ -26,12 +28,18 @@ public class ConfigTool extends AbstractSeedTool {
2628
private final Node root = new Node();
2729
@CliArgs
2830
private String[] args;
31+
private Coffig configuration;
2932

3033
@Override
3134
public String toolName() {
3235
return "config";
3336
}
3437

38+
@Override
39+
protected void setup(SeedRuntime seedRuntime) {
40+
configuration = seedRuntime.getConfiguration();
41+
}
42+
3543
@Override
3644
public Collection<ClasspathScanRequest> classpathScanRequests() {
3745
return classpathScanRequestBuilder()
@@ -42,7 +50,7 @@ public Collection<ClasspathScanRequest> classpathScanRequests() {
4250
@Override
4351
protected InitState initialize(InitContext initContext) {
4452
List<Node> nodes = new ArrayList<>();
45-
initContext.scannedClassesByAnnotationClass().get(Config.class).stream().map(Node::new).forEach(nodes::add);
53+
initContext.scannedClassesByAnnotationClass().get(Config.class).stream().map(configClass -> new Node(configClass, configuration)).forEach(nodes::add);
4654
Collections.sort(nodes);
4755
nodes.forEach(this::buildTree);
4856
return InitState.INITIALIZED;
@@ -67,7 +75,7 @@ public Integer call() throws Exception {
6775
private void info(String[] path) {
6876
Node node = root.find(Arrays.copyOfRange(path, 0, path.length - 1));
6977
if (node == null) {
70-
throw SeedException.createNew(CoreErrorCode.INVALID_CONFIG_PATH).put("path", path);
78+
throw SeedException.createNew(CoreErrorCode.INVALID_CONFIG_PATH).put("path", String.join(".", path));
7179
} else {
7280
PropertyInfo propertyInfo = node.getPropertyInfo(path[path.length - 1]);
7381
if (propertyInfo == null) {

core/src/main/java/org/seedstack/seed/core/internal/configuration/tool/Node.java

Lines changed: 77 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,19 @@
77
*/
88
package org.seedstack.seed.core.internal.configuration.tool;
99

10+
import org.seedstack.coffig.Coffig;
1011
import org.seedstack.coffig.Config;
1112
import org.seedstack.coffig.SingleValue;
1213
import org.seedstack.shed.reflect.Annotations;
14+
import org.seedstack.shed.reflect.ReflectUtils;
15+
import org.seedstack.shed.reflect.Types;
1316

17+
import javax.annotation.Nullable;
1418
import javax.validation.constraints.NotNull;
1519
import java.lang.reflect.Field;
1620
import java.lang.reflect.Modifier;
21+
import java.lang.reflect.ParameterizedType;
22+
import java.lang.reflect.Type;
1723
import java.util.ArrayList;
1824
import java.util.Arrays;
1925
import java.util.Collection;
@@ -24,37 +30,40 @@
2430
import java.util.Map;
2531
import java.util.MissingResourceException;
2632
import java.util.ResourceBundle;
27-
import java.util.SortedMap;
2833
import java.util.TreeMap;
2934

3035
import static org.seedstack.shed.reflect.Classes.instantiateDefault;
3136
import static org.seedstack.shed.reflect.ReflectUtils.makeAccessible;
3237
import static org.seedstack.shed.reflect.Types.simpleNameOf;
3338

3439
class Node implements Comparable<Node> {
35-
private final String name;
3640
private final Class<?> configClass;
41+
private final Coffig coffig;
42+
private final String name;
3743
private final Class<?> outermostClass;
3844
private final int outermostLevel;
3945
private final String[] path;
46+
private final ResourceBundle bundle;
4047
private final Map<String, PropertyInfo> propertyInfo;
41-
private final SortedMap<String, Node> children = new TreeMap<>();
48+
private final Map<String, Node> children = new TreeMap<>();
4249

4350
Node() {
44-
this.name = "";
4551
this.configClass = null;
52+
this.coffig = null;
53+
this.name = "";
4654
this.outermostClass = null;
4755
this.outermostLevel = 0;
4856
this.path = new String[0];
57+
this.bundle = null;
4958
this.propertyInfo = new HashMap<>();
5059
}
5160

52-
Node(Class<?> configClass) {
61+
Node(Class<?> configClass, Coffig coffig) {
5362
this.configClass = configClass;
63+
this.coffig = coffig;
5464

5565
List<String> path = new ArrayList<>();
5666
Class<?> previousClass = configClass;
57-
int nestingLevel = -1;
5867
do {
5968
Config annotation = configClass.getAnnotation(Config.class);
6069
if (annotation == null) {
@@ -64,15 +73,15 @@ class Node implements Comparable<Node> {
6473
Collections.reverse(splitPath);
6574
path.addAll(splitPath);
6675
previousClass = configClass;
67-
nestingLevel++;
6876
} while ((configClass = configClass.getDeclaringClass()) != null);
6977

7078
Collections.reverse(path);
7179
this.outermostClass = previousClass;
7280
this.outermostLevel = this.outermostClass.getAnnotation(Config.class).value().split("\\.").length;
7381
this.path = path.toArray(new String[path.size()]);
7482
this.name = this.path[this.path.length - 1];
75-
this.propertyInfo = buildPropertyInfo();
83+
this.bundle = getResourceBundle();
84+
this.propertyInfo = buildPropertyInfo(this.configClass, "", null);
7685
}
7786

7887
String getName() {
@@ -149,68 +158,86 @@ public int compareTo(Node that) {
149158
return 0;
150159
}
151160

152-
private Map<String, PropertyInfo> buildPropertyInfo() {
161+
private Map<String, PropertyInfo> buildPropertyInfo(Class<?> configClass, String parentPropertyName, Object defaultInstance) {
153162
Map<String, PropertyInfo> result = new LinkedHashMap<>();
154163

155-
ResourceBundle bundle = null;
156-
try {
157-
bundle = ResourceBundle.getBundle(outermostClass.getName());
158-
} catch (MissingResourceException e) {
159-
// ignore
160-
}
161-
162-
Object defaultInstance;
163-
try {
164-
defaultInstance = instantiateDefault(configClass);
165-
} catch (Exception e) {
166-
defaultInstance = null;
164+
if (defaultInstance == null) {
165+
try {
166+
defaultInstance = instantiateDefault(configClass);
167+
} catch (Exception e) {
168+
defaultInstance = null;
169+
}
167170
}
168171

169172
for (Field field : configClass.getDeclaredFields()) {
170173
if (Modifier.isStatic(field.getModifiers())) {
174+
// Skip static fields (not used for configuration)
171175
continue;
172176
}
173177
if (field.getType().isAnnotationPresent(Config.class)) {
178+
// Skip fields of type annotated with @Config as they are already detected
174179
continue;
175180
}
176181

177182
makeAccessible(field);
178183

179-
PropertyInfo propertyInfo = new PropertyInfo();
180184
Config configAnnotation = field.getAnnotation(Config.class);
185+
Type genericType = field.getGenericType();
181186
String name;
182187
if (configAnnotation != null) {
183188
name = configAnnotation.value();
184189
} else {
185190
name = field.getName();
186191
}
187192

193+
PropertyInfo propertyInfo = new PropertyInfo();
188194
propertyInfo.setName(name);
189-
propertyInfo.setShortDescription(getMessage(bundle, "No description.", buildKey(name)));
190-
propertyInfo.setLongDescription(getMessage(bundle, null, buildKey(name, "long")));
191-
propertyInfo.setType(simpleNameOf(field.getGenericType()));
195+
propertyInfo.setShortDescription(getMessage("", buildKey(parentPropertyName, name)));
196+
propertyInfo.setLongDescription(getMessage(null, buildKey(parentPropertyName, name, "long")));
197+
propertyInfo.setType(simpleNameOf(genericType));
192198
propertyInfo.setSingleValue(field.isAnnotationPresent(SingleValue.class));
199+
propertyInfo.setMandatory(isNotNull(field));
193200
if (defaultInstance != null) {
194-
try {
195-
propertyInfo.setDefaultValue(field.get(defaultInstance));
196-
} catch (IllegalAccessException e) {
197-
// ignore
198-
}
201+
propertyInfo.setDefaultValue(ReflectUtils.getValue(field, defaultInstance));
202+
}
203+
204+
Class<?> rawClass = Types.rawClassOf(genericType);
205+
Type itemType;
206+
if (Collection.class.isAssignableFrom(rawClass) && genericType instanceof ParameterizedType) {
207+
itemType = Types.rawClassOf(((ParameterizedType) genericType).getActualTypeArguments()[0]);
208+
} else if (Map.class.isAssignableFrom(rawClass) && genericType instanceof ParameterizedType) {
209+
itemType = Types.rawClassOf(((ParameterizedType) genericType).getActualTypeArguments()[1]);
210+
} else if (genericType instanceof Class<?> && ((Class<?>) genericType).isArray()) {
211+
itemType = ((Class<?>) genericType).getComponentType();
212+
} else {
213+
itemType = genericType;
214+
}
215+
if (!coffig.getMapper().canHandle(itemType)) {
216+
propertyInfo.addInnerPropertyInfo(
217+
buildPropertyInfo(
218+
Types.rawClassOf(itemType),
219+
parentPropertyName.isEmpty() ? name : parentPropertyName + "." + name,
220+
itemType.equals(genericType) ? ReflectUtils.getValue(field, defaultInstance) : null
221+
)
222+
);
199223
}
200-
propertyInfo.setMandatory(propertyInfo.getDefaultValue() == null && Annotations.on(field).includingMetaAnnotations().find(NotNull.class).isPresent());
201224

202225
result.put(name, propertyInfo);
203226
}
204227

205228
return result;
206229
}
207230

208-
private String getMessage(ResourceBundle resourceBundle, String defaultMessage, String key) {
209-
if (resourceBundle == null) {
231+
private boolean isNotNull(Field field) {
232+
return Annotations.on(field).includingMetaAnnotations().find(NotNull.class).isPresent();
233+
}
234+
235+
private String getMessage(String defaultMessage, String key) {
236+
if (bundle == null) {
210237
return defaultMessage;
211238
}
212239
try {
213-
return resourceBundle.getString(key);
240+
return bundle.getString(key);
214241
} catch (MissingResourceException e) {
215242
return defaultMessage;
216243
}
@@ -227,11 +254,24 @@ private String buildKey(String... parts) {
227254
}
228255
}
229256
for (int i = 0; i < parts.length; i++) {
230-
sb.append(parts[i]);
231-
if (i < parts.length - 1) {
232-
sb.append(".");
257+
if (!parts[i].isEmpty()) {
258+
sb.append(parts[i]);
259+
if (i < parts.length - 1) {
260+
sb.append(".");
261+
}
233262
}
234263
}
235264
return sb.toString();
236265
}
266+
267+
@Nullable
268+
private ResourceBundle getResourceBundle() {
269+
ResourceBundle bundle;
270+
try {
271+
bundle = ResourceBundle.getBundle(outermostClass.getName());
272+
} catch (MissingResourceException e) {
273+
bundle = null;
274+
}
275+
return bundle;
276+
}
237277
}

core/src/main/java/org/seedstack/seed/core/internal/configuration/tool/PropertyInfo.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
*/
88
package org.seedstack.seed.core.internal.configuration.tool;
99

10+
import java.util.Collections;
11+
import java.util.Map;
12+
import java.util.TreeMap;
13+
1014
class PropertyInfo {
1115
private String name;
1216
private String type;
@@ -15,6 +19,7 @@ class PropertyInfo {
1519
private boolean singleValue;
1620
private boolean mandatory;
1721
private Object defaultValue;
22+
private Map<String, PropertyInfo> innerPropertyInfo = new TreeMap<>();
1823

1924
String getName() {
2025
return name;
@@ -71,4 +76,12 @@ Object getDefaultValue() {
7176
void setDefaultValue(Object defaultValue) {
7277
this.defaultValue = defaultValue;
7378
}
79+
80+
Map<String, PropertyInfo> getInnerPropertyInfo() {
81+
return Collections.unmodifiableMap(innerPropertyInfo);
82+
}
83+
84+
void addInnerPropertyInfo(Map<String, PropertyInfo> innerPropertyInfo) {
85+
this.innerPropertyInfo.putAll(innerPropertyInfo);
86+
}
7487
}

core/src/main/java/org/seedstack/seed/core/internal/configuration/tool/TreePrinter.java

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package org.seedstack.seed.core.internal.configuration.tool;
99

1010
import org.fusesource.jansi.Ansi;
11+
import org.fusesource.jansi.AnsiRenderer;
1112

1213
import java.io.PrintStream;
1314

@@ -65,13 +66,36 @@ private void printProperty(PropertyInfo propertyInfo, String leftPadding, Ansi a
6566
.a(propertyInfo.isSingleValue() ? "~" : "")
6667
.a(propertyInfo.isMandatory() ? "*" : "")
6768
.a(propertyInfo.getName())
68-
.reset()
69-
.a(": ")
69+
.reset();
70+
71+
Object defaultValue = propertyInfo.getDefaultValue();
72+
if (defaultValue != null) {
73+
String stringDefaultValue = String.valueOf(defaultValue);
74+
if (!defaultToString(defaultValue).equals(stringDefaultValue)) {
75+
ansi
76+
.a(" = ")
77+
.fgBright(Ansi.Color.GREEN)
78+
.a(defaultValue instanceof String ? String.format("\"%s\"", stringDefaultValue) : stringDefaultValue)
79+
.reset();
80+
}
81+
}
82+
83+
ansi
7084
.fgBright(Ansi.Color.MAGENTA)
85+
.a(" (")
7186
.a(propertyInfo.getType())
87+
.a(")")
7288
.reset()
73-
.a(". ")
74-
.a(propertyInfo.getShortDescription())
89+
.a(": ")
90+
.a(AnsiRenderer.render(propertyInfo.getShortDescription()))
7591
.newline();
92+
93+
for (PropertyInfo child : propertyInfo.getInnerPropertyInfo().values()) {
94+
printProperty(child, leftPadding + INDENTATION, ansi);
95+
}
96+
}
97+
98+
private static String defaultToString(Object o) {
99+
return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode());
76100
}
77101
}

security/specs/src/main/resources/org/seedstack/seed/security/SecurityConfig.properties

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,20 @@
77
#
88

99
realms=The security realms used to authenticate and authorize users.
10+
realms.name=Name of the security realm.
11+
realms.permissionResolver=Name of the permission resolver used for this realm (optional).
12+
realms.roleMapper=Name of the role mapper used for this realm (optional).
1013
users=Users valid in the application (key: user id, value: user config).
14+
users.password=The password of the user.
15+
users.roles=Set of roles granted to the user.
1116
roles=Application roles (key: app role name, value: corresponding realm role(s)).
1217
permissions=Application permissions (key: app role name, value: permission(s) granted for this role).
1318
cache.authorization=Configuration of the authorization cache.
19+
cache.authorization.enabled=If true, authorization information is cached by the security subsystem.
20+
cache.authorization.name=The name of the authorization cache if any.
1421
cache.authentication=Configuration of the authorization cache.
22+
cache.authentication.enabled=If true, authentication information is cached by the security subsystem.
23+
cache.authentication.name=The name of the authentication cache if any.
1524
cache.enabled=If true, security caching is enabled, otherwise realms are queried for each security operation.
1625
sessions.enabled=If true, security sessions are enabled, otherwise security is session-less.
1726
sessions.timeout=Session inactivity timeout, in seconds.

specs/src/main/java/org/seedstack/seed/transaction/TransactionConfig.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@ public JtaConfig jta() {
4343

4444
@Config("jta")
4545
public static class JtaConfig {
46-
private static final String DEFAULT_USER_TRANSACTION_NAME = "java:comp/UserTransaction";
47-
46+
private static final String DEFAULT_USER_TRANSACTION_NAME = "java:comp/UserTransaction";
4847
@SingleValue
4948
private String txManagerName;
5049
private String userTxName = DEFAULT_USER_TRANSACTION_NAME;

0 commit comments

Comments
 (0)