Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Helidon Features #1240

Merged
merged 1 commit into from
Jan 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 111 additions & 16 deletions common/common/src/main/java/io/helidon/common/HelidonFeatures.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
package io.helidon.common;

import java.util.EnumMap;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger;
Expand All @@ -29,10 +31,10 @@
*/
public final class HelidonFeatures {
private static final Logger LOGGER = Logger.getLogger(HelidonFeatures.class.getName());

private static final Map<HelidonFlavor, Set<String>> FEATURES = new EnumMap<>(HelidonFlavor.class);
private static final AtomicReference<HelidonFlavor> CURRENT_FLAVOR = new AtomicReference<>();
private static final AtomicBoolean PRINTED = new AtomicBoolean();
private static final AtomicReference<HelidonFlavor> CURRENT_FLAVOR = new AtomicReference<>();
private static final Map<HelidonFlavor, Set<String>> FEATURES = new EnumMap<>(HelidonFlavor.class);
private static final Map<HelidonFlavor, Map<String, Node>> ROOT_FEATURE_NODES = new EnumMap<>(HelidonFlavor.class);

private HelidonFeatures() {
}
Expand All @@ -43,12 +45,70 @@ private HelidonFeatures() {
* class. In SE this would be one of the *Support classes or similar,
* in MP most likely a CDI extension class.
*
* <p>Example for security providers (SE) - application with Oidc provider:
* <ul>
* <li>Security calls {@code register(SE, "security")}</li>
* <li>OIDC provider calls {@code register(SE, "security", "authentication", "OIDC"}</li>
* <li>OIDC provider calls {@code register(SE, "security", "outbound", "OIDC"} if outbound is enabled</li>
* </ul>
*
* @param flavor flavor to register a feature for
* @param name name of the feature
* @param path path of the feature (single value for root level features)
*/
public static void register(HelidonFlavor flavor, String name) {
FEATURES.computeIfAbsent(flavor, key -> new HashSet<>())
.add(name);
public static void register(HelidonFlavor flavor, String... path) {
if (path.length == 0) {
throw new IllegalArgumentException("At least the root feature name must be provided, but path was empty");
}
if (path.length == 1) {
FEATURES.computeIfAbsent(flavor, key -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER))
.add(path[0]);
}

var rootFeatures = ROOT_FEATURE_NODES.computeIfAbsent(flavor, it -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER));
ensureNode(rootFeatures, path);
}

/**
* Register a feature for all flavors.
*
* <p>Example for security providers (SE) - application with Oidc provider:
* <ul>
* <li>Security calls {@code register(SE, "security")}</li>
* <li>OIDC provider calls {@code register("security", "authentication", "OIDC"}</li>
* <li>OIDC provider calls {@code register("security", "outbound", "OIDC"} if outbound is enabled</li>
* </ul>
*
* @param path path of the feature (single value for root level features)
*/
public static void register(String... path) {
for (HelidonFlavor value : HelidonFlavor.values()) {
register(value, path);
}
}

static Node ensureNode(Map<String, Node> rootFeatureNodes, String... path) {
// last part of the path is the name
if (path.length == 1) {
return rootFeatureNodes.computeIfAbsent(path[0], Node::new);
}
// we have a path, let's go through it

// start with root
Node lastNode = ensureNode(rootFeatureNodes, path[0]);
for (int i = 1; i < path.length; i++) {
String pathElement = path[i];
lastNode = ensureNode(pathElement, lastNode);
}
return lastNode;
}

static Node ensureNode(String name, Node parent) {
return parent.children.computeIfAbsent(name, it -> new Node(name));
}

// testing only
static Map<String, Node> rootFeatureNodes(HelidonFlavor flavor) {
return ROOT_FEATURE_NODES.computeIfAbsent(flavor, it -> new HashMap<>());
}

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

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

if (PRINTED.compareAndSet(false, true)) {
Set<String> strings = FEATURES.get(currentFlavor);
if (null == strings) {
LOGGER.info("Helidon " + currentFlavor + " " + Version.VERSION + " has no registered features");
} else {
LOGGER.info("Helidon " + currentFlavor + " " + Version.VERSION + " features: " + strings);
}
if (!PRINTED.compareAndSet(false, true)) {
return;
}
Set<String> strings = FEATURES.get(currentFlavor);
if (null == strings) {
LOGGER.info("Helidon " + currentFlavor + " " + Version.VERSION + " has no registered features");
} else {
LOGGER.info("Helidon " + currentFlavor + " " + Version.VERSION + " features: " + strings);
}
if (details) {
LOGGER.info("Detailed feature tree:");
FEATURES.get(currentFlavor)
.forEach(name -> printDetails(name, ROOT_FEATURE_NODES.get(currentFlavor).get(name), 0));
}
}

private static void printDetails(String name, Node node, int level) {

System.out.println(" ".repeat(level) + name);

node.children.forEach((childName, childNode) -> printDetails(childName, childNode, level + 1));
}

/**
* Set the current Helidon flavor. Features will only be printed for the
* flavor configured.
*
* The first flavor configured wins.
*
* @param flavor current flavor
*/
public static void flavor(HelidonFlavor flavor) {
CURRENT_FLAVOR.compareAndSet(null, flavor);
}

static final class Node {
private final Map<String, Node> children = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
private final String name;

Node(String name) {
this.name = name;
}

String name() {
return name;
}

// for tests
Map<String, Node> children() {
return children;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
*/
public enum HelidonFlavor {
/**
* The "standard edition" flavor.
* The "Standard Edition" flavor.
*/
SE,
/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.helidon.common;

import java.util.Map;

import org.junit.jupiter.api.Test;

import static io.helidon.common.HelidonFeatures.register;
import static io.helidon.common.HelidonFlavor.SE;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;

/**
* Unit test for {@link HelidonFeatures}.
*/
class HelidonFeaturesTest {
@Test
void testFeatureTree() {
register(SE, "security");
register(SE, "security", "authorization", "ABAC");
register(SE, "security", "authentication", "OIDC");
register(SE, "security", "outbound", "OIDC");
register(SE, "tracing");
register(SE, "tracing", "zipkin");
register(SE, "complex");
register(SE, "complex", "first");
register(SE, "complex", "first", "second");
register(SE, "complex", "first", "second", "third");
register(SE, "complex", "first", "second", "third2");

Map<String, HelidonFeatures.Node> features = HelidonFeatures.rootFeatureNodes(SE);

assertThat(features.keySet(), containsInAnyOrder("security", "tracing", "complex"));

HelidonFeatures.Node security = features.get("security");
assertThat(security.name(), is("security"));

Map<String, HelidonFeatures.Node> children = security.children();
assertThat(children.keySet(), containsInAnyOrder("authorization", "authentication", "outbound"));
HelidonFeatures.Node node = children.get("authentication");
assertThat(node.name(), is("authentication"));
HelidonFeatures.Node oidc = node.children().get("OIDC");
assertThat(oidc.name(), is("OIDC"));
assertThat(oidc.children(), is(Map.of()));

HelidonFeatures.Node second = features.get("complex").children().get("first").children().get("second");
Map<String, HelidonFeatures.Node> secondChildren = second.children();
assertThat(secondChildren.keySet(), containsInAnyOrder("third", "third2"));

HelidonFeatures.print(SE, true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
import javax.annotation.Priority;

import io.helidon.common.GenericType;
import io.helidon.common.HelidonFeatures;
import io.helidon.common.HelidonFlavor;
import io.helidon.common.Prioritized;
import io.helidon.common.serviceloader.HelidonServiceLoader;
import io.helidon.common.serviceloader.Priorities;
Expand All @@ -65,6 +67,10 @@
class BuilderImpl implements Config.Builder {
static final Executor DEFAULT_CHANGES_EXECUTOR = Executors.newCachedThreadPool(new ConfigThreadFactory("config"));

static {
HelidonFeatures.register(HelidonFlavor.SE, "Config");
}

/*
* Config sources
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -25,6 +25,7 @@
import java.util.logging.Level;
import java.util.logging.Logger;

import io.helidon.common.HelidonFeatures;
import io.helidon.common.pki.KeyConfig;
import io.helidon.config.Config;
import io.helidon.config.MissingValueException;
Expand Down Expand Up @@ -66,6 +67,10 @@ public final class EncryptionFilter implements ConfigFilter {
private static final String PREFIX_ALIAS = "${ALIAS=";
private static final String PREFIX_CLEAR = "${CLEAR=";

static {
HelidonFeatures.register("Config", "Encryption");
}

private final PrivateKey privateKey;
private final char[] masterPassword;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -23,6 +23,7 @@
import java.util.logging.Level;
import java.util.logging.Logger;

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

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

static {
HelidonFeatures.register("Config", "etcd");
}

private final EtcdEndpoint endpoint;
private final EtcdClient client;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -33,6 +33,7 @@
import java.util.logging.Level;
import java.util.logging.Logger;

import io.helidon.common.HelidonFeatures;
import io.helidon.config.Config;
import io.helidon.config.ConfigException;
import io.helidon.config.ConfigHelper;
Expand Down Expand Up @@ -61,6 +62,10 @@ public class GitConfigSource extends AbstractParsableConfigSource<byte[]> {

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

static {
HelidonFeatures.register("Config", "git");
}

private final URI uri;
private final String branch;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -21,6 +21,7 @@

import javax.annotation.Priority;

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

static {
HelidonFeatures.register("Config", "HOCON");
}

private final boolean resolvingEnabled;
private final ConfigResolveOptions resolveOptions;

Expand Down
Loading