Skip to content

Commit 6375428

Browse files
committed
Helidon Features.
Signed-off-by: Tomas Langer <tomas.langer@oracle.com>
1 parent a4051f9 commit 6375428

57 files changed

Lines changed: 541 additions & 68 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

common/common/src/main/java/io/helidon/common/HelidonFeatures.java

Lines changed: 111 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
package io.helidon.common;
1818

1919
import java.util.EnumMap;
20-
import java.util.HashSet;
20+
import java.util.HashMap;
2121
import java.util.Map;
2222
import java.util.Set;
23+
import java.util.TreeMap;
24+
import java.util.TreeSet;
2325
import java.util.concurrent.atomic.AtomicBoolean;
2426
import java.util.concurrent.atomic.AtomicReference;
2527
import java.util.logging.Logger;
@@ -29,10 +31,10 @@
2931
*/
3032
public final class HelidonFeatures {
3133
private static final Logger LOGGER = Logger.getLogger(HelidonFeatures.class.getName());
32-
33-
private static final Map<HelidonFlavor, Set<String>> FEATURES = new EnumMap<>(HelidonFlavor.class);
34-
private static final AtomicReference<HelidonFlavor> CURRENT_FLAVOR = new AtomicReference<>();
3534
private static final AtomicBoolean PRINTED = new AtomicBoolean();
35+
private static final AtomicReference<HelidonFlavor> CURRENT_FLAVOR = new AtomicReference<>();
36+
private static final Map<HelidonFlavor, Set<String>> FEATURES = new EnumMap<>(HelidonFlavor.class);
37+
private static final Map<HelidonFlavor, Map<String, Node>> ROOT_FEATURE_NODES = new EnumMap<>(HelidonFlavor.class);
3638

3739
private HelidonFeatures() {
3840
}
@@ -43,12 +45,70 @@ private HelidonFeatures() {
4345
* class. In SE this would be one of the *Support classes or similar,
4446
* in MP most likely a CDI extension class.
4547
*
48+
* <p>Example for security providers (SE) - application with Oidc provider:
49+
* <ul>
50+
* <li>Security calls {@code register(SE, "security")}</li>
51+
* <li>OIDC provider calls {@code register(SE, "security", "authentication", "OIDC"}</li>
52+
* <li>OIDC provider calls {@code register(SE, "security", "outbound", "OIDC"} if outbound is enabled</li>
53+
* </ul>
54+
*
4655
* @param flavor flavor to register a feature for
47-
* @param name name of the feature
56+
* @param path path of the feature (single value for root level features)
4857
*/
49-
public static void register(HelidonFlavor flavor, String name) {
50-
FEATURES.computeIfAbsent(flavor, key -> new HashSet<>())
51-
.add(name);
58+
public static void register(HelidonFlavor flavor, String... path) {
59+
if (path.length == 0) {
60+
throw new IllegalArgumentException("At least the root feature name must be provided, but path was empty");
61+
}
62+
if (path.length == 1) {
63+
FEATURES.computeIfAbsent(flavor, key -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER))
64+
.add(path[0]);
65+
}
66+
67+
var rootFeatures = ROOT_FEATURE_NODES.computeIfAbsent(flavor, it -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER));
68+
ensureNode(rootFeatures, path);
69+
}
70+
71+
/**
72+
* Register a feature for all flavors.
73+
*
74+
* <p>Example for security providers (SE) - application with Oidc provider:
75+
* <ul>
76+
* <li>Security calls {@code register(SE, "security")}</li>
77+
* <li>OIDC provider calls {@code register("security", "authentication", "OIDC"}</li>
78+
* <li>OIDC provider calls {@code register("security", "outbound", "OIDC"} if outbound is enabled</li>
79+
* </ul>
80+
*
81+
* @param path path of the feature (single value for root level features)
82+
*/
83+
public static void register(String... path) {
84+
for (HelidonFlavor value : HelidonFlavor.values()) {
85+
register(value, path);
86+
}
87+
}
88+
89+
static Node ensureNode(Map<String, Node> rootFeatureNodes, String... path) {
90+
// last part of the path is the name
91+
if (path.length == 1) {
92+
return rootFeatureNodes.computeIfAbsent(path[0], Node::new);
93+
}
94+
// we have a path, let's go through it
95+
96+
// start with root
97+
Node lastNode = ensureNode(rootFeatureNodes, path[0]);
98+
for (int i = 1; i < path.length; i++) {
99+
String pathElement = path[i];
100+
lastNode = ensureNode(pathElement, lastNode);
101+
}
102+
return lastNode;
103+
}
104+
105+
static Node ensureNode(String name, Node parent) {
106+
return parent.children.computeIfAbsent(name, it -> new Node(name));
107+
}
108+
109+
// testing only
110+
static Map<String, Node> rootFeatureNodes(HelidonFlavor flavor) {
111+
return ROOT_FEATURE_NODES.computeIfAbsent(flavor, it -> new HashMap<>());
52112
}
53113

54114
/**
@@ -58,9 +118,11 @@ public static void register(HelidonFlavor flavor, String name) {
58118
* This is to make sure we do not print SE flavors in MP, and at the
59119
* same time can have this method used from Web Server.
60120
* This method only prints feature the first time it is called.
121+
*
61122
* @param flavor flavor to print features for
123+
* @param details set to {@code true} to print the tree structure of sub-features
62124
*/
63-
public static void print(HelidonFlavor flavor) {
125+
public static void print(HelidonFlavor flavor, boolean details) {
64126
CURRENT_FLAVOR.compareAndSet(null, HelidonFlavor.SE);
65127

66128
HelidonFlavor currentFlavor = CURRENT_FLAVOR.get();
@@ -69,23 +131,56 @@ public static void print(HelidonFlavor flavor) {
69131
return;
70132
}
71133

72-
if (PRINTED.compareAndSet(false, true)) {
73-
Set<String> strings = FEATURES.get(currentFlavor);
74-
if (null == strings) {
75-
LOGGER.info("Helidon " + currentFlavor + " " + Version.VERSION + " has no registered features");
76-
} else {
77-
LOGGER.info("Helidon " + currentFlavor + " " + Version.VERSION + " features: " + strings);
78-
}
134+
if (!PRINTED.compareAndSet(false, true)) {
135+
return;
136+
}
137+
Set<String> strings = FEATURES.get(currentFlavor);
138+
if (null == strings) {
139+
LOGGER.info("Helidon " + currentFlavor + " " + Version.VERSION + " has no registered features");
140+
} else {
141+
LOGGER.info("Helidon " + currentFlavor + " " + Version.VERSION + " features: " + strings);
142+
}
143+
if (details) {
144+
LOGGER.info("Detailed feature tree:");
145+
FEATURES.get(currentFlavor)
146+
.forEach(name -> printDetails(name, ROOT_FEATURE_NODES.get(currentFlavor).get(name), 0));
79147
}
80148
}
81149

150+
private static void printDetails(String name, Node node, int level) {
151+
152+
System.out.println(" ".repeat(level) + name);
153+
154+
node.children.forEach((childName, childNode) -> printDetails(childName, childNode, level + 1));
155+
}
156+
82157
/**
83158
* Set the current Helidon flavor. Features will only be printed for the
84159
* flavor configured.
85160
*
161+
* The first flavor configured wins.
162+
*
86163
* @param flavor current flavor
87164
*/
88165
public static void flavor(HelidonFlavor flavor) {
89166
CURRENT_FLAVOR.compareAndSet(null, flavor);
90167
}
168+
169+
static final class Node {
170+
private final Map<String, Node> children = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
171+
private final String name;
172+
173+
Node(String name) {
174+
this.name = name;
175+
}
176+
177+
String name() {
178+
return name;
179+
}
180+
181+
// for tests
182+
Map<String, Node> children() {
183+
return children;
184+
}
185+
}
91186
}

common/common/src/main/java/io/helidon/common/HelidonFlavor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
*/
2222
public enum HelidonFlavor {
2323
/**
24-
* The "standard edition" flavor.
24+
* The "Standard Edition" flavor.
2525
*/
2626
SE,
2727
/**
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.helidon.common;
18+
19+
import java.util.Map;
20+
21+
import org.junit.jupiter.api.Test;
22+
23+
import static io.helidon.common.HelidonFeatures.register;
24+
import static io.helidon.common.HelidonFlavor.SE;
25+
import static org.hamcrest.CoreMatchers.is;
26+
import static org.hamcrest.MatcherAssert.assertThat;
27+
import static org.hamcrest.Matchers.containsInAnyOrder;
28+
29+
/**
30+
* Unit test for {@link HelidonFeatures}.
31+
*/
32+
class HelidonFeaturesTest {
33+
@Test
34+
void testFeatureTree() {
35+
register(SE, "security");
36+
register(SE, "security", "authorization", "ABAC");
37+
register(SE, "security", "authentication", "OIDC");
38+
register(SE, "security", "outbound", "OIDC");
39+
register(SE, "tracing");
40+
register(SE, "tracing", "zipkin");
41+
register(SE, "complex");
42+
register(SE, "complex", "first");
43+
register(SE, "complex", "first", "second");
44+
register(SE, "complex", "first", "second", "third");
45+
register(SE, "complex", "first", "second", "third2");
46+
47+
Map<String, HelidonFeatures.Node> features = HelidonFeatures.rootFeatureNodes(SE);
48+
49+
assertThat(features.keySet(), containsInAnyOrder("security", "tracing", "complex"));
50+
51+
HelidonFeatures.Node security = features.get("security");
52+
assertThat(security.name(), is("security"));
53+
54+
Map<String, HelidonFeatures.Node> children = security.children();
55+
assertThat(children.keySet(), containsInAnyOrder("authorization", "authentication", "outbound"));
56+
HelidonFeatures.Node node = children.get("authentication");
57+
assertThat(node.name(), is("authentication"));
58+
HelidonFeatures.Node oidc = node.children().get("OIDC");
59+
assertThat(oidc.name(), is("OIDC"));
60+
assertThat(oidc.children(), is(Map.of()));
61+
62+
HelidonFeatures.Node second = features.get("complex").children().get("first").children().get("second");
63+
Map<String, HelidonFeatures.Node> secondChildren = second.children();
64+
assertThat(secondChildren.keySet(), containsInAnyOrder("third", "third2"));
65+
66+
HelidonFeatures.print(SE, true);
67+
}
68+
}

config/config/src/main/java/io/helidon/config/BuilderImpl.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
import javax.annotation.Priority;
4040

4141
import io.helidon.common.GenericType;
42+
import io.helidon.common.HelidonFeatures;
43+
import io.helidon.common.HelidonFlavor;
4244
import io.helidon.common.Prioritized;
4345
import io.helidon.common.serviceloader.HelidonServiceLoader;
4446
import io.helidon.common.serviceloader.Priorities;
@@ -65,6 +67,10 @@
6567
class BuilderImpl implements Config.Builder {
6668
static final Executor DEFAULT_CHANGES_EXECUTOR = Executors.newCachedThreadPool(new ConfigThreadFactory("config"));
6769

70+
static {
71+
HelidonFeatures.register(HelidonFlavor.SE, "Config");
72+
}
73+
6874
/*
6975
* Config sources
7076
*/

config/encryption/src/main/java/io/helidon/config/encryption/EncryptionFilter.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@
2525
import java.util.logging.Level;
2626
import java.util.logging.Logger;
2727

28+
import io.helidon.common.HelidonFeatures;
2829
import io.helidon.common.pki.KeyConfig;
2930
import io.helidon.config.Config;
3031
import io.helidon.config.MissingValueException;
@@ -66,6 +67,10 @@ public final class EncryptionFilter implements ConfigFilter {
6667
private static final String PREFIX_ALIAS = "${ALIAS=";
6768
private static final String PREFIX_CLEAR = "${CLEAR=";
6869

70+
static {
71+
HelidonFeatures.register("Config", "Encryption");
72+
}
73+
6974
private final PrivateKey privateKey;
7075
private final char[] masterPassword;
7176

config/etcd/src/main/java/io/helidon/config/etcd/EtcdConfigSource.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
2323
import java.util.logging.Level;
2424
import java.util.logging.Logger;
2525

26+
import io.helidon.common.HelidonFeatures;
2627
import io.helidon.config.Config;
2728
import io.helidon.config.ConfigException;
2829
import io.helidon.config.ConfigHelper;
@@ -43,6 +44,10 @@ public class EtcdConfigSource extends AbstractParsableConfigSource<Long> {
4344

4445
private static final Logger LOGGER = Logger.getLogger(EtcdConfigSource.class.getName());
4546

47+
static {
48+
HelidonFeatures.register("Config", "etcd");
49+
}
50+
4651
private final EtcdEndpoint endpoint;
4752
private final EtcdClient client;
4853

config/git/src/main/java/io/helidon/config/git/GitConfigSource.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -33,6 +33,7 @@
3333
import java.util.logging.Level;
3434
import java.util.logging.Logger;
3535

36+
import io.helidon.common.HelidonFeatures;
3637
import io.helidon.config.Config;
3738
import io.helidon.config.ConfigException;
3839
import io.helidon.config.ConfigHelper;
@@ -61,6 +62,10 @@ public class GitConfigSource extends AbstractParsableConfigSource<byte[]> {
6162

6263
private static final Logger LOGGER = Logger.getLogger(GitConfigSource.class.getName());
6364

65+
static {
66+
HelidonFeatures.register("Config", "git");
67+
}
68+
6469
private final URI uri;
6570
private final String branch;
6671

config/hocon/src/main/java/io/helidon/config/hocon/internal/HoconConfigParser.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
2121

2222
import javax.annotation.Priority;
2323

24+
import io.helidon.common.HelidonFeatures;
2425
import io.helidon.config.ConfigException;
2526
import io.helidon.config.ConfigHelper;
2627
import io.helidon.config.spi.ConfigNode.ListNode;
@@ -71,6 +72,10 @@ public class HoconConfigParser implements ConfigParser {
7172
private static final Set<String> SUPPORTED_MEDIA_TYPES =
7273
Set.of(MEDIA_TYPE_APPLICATION_HOCON, MEDIA_TYPE_APPLICATION_JSON);
7374

75+
static {
76+
HelidonFeatures.register("Config", "HOCON");
77+
}
78+
7479
private final boolean resolvingEnabled;
7580
private final ConfigResolveOptions resolveOptions;
7681

0 commit comments

Comments
 (0)