Skip to content

Commit

Permalink
Add template expansion engine
Browse files Browse the repository at this point in the history
This is a generic template expansion engine. Here we use it to implement
expressions for cluster node identifiers (-Dmidpoint.nodeIdExpression).

It is the first part of resolution of MID-5904.
  • Loading branch information
mederly committed Dec 9, 2019
1 parent 5b9d6d6 commit ca20dc3
Show file tree
Hide file tree
Showing 19 changed files with 864 additions and 137 deletions.
Expand Up @@ -22,6 +22,7 @@ public interface MidpointConfiguration {
String MIDPOINT_LOGGING_ALT_FILENAME_PROPERTY = "midpoint.logging.alt.filename";
String MIDPOINT_LOGGING_ALT_PREFIX_PROPERTY = "midpoint.logging.alt.prefix";
String MIDPOINT_NODE_ID_PROPERTY = "midpoint.nodeId";
String MIDPOINT_NODE_ID_EXPRESSION_PROPERTY = "midpoint.nodeIdExpression";
String MIDPOINT_NODE_ID_SOURCE_PROPERTY = "midpoint.nodeIdSource";
@Deprecated String MIDPOINT_JMX_HOST_NAME_PROPERTY = "midpoint.jmxHostName";
String MIDPOINT_URL_PROPERTY = "midpoint.url";
Expand Down
22 changes: 0 additions & 22 deletions infra/util/src/main/java/com/evolveum/midpoint/util/MiscUtil.java
Expand Up @@ -819,26 +819,4 @@ public static String readZipFile(File file, Charset charset) throws IOException
}
}
}

public static String expandProperties(String value) {
StringBuilder sb = new StringBuilder();
int pointer = 0;
for (;;) {
int i = value.indexOf("${", pointer);
if (i < 0) {
sb.append(value.substring(pointer));
return sb.toString();
}
int j = value.indexOf("}", i);
if (j < 0) {
LOGGER.warn("Missing closing '}' in {}", value);
sb.append(value.substring(pointer));
return sb.toString();
}
sb.append(value, pointer, i);
String propertyName = value.substring(i+2, j);
sb.append(System.getProperty(propertyName));
pointer = j+1;
}
}
}

This file was deleted.

@@ -0,0 +1,59 @@
/*
* Copyright (c) 2019 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/

package com.evolveum.midpoint.util.template;

import org.jetbrains.annotations.NotNull;

import java.util.Collection;
import java.util.List;

/**
* Resolver that is able to call upstream if it cannot resolve a reference.
*/
public abstract class AbstractChainedResolver implements ReferenceResolver {

/**
* Resolver to call if we are unable to help.
*/
private final ReferenceResolver upstreamResolver;

/**
* Whether this resolver is engaged in resolving references in default (null) scope.
*/
private final boolean actsAsDefault;

public AbstractChainedResolver(ReferenceResolver upstreamResolver, boolean actsAsDefault) {
this.upstreamResolver = upstreamResolver;
this.actsAsDefault = actsAsDefault;
}

protected abstract String resolveLocally(String reference, List<String> parameters);

@NotNull
protected abstract Collection<String> getScopes();

@Override
public String resolve(String scope, String reference, @NotNull List<String> parameters) {
if (isScopeApplicable(scope)) {
String resolution = resolveLocally(reference, parameters);
if (resolution != null) {
return resolution;
}
}

if (upstreamResolver != null) {
return upstreamResolver.resolve(scope, reference, parameters);
} else {
return null;
}
}

private boolean isScopeApplicable(String scope) {
return actsAsDefault && scope == null || getScopes().contains(scope);
}
}
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2019 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/

package com.evolveum.midpoint.util.template;

import org.jetbrains.annotations.NotNull;

import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
* Resolves references using Java system properties.
*/
public class JavaPropertiesResolver extends AbstractChainedResolver {

@SuppressWarnings("WeakerAccess")
public static final String SCOPE = "prop";

public JavaPropertiesResolver(ReferenceResolver upstreamResolver, boolean actsAsDefault) {
super(upstreamResolver, actsAsDefault);
}

@Override
protected String resolveLocally(String reference, List<String> parameters) {
return System.getProperty(reference);
}

@NotNull
@Override
protected Collection<String> getScopes() {
return Collections.singletonList(SCOPE);
}
}
@@ -0,0 +1,41 @@
/*
* Copyright (c) 2019 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/

package com.evolveum.midpoint.util.template;

import org.jetbrains.annotations.NotNull;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
* Resolves references using externally provided map.
*/
public class MapResolver extends AbstractChainedResolver {

private final String myScope;
private final Map<String, String> map;

public MapResolver(ReferenceResolver upstreamResolver, boolean actsAsDefault, String myScope, Map<String, String> map) {
super(upstreamResolver, actsAsDefault);
this.myScope = myScope;
this.map = map;
}

@Override
protected String resolveLocally(String reference, List<String> parameters) {
return map.get(reference);
}

@NotNull
@Override
protected Collection<String> getScopes() {
return myScope != null ? Collections.singleton(myScope) : Collections.emptySet();
}
}
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2019 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/

package com.evolveum.midpoint.util.template;

import org.jetbrains.annotations.NotNull;

import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
* Resolves references using operating system environment variables.
*/
public class OsEnvironmentResolver extends AbstractChainedResolver {

@SuppressWarnings("WeakerAccess")
public static final String SCOPE = "env";

public OsEnvironmentResolver(ReferenceResolver upstreamResolver, boolean actsAsDefault) {
super(upstreamResolver, actsAsDefault);
}

@Override
protected String resolveLocally(String reference, List<String> parameters) {
return System.getenv(reference);
}

@NotNull
@Override
protected Collection<String> getScopes() {
return Collections.singleton(SCOPE);
}
}
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2019 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/

package com.evolveum.midpoint.util.template;

import org.jetbrains.annotations.NotNull;

import java.util.List;

/**
* Resolver of references in templates.
*/
@FunctionalInterface
public interface ReferenceResolver {

/**
* Returns resolved value of the reference.
*
* @param scope Scope e.g. null meaning default scope, "env" meaning environment variables, "" meaning built-in references etc.
* @param reference Reference name e.g. "seq"
* @param parameters Reference parameters e.g. "%04d"
* @return Resolved reference value e.g. "0003"
*/
String resolve(String scope, String reference, @NotNull List<String> parameters);
}
@@ -0,0 +1,77 @@
/*
* Copyright (c) 2019 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/
package com.evolveum.midpoint.util.template;

import org.jetbrains.annotations.NotNull;

import java.util.Map;

/**
* Generic template expansion engine.
*
* Beyond simple references like $ref it supports scoped and parameterized expressions in the form of ${scope:ref(parameters)}.
* Allows the use of '\' as a general escaping character (e.g. for "$" sign, for brackets, colons, quotes, etc).
*/
public class TemplateEngine {

/**
* Resolver of the references.
*/
private final ReferenceResolver resolver;

/**
* If true, expressions are marked as ${...}. This is the recommended way.
* If false, legacy marking of {...} is used.
*/
private final boolean useExpressionStartCharacter;

/**
* If a reference cannot be resolved, should we throw an error (true), or silently replace it with an empty string (false)?
*/
private final boolean errorOnUnresolved;

public TemplateEngine(ReferenceResolver resolver, boolean useExpressionStartCharacter, boolean errorOnUnresolved) {
this.resolver = resolver;
this.useExpressionStartCharacter = useExpressionStartCharacter;
this.errorOnUnresolved = errorOnUnresolved;
}

/**
* Generic templating function.
*
* @param template Template e.g. node-${builtin:seq(%04d)}
* @return resolved template e.g. node-0003
*/
public String expand(String template) {
return new TemplateResolution(template, resolver, useExpressionStartCharacter, errorOnUnresolved).resolve();
}

/**
* Simply expands ${propertyName} Java system properties.
*/
public static String simpleExpandProperties(String template) {
JavaPropertiesResolver resolver = new JavaPropertiesResolver(null, true);
TemplateEngine engine = new TemplateEngine(resolver, true, false);
return engine.expand(template);
}

/**
* Evaluates a template against set of replacement mappings.
* The string(s) to be matched are denoted by "{key}" sequence(s).
*
* This is a legacy method. We should use the "$" sign in the future.
*
* @param template Template e.g. "{masterTaskName} ({index})"
* @param replacements Map of e.g. "masterTaskName" -> "Reconciliation", "index" -> "1"
* @return resolved template, e.g. "Reconciliation (1)"
*/
public static String simpleExpand(String template, @NotNull Map<String, String> replacements) {
MapResolver resolver = new MapResolver(null, true, null, replacements);
TemplateEngine engine = new TemplateEngine(resolver, false, false);
return engine.expand(template);
}
}

0 comments on commit ca20dc3

Please sign in to comment.