Skip to content

Commit

Permalink
[WFCORE-7087] Support resolution of extension expressions during prep…
Browse files Browse the repository at this point in the history
…aration of a domain server launch command
  • Loading branch information
bstansberry committed Dec 12, 2024
1 parent b124810 commit 3bd163b
Show file tree
Hide file tree
Showing 15 changed files with 272 additions and 116 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright The WildFly Authors
* SPDX-License-Identifier: Apache-2.0
*/

package org.jboss.as.controller;

import static org.wildfly.common.Assert.checkNotNullParam;

import java.util.Optional;

import org.jboss.as.controller.capability.CapabilityServiceSupport;
import org.jboss.as.controller.capability.RuntimeCapability;
import org.jboss.as.controller.capability.registry.CapabilityScope;
import org.jboss.as.controller.capability.registry.ImmutableCapabilityRegistry;
import org.jboss.as.controller.logging.ControllerLogger;
import org.jboss.msc.service.ServiceName;

/**
* Default implementation of {@link CapabilityServiceSupport}.
*/
public final class DefaultCapabilityServiceSupport implements CapabilityServiceSupport {
private final ImmutableCapabilityRegistry registry;

/**
* Creates a new DefaultCapabilityServiceSupport.
* @param registry a capability registry. Cannot be {@code null}.
*/
public DefaultCapabilityServiceSupport(ImmutableCapabilityRegistry registry) {
checkNotNullParam("registry", registry);
this.registry = registry;
}

@Override
public boolean hasCapability(String capabilityName) {
return registry.hasCapability(capabilityName, CapabilityScope.GLOBAL);
}

@Override
public <T> T getCapabilityRuntimeAPI(String capabilityName, Class<T> apiType) throws NoSuchCapabilityException {
try {
return registry.getCapabilityRuntimeAPI(capabilityName, CapabilityScope.GLOBAL, apiType);
} catch (IllegalStateException e) {
throw new NoSuchCapabilityException(capabilityName);
}
}

@Override
public <T> T getCapabilityRuntimeAPI(String capabilityBaseName, String dynamicPart, Class<T> apiType) throws NoSuchCapabilityException {
String fullName = RuntimeCapability.buildDynamicCapabilityName(capabilityBaseName, dynamicPart);
return getCapabilityRuntimeAPI(fullName, apiType);
}

@Override
public <T> Optional<T> getOptionalCapabilityRuntimeAPI(String capabilityName, Class<T> apiType) {
try {
return Optional.of(getCapabilityRuntimeAPI(capabilityName, apiType));
} catch (NoSuchCapabilityException e) {
return Optional.empty();
}
}

@Override
public <T> Optional<T> getOptionalCapabilityRuntimeAPI(String capabilityBaseName, String dynamicPart, Class<T> apiType) {
try {
return Optional.of(getCapabilityRuntimeAPI(capabilityBaseName, dynamicPart, apiType));
} catch (NoSuchCapabilityException e) {
return Optional.empty();
}
}

@Override
public ServiceName getCapabilityServiceName(String capabilityName) {
try {
return registry.getCapabilityServiceName(capabilityName, CapabilityScope.GLOBAL, null);
} catch (IllegalStateException | IllegalArgumentException ignore) {
// ignore
}
ControllerLogger.ROOT_LOGGER.debugf("CapabilityServiceSupport: Parsing ServiceName for %s", capabilityName);
return ServiceNameFactory.parseServiceName(capabilityName);
}

@Override
public ServiceName getCapabilityServiceName(String capabilityBaseName, String... dynamicPart) {
ServiceName name = getCapabilityServiceName(capabilityBaseName);
return (dynamicPart.length > 0) ? name.append(dynamicPart) : name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import java.util.regex.Pattern;

import org.jboss.as.controller.capability.CapabilityServiceSupport;
import org.jboss.as.controller.extension.ExpressionResolverExtension;
import org.jboss.as.controller.extension.ResolverExtensionRegistry;
import org.jboss.as.controller.logging.ControllerLogger;
Expand All @@ -23,9 +24,9 @@ public interface ExpressionResolver {
Pattern EXPRESSION_PATTERN = Pattern.compile(".*\\$\\{.*}.*");

/**
* Resolves any expressions in the passed in ModelNode.
*
* Expressions may represent system properties, vaulted date, or a custom format to be handled by an
* Resolves any expressions in the passed-in ModelNode.
* <p/>
* Expressions may represent system properties, environment variables or a custom format to be handled by an
* {@link ExpressionResolverExtension} registered using the {@link ResolverExtensionRegistry}.
*
* @param node the ModelNode containing expressions.
Expand All @@ -47,13 +48,11 @@ public interface ExpressionResolver {
ModelNode resolveExpressions(ModelNode node) throws OperationFailedException;

/**
* Resolves any expressions in the passed in ModelNode.
*
* Expressions may represent system properties, vaulted date, or a custom format to be handled by an
* Resolves any expressions in the passed-in ModelNode.
* <p/>
* Expressions may represent system properties, environment variables or a custom format to be handled by an
* {@link ExpressionResolverExtension} registered using the {@link ResolverExtensionRegistry}.
*
* For vaulted data the format is ${VAULT::vault_block::attribute_name::sharedKey}
*
* @param node the ModelNode containing expressions.
* @param context the current {@code OperationContext} to provide additional contextual information.
* @return a copy of the node with expressions resolved
Expand All @@ -75,6 +74,33 @@ default ModelNode resolveExpressions(ModelNode node, OperationContext context) t
return resolveExpressions(node);
}

/**
* Resolves any expressions in the passed-in ModelNode.
* <p/>
* Expressions may represent system properties, environment variables or a custom format to be handled by an
* {@link ExpressionResolverExtension} registered using the {@link ResolverExtensionRegistry}.
*
* @param node the ModelNode containing expressions.
* @param capabilitySupport support object for accessing capability-backed APIs.
* @return a copy of the node with expressions resolved
*
* @throws ExpressionResolutionUserException if {@code expression} is a form understood by the resolver but in some
* way is unacceptable. This should only be thrown due to flaws in the
* provided {@code expression} or the configuration of resources used by
* the resolver extension, which are 'user' problems>. It should not
* be used for internal problems in the resolver extension. If a
* if a security manager exists and its
* {@link SecurityManager#checkPermission checkPermission} method doesn't
* allow access to a relevant system property or environment variable,
* an {@code ExpressionResolutionUserException} should be thrown
* @throws OperationFailedException if an {@link ExpressionResolverExtension} throws one from its
* {@link ExpressionResolverExtension#initialize(OperationContext)} method.
* @throws ExpressionResolver.ExpressionResolutionServerException if some other internal expression resolution failure occurs.
*/
default ModelNode resolveExpressions(ModelNode node, CapabilityServiceSupport capabilitySupport) throws OperationFailedException {
return resolveExpressions(node);
}

/**
* An {@code ExpressionResolver} that can only resolve from system properties
* and environment variables. Should not be used for most product resolution use cases as it does
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import java.util.Stack;

import org.jboss.as.controller.capability.CapabilityServiceSupport;
import org.jboss.as.controller.logging.ControllerLogger;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
Expand Down Expand Up @@ -45,48 +46,57 @@ protected ExpressionResolverImpl(boolean lenient) {
}

@Override
public final ModelNode resolveExpressions(final ModelNode node) throws OperationFailedException {
return resolveExpressions(node, null);
public ModelNode resolveExpressions(final ModelNode node) throws OperationFailedException {
return resolveExpressions(node, (OperationContext) null);
}

@Override
public ModelNode resolveExpressions(ModelNode node, OperationContext context) throws OperationFailedException {
return resolveExpressionsRecursively(node, context);
return resolveExpressionsRecursively(node, context, null);
}

@Override
public ModelNode resolveExpressions(ModelNode node, CapabilityServiceSupport capabilityServiceSupport) throws OperationFailedException {
return resolveExpressionsRecursively(node, null, capabilityServiceSupport);
}

/**
* Examine the given model node, resolving any expressions found within, including within child nodes.
*
* @param node the node
* @param context the {@link OperationContext}
* @param context any {@link OperationContext}. May be {@code null}.
* @param capabilitySupport any {@link CapabilityServiceSupport}. May be {@code null}.
* @return a node with all expressions resolved
* @throws OperationFailedException if an expression cannot be resolved
*/
private ModelNode resolveExpressionsRecursively(final ModelNode node, final OperationContext context) throws OperationFailedException {
private ModelNode resolveExpressionsRecursively(final ModelNode node, final OperationContext context,
final CapabilityServiceSupport capabilitySupport) throws OperationFailedException {
if (!node.isDefined()) {
return node;
}

ModelType type = node.getType();
ModelNode resolved;
if (type == ModelType.EXPRESSION) {
resolved = resolveExpressionStringRecursively(node.asExpression().getExpressionString(), lenient, true, context);
resolved = resolveExpressionStringRecursively(node.asExpression().getExpressionString(),
lenient, true, context, capabilitySupport);
} else if (type == ModelType.OBJECT) {
resolved = node.clone();
for (Property prop : resolved.asPropertyList()) {
resolved.get(prop.getName()).set(resolveExpressionsRecursively(prop.getValue(), context));
resolved.get(prop.getName()).set(resolveExpressionsRecursively(prop.getValue(), context, capabilitySupport));
}
} else if (type == ModelType.LIST) {
resolved = node.clone();
ModelNode list = new ModelNode();
list.setEmptyList();
for (ModelNode current : resolved.asList()) {
list.add(resolveExpressionsRecursively(current, context));
list.add(resolveExpressionsRecursively(current, context, capabilitySupport));
}
resolved = list;
} else if (type == ModelType.PROPERTY) {
resolved = node.clone();
resolved.set(resolved.asProperty().getName(), resolveExpressionsRecursively(resolved.asProperty().getValue(), context));
resolved.set(resolved.asProperty().getName(),
resolveExpressionsRecursively(resolved.asProperty().getValue(), context, capabilitySupport));
} else {
resolved = node;
}
Expand All @@ -113,6 +123,30 @@ private ModelNode resolveExpressionsRecursively(final ModelNode node, final Oper
protected void resolvePluggableExpression(ModelNode node, OperationContext context) throws OperationFailedException {
}

/**
* Attempt to resolve the expression {@link org.jboss.dmr.ModelNode#asString() encapsulated in the given node},
* setting the value of {@code node} to the resolved string if successful, or leaving {@code node} unaltered
* if the expression is not of a form resolvable by this method. When this method returns, the type of {@code node}
* should either be {@link ModelType#STRING} if this method was able to resolve, or {@link ModelType#EXPRESSION} if
* not.
* <p>
* The default implementation simply calls the {@link #resolvePluggableExpression(ModelNode, OperationContext)} variant.
* </p>
*
* @param node a node of type {@link ModelType#EXPRESSION}
* @param context the current {@link OperationContext}, if any. May be {@code null}
* @param capabilitySupport support object for resolving capability-based APIs. May be {@code null}
*
* @throws OperationFailedException if the expression in {@code node} is of a form that should be resolvable by this
* method but some resolution failure occurs
*/
protected void resolvePluggableExpression(ModelNode node, OperationContext context, CapabilityServiceSupport capabilitySupport) throws OperationFailedException {
// For compatibility, just call the older method. Subclasses can override this.
if (context != null) {
resolvePluggableExpression(node, context);
}
}

/**
* Attempt to resolve the given expression string, recursing if resolution of one string produces
* another expression.
Expand All @@ -129,13 +163,14 @@ protected void resolvePluggableExpression(ModelNode node, OperationContext conte
* @throws OperationFailedException if the expression cannot be resolved
*/
private ModelNode resolveExpressionStringRecursively(final String expressionString, final boolean ignoreDMRResolutionFailure,
final boolean initial, final OperationContext context) throws OperationFailedException {
ParseAndResolveResult resolved = parseAndResolve(expressionString, ignoreDMRResolutionFailure, context);
final boolean initial, final OperationContext context,
final CapabilityServiceSupport capabilitySupport) throws OperationFailedException {
ParseAndResolveResult resolved = parseAndResolve(expressionString, ignoreDMRResolutionFailure, context, capabilitySupport);
if (resolved.recursive) {
// Some part of expressionString resolved into a different expression.
// So, start over, ignoring failures. Ignore failures because we don't require
// that expressions must not resolve to something that *looks like* an expression but isn't
return resolveExpressionStringRecursively(resolved.result, true, false, context);
return resolveExpressionStringRecursively(resolved.result, true, false, context, capabilitySupport);
} else if (resolved.modified) {
// Typical case
return new ModelNode(resolved.result);
Expand All @@ -155,7 +190,8 @@ private ModelNode resolveExpressionStringRecursively(final String expressionStri
}
}

private ParseAndResolveResult parseAndResolve(final String initialValue, boolean lenient, OperationContext context) throws OperationFailedException {
private ParseAndResolveResult parseAndResolve(final String initialValue, boolean lenient,
OperationContext context, CapabilityServiceSupport capabilitySupport) throws OperationFailedException {


final StringBuilder builder = new StringBuilder();
Expand Down Expand Up @@ -233,7 +269,7 @@ private ParseAndResolveResult parseAndResolve(final String initialValue, boolean
continue;
}
String toResolve = getStringToResolve(initialValue, stack, i);
final String resolved = resolveExpressionString(toResolve, context); // TODO we could catch OFE or ERUE here
final String resolved = resolveExpressionString(toResolve, context, capabilitySupport); // TODO we could catch OFE or ERUE here
// and if lenient respond with
// the initial value, else rethrow
// But for now it's a corner case
Expand Down Expand Up @@ -317,7 +353,8 @@ private static Stack<OpenExpression> addToStack(Stack<OpenExpression> stack, int
}

/** Resolve the given string using any plugin and the DMR resolve method */
private String resolveExpressionString(final String unresolvedString, final OperationContext context) throws OperationFailedException {
private String resolveExpressionString(final String unresolvedString, final OperationContext context,
final CapabilityServiceSupport capabilitySupport) throws OperationFailedException {

// parseAndResolve should only be providing expressions with no leading or trailing chars
assert unresolvedString.startsWith("${") && unresolvedString.endsWith("}");
Expand All @@ -328,7 +365,7 @@ private String resolveExpressionString(final String unresolvedString, final Oper
ModelNode resolveNode = new ModelNode(new ValueExpression(unresolvedString));

// Try plug-in resolution; i.e. vault
resolvePluggableExpression(resolveNode, context);
resolvePluggableExpression(resolveNode, context, capabilitySupport);

if (resolveNode.getType() == ModelType.EXPRESSION ) {
// resolvePluggableExpression did nothing. Try standard resolution
Expand Down
Loading

0 comments on commit 3bd163b

Please sign in to comment.