From ca20dc3d549b5d03c602c0a76253a986e92069a6 Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Mon, 9 Dec 2019 15:09:04 +0100 Subject: [PATCH] Add template expansion engine 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. --- .../api/MidpointConfiguration.java | 1 + .../com/evolveum/midpoint/util/MiscUtil.java | 22 -- .../evolveum/midpoint/util/TemplateUtil.java | 32 --- .../template/AbstractChainedResolver.java | 59 ++++ .../util/template/JavaPropertiesResolver.java | 38 +++ .../midpoint/util/template/MapResolver.java | 41 +++ .../util/template/OsEnvironmentResolver.java | 38 +++ .../util/template/ReferenceResolver.java | 29 ++ .../util/template/TemplateEngine.java | 77 ++++++ .../util/template/TemplateResolution.java | 203 ++++++++++++++ .../evolveum/midpoint/util/TestMiscUtil.java | 16 -- .../midpoint/util/TestTemplateEngine.java | 98 +++++++ infra/util/testng-unit.xml | 1 + .../impl/controller/ModelDiagController.java | 3 +- .../midpoint/task/quartzimpl/Initializer.java | 5 +- .../task/quartzimpl/NodeIdComputer.java | 253 ++++++++++++++++++ .../quartzimpl/TaskManagerConfiguration.java | 69 +---- .../handlers/PartitioningTaskHandler.java | 10 +- .../work/workers/WorkersManager.java | 6 +- 19 files changed, 864 insertions(+), 137 deletions(-) delete mode 100644 infra/util/src/main/java/com/evolveum/midpoint/util/TemplateUtil.java create mode 100644 infra/util/src/main/java/com/evolveum/midpoint/util/template/AbstractChainedResolver.java create mode 100644 infra/util/src/main/java/com/evolveum/midpoint/util/template/JavaPropertiesResolver.java create mode 100644 infra/util/src/main/java/com/evolveum/midpoint/util/template/MapResolver.java create mode 100644 infra/util/src/main/java/com/evolveum/midpoint/util/template/OsEnvironmentResolver.java create mode 100644 infra/util/src/main/java/com/evolveum/midpoint/util/template/ReferenceResolver.java create mode 100644 infra/util/src/main/java/com/evolveum/midpoint/util/template/TemplateEngine.java create mode 100644 infra/util/src/main/java/com/evolveum/midpoint/util/template/TemplateResolution.java create mode 100644 infra/util/src/test/java/com/evolveum/midpoint/util/TestTemplateEngine.java create mode 100644 repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/NodeIdComputer.java diff --git a/infra/common/src/main/java/com/evolveum/midpoint/common/configuration/api/MidpointConfiguration.java b/infra/common/src/main/java/com/evolveum/midpoint/common/configuration/api/MidpointConfiguration.java index 2661ca28f3d..a94307a22e2 100644 --- a/infra/common/src/main/java/com/evolveum/midpoint/common/configuration/api/MidpointConfiguration.java +++ b/infra/common/src/main/java/com/evolveum/midpoint/common/configuration/api/MidpointConfiguration.java @@ -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"; diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/MiscUtil.java b/infra/util/src/main/java/com/evolveum/midpoint/util/MiscUtil.java index 264bd0fbac8..f052ef7d436 100644 --- a/infra/util/src/main/java/com/evolveum/midpoint/util/MiscUtil.java +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/MiscUtil.java @@ -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; - } - } } diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/TemplateUtil.java b/infra/util/src/main/java/com/evolveum/midpoint/util/TemplateUtil.java deleted file mode 100644 index f1bb2f2e242..00000000000 --- a/infra/util/src/main/java/com/evolveum/midpoint/util/TemplateUtil.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2010-2018 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; - -import org.jetbrains.annotations.NotNull; - -import java.util.Map; -import java.util.Map.Entry; - -/** - * @author mederly - * - */ -public class TemplateUtil { - - // very primitive implementation, for now - // TODO implement some escaping of control characters - public static String replace(String template, @NotNull Map replacements) { - if (template == null) { - return null; - } - String rv = template; - for (Entry entry : replacements.entrySet()) { - rv = rv.replace("{" + entry.getKey() + "}", entry.getValue()); - } - return rv; - } -} diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/template/AbstractChainedResolver.java b/infra/util/src/main/java/com/evolveum/midpoint/util/template/AbstractChainedResolver.java new file mode 100644 index 00000000000..713cb30a660 --- /dev/null +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/template/AbstractChainedResolver.java @@ -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 parameters); + + @NotNull + protected abstract Collection getScopes(); + + @Override + public String resolve(String scope, String reference, @NotNull List 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); + } +} diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/template/JavaPropertiesResolver.java b/infra/util/src/main/java/com/evolveum/midpoint/util/template/JavaPropertiesResolver.java new file mode 100644 index 00000000000..61cae00f004 --- /dev/null +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/template/JavaPropertiesResolver.java @@ -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 parameters) { + return System.getProperty(reference); + } + + @NotNull + @Override + protected Collection getScopes() { + return Collections.singletonList(SCOPE); + } +} diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/template/MapResolver.java b/infra/util/src/main/java/com/evolveum/midpoint/util/template/MapResolver.java new file mode 100644 index 00000000000..436bbd3bb8a --- /dev/null +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/template/MapResolver.java @@ -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 map; + + public MapResolver(ReferenceResolver upstreamResolver, boolean actsAsDefault, String myScope, Map map) { + super(upstreamResolver, actsAsDefault); + this.myScope = myScope; + this.map = map; + } + + @Override + protected String resolveLocally(String reference, List parameters) { + return map.get(reference); + } + + @NotNull + @Override + protected Collection getScopes() { + return myScope != null ? Collections.singleton(myScope) : Collections.emptySet(); + } +} diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/template/OsEnvironmentResolver.java b/infra/util/src/main/java/com/evolveum/midpoint/util/template/OsEnvironmentResolver.java new file mode 100644 index 00000000000..027302840a9 --- /dev/null +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/template/OsEnvironmentResolver.java @@ -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 parameters) { + return System.getenv(reference); + } + + @NotNull + @Override + protected Collection getScopes() { + return Collections.singleton(SCOPE); + } +} diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/template/ReferenceResolver.java b/infra/util/src/main/java/com/evolveum/midpoint/util/template/ReferenceResolver.java new file mode 100644 index 00000000000..04b52c932e4 --- /dev/null +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/template/ReferenceResolver.java @@ -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 parameters); +} \ No newline at end of file diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/template/TemplateEngine.java b/infra/util/src/main/java/com/evolveum/midpoint/util/template/TemplateEngine.java new file mode 100644 index 00000000000..5e679e3decd --- /dev/null +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/template/TemplateEngine.java @@ -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 replacements) { + MapResolver resolver = new MapResolver(null, true, null, replacements); + TemplateEngine engine = new TemplateEngine(resolver, false, false); + return engine.expand(template); + } +} diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/template/TemplateResolution.java b/infra/util/src/main/java/com/evolveum/midpoint/util/template/TemplateResolution.java new file mode 100644 index 00000000000..aa6b73c20b6 --- /dev/null +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/template/TemplateResolution.java @@ -0,0 +1,203 @@ +/* + * 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.ArrayList; +import java.util.List; + +import static java.util.Collections.emptyList; + +/** + * Represents a state in template resolution process. + */ +class TemplateResolution { + + private static final char SCOPE_DELIMITER = ':'; + private static final char EXPRESSION_START_CHARACTER = '$'; + + // Configuration + @NotNull private final String template; + @NotNull private final ReferenceResolver resolver; + private final boolean useExpressionStartCharacter; + private final boolean errorOnUnresolved; + + // State + private StringBuilder result = new StringBuilder(); + private int position = 0; + private int errorReportingSegmentStart; + + TemplateResolution(@NotNull String template, @NotNull ReferenceResolver resolver, boolean useExpressionStartCharacter, + boolean errorOnUnresolved) { + this.template = template; + this.resolver = resolver; + this.useExpressionStartCharacter = useExpressionStartCharacter; + this.errorOnUnresolved = errorOnUnresolved; + } + + String resolve() { + while (position < template.length()) { + char current = template.charAt(position); + if (position < template.length() - 1) { + char next = template.charAt(position + 1); + if (current == '\\') { + result.append(next); + position++; + } else if (useExpressionStartCharacter && current == EXPRESSION_START_CHARACTER && next != '{') { + resolveSimpleReference(); + } else if (useExpressionStartCharacter && current == EXPRESSION_START_CHARACTER || + !useExpressionStartCharacter && current == '{') { + resolveComplexReference(); + } else { + result.append(current); + position++; + } + } else { + // whatever the last character is, let's output it + result.append(current); + position++; + } + } + return result.toString(); + } + + private void resolveSimpleReference() { + int start = ++position; + while (position < template.length() && isReferenceChar(template.charAt(position))) { + position++; + } + if (position > start) { + String reference = template.substring(start, position); + resolveAndAppend(null, reference, emptyList()); + } else { + result.append(EXPRESSION_START_CHARACTER); + } + } + + /** + * Resolves something like ${scope:ref(parameters)} (with scope and parameters being optional). + * + * parameters = parameter1,parameter2,...,parameterN + * where each parameter is either a plain string (except for comma and ')') or quoted string - in single or double quotes + */ + private void resolveComplexReference() { + errorReportingSegmentStart = position; + if (useExpressionStartCharacter) { + position += 2; + } else { + position += 1; + } + StringBuilder currentSegment = new StringBuilder(); + String scope = null; + String reference = null; + List parameters = new ArrayList<>(); + for (; position < template.length(); position++) { + char current = template.charAt(position); + if (current == '\\') { + position++; + if (position == template.length()) { + currentSegment.append(current); // What else? It will end up wrong anyway, as we should close with '}' + } else { + currentSegment.append(template.charAt(position)); + } + } else if (scope == null && current == SCOPE_DELIMITER) { + scope = currentSegment.toString().trim(); + currentSegment.setLength(0); + } else if (current == '(' || current == '}') { + reference = currentSegment.toString().trim(); + if (current == '(') { + position++; + parseParameters(parameters); + } + break; + } else { + currentSegment.append(current); + } + } + skipWhitespaces(); + if (position < template.length() && template.charAt(position) == '}') { + resolveAndAppend(scope, reference, parameters); + position++; + } else { + throw new IllegalArgumentException("Unfinished reference: " + template.substring(errorReportingSegmentStart)); + } + } + + private void parseParameters(List parameters) { + while (position < template.length()) { + char current = template.charAt(position); + if (Character.isWhitespace(current)) { + position++; + } else if (current == ')') { + position++; + break; + } else if (current == '\'' || current == '\"') { + //noinspection UnnecessaryLocalVariable + char border = current; + position++; + StringBuilder parameter = new StringBuilder(); + while (position < template.length()) { + char c = template.charAt(position++); + if (c == border) { + parameters.add(parameter.toString()); + skipWhitespaces(); + if (position < template.length()) { + char separator = template.charAt(position++); + if (separator == ')') { + return; + } else if (separator == ',') { + break; + } else { + throw new IllegalArgumentException("Unexpected content after parameter: " + template.substring(errorReportingSegmentStart, position)); + } + } + } else { + parameter.append(c); + } + } + } else { + StringBuilder parameter = new StringBuilder(); + while (position < template.length()) { + char c = template.charAt(position++); + if (c == ',' || c == ')') { + parameters.add(parameter.toString().trim()); + if (c == ')') { + return; + } else { + break; + } + } else { + parameter.append(c); + } + } + } + } + } + + private void skipWhitespaces() { + while (position < template.length() && Character.isWhitespace(template.charAt(position))) { + position++; + } + } + + private boolean isReferenceChar(char c) { + return Character.isLetterOrDigit(c) || c == '.' || c == '_' || c == '-'; + } + + private void resolveAndAppend(String scope, String reference, @NotNull List parameters) { + String resolved = resolver.resolve(scope, reference, parameters); + if (resolved != null) { + result.append(resolved); + } else if (errorOnUnresolved) { + throw new IllegalStateException("Reference '" + reference + "' in scope '" + scope + "' couldn't be resolved"); + } else { + // silently ignore + } + } +} diff --git a/infra/util/src/test/java/com/evolveum/midpoint/util/TestMiscUtil.java b/infra/util/src/test/java/com/evolveum/midpoint/util/TestMiscUtil.java index 3feeebf20c1..6b275a4a43b 100644 --- a/infra/util/src/test/java/com/evolveum/midpoint/util/TestMiscUtil.java +++ b/infra/util/src/test/java/com/evolveum/midpoint/util/TestMiscUtil.java @@ -111,20 +111,4 @@ public void testCarthesian() { System.out.println(combinations); assertEquals("Wrong number of results", 24, combinations.size()); } - - @Test - public void testExpandProperties() { - System.out.println("===[ testExpandProperties ]==="); - System.setProperty("t1", "TEST1"); - System.setProperty("t2", "TEST2"); - assertEquals("Wrong result", "", MiscUtil.expandProperties("")); - assertEquals("Wrong result", "abc", MiscUtil.expandProperties("abc")); - assertEquals("Wrong result", "TEST1", MiscUtil.expandProperties("${t1}")); - assertEquals("Wrong result", "abcTEST1", MiscUtil.expandProperties("abc${t1}")); - assertEquals("Wrong result", "abcTEST1def", MiscUtil.expandProperties("abc${t1}def")); - assertEquals("Wrong result", "TEST1TEST2", MiscUtil.expandProperties("${t1}${t2}")); - assertEquals("Wrong result", "${t1", MiscUtil.expandProperties("${t1")); - assertEquals("Wrong result", "abc${t1", MiscUtil.expandProperties("abc${t1")); - - } } diff --git a/infra/util/src/test/java/com/evolveum/midpoint/util/TestTemplateEngine.java b/infra/util/src/test/java/com/evolveum/midpoint/util/TestTemplateEngine.java new file mode 100644 index 00000000000..8686314a47e --- /dev/null +++ b/infra/util/src/test/java/com/evolveum/midpoint/util/TestTemplateEngine.java @@ -0,0 +1,98 @@ +/* + * 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; + +import com.evolveum.midpoint.util.template.*; +import org.jetbrains.annotations.NotNull; +import org.testng.annotations.Test; + +import java.util.*; + +import static org.testng.AssertJUnit.*; + +public class TestTemplateEngine { + + @Test + public void testTemplateWithJavaProperties() { + System.out.println("===[ testTemplateWithJavaProperties ]==="); + System.setProperty("t1", "TEST1"); + System.setProperty("t2", "TEST2"); + + ReferenceResolver resolver = new JavaPropertiesResolver(null, true); + TemplateEngine engine = new TemplateEngine(resolver, true, false); + assertExpand(engine, "", ""); + assertExpand(engine, "abc", "abc"); + assertExpand(engine, "${t1}", "TEST1"); + assertExpand(engine, "${t3}", ""); + assertExpand(engine, "$t1", "TEST1"); + assertExpand(engine, "$t1 $t2", "TEST1 TEST2"); + assertExpand(engine, "abc${t1}", "abcTEST1"); + assertExpand(engine, "abc${t1}def", "abcTEST1def"); + assertExpand(engine, "${t1}${t2}", "TEST1TEST2"); + assertExpand(engine, "${prop:t1} ${prop:t2()}", "TEST1 TEST2"); + assertExpand(engine, "${prop:t1(abc)} ${prop:t2('abc')}", "TEST1 TEST2"); + assertExpand(engine, "${prop:t1(abc)} ${prop:t2('abc\\}')}", "TEST1 TEST2"); + assertError(engine, "${t1", "Unfinished reference"); + assertError(engine, "abc${t1", "Unfinished reference"); + assertError(engine, "${t1('abc", "Unfinished reference"); + assertError(engine, "${t1('abc' abc", "Unexpected content after parameter"); + } + + @Test + public void testTemplateWithBuiltin() { + System.out.println("===[ testTemplateWithBuiltin ]==="); + Map resolutionMap = new HashMap<>(); + resolutionMap.put("k1", "Key1"); + resolutionMap.put("k2", "Key2"); + + AbstractChainedResolver builtinResolver = new AbstractChainedResolver(null, false) { + @Override + protected String resolveLocally(String reference, List parameters) { + if ("concat".equals(reference)) { + return String.join("+", parameters); + } else { + return null; + } + } + + @NotNull + @Override + protected Collection getScopes() { + return Collections.singleton(""); + } + }; + ReferenceResolver resolver = new MapResolver(builtinResolver, true, "map", resolutionMap); + + TemplateEngine engine = new TemplateEngine(resolver, true, true); + assertExpand(engine, "", ""); + assertExpand(engine, "abc", "abc"); + assertExpand(engine, "${k1}", "Key1"); + assertExpand(engine, "${:concat}", ""); + assertExpand(engine, "x${:concat(a,'b','',d)}x", "xa+b++dx"); + assertExpand(engine, "x${ : concat ( a, 'b', ' ', d)}x", "xa+b+ +dx"); + assertError(engine, "${wrong}", "couldn't be resolved"); + } + + private void assertExpand(TemplateEngine engine, String template, String expected) { + String result = engine.expand(template); + System.out.println(template + " => " + result); + assertEquals("Wrong result", expected, result); + } + + private void assertError(TemplateEngine engine, String template, String expected) { + try { + String result = engine.expand(template); + fail("Unexpected success with result=" + result); + } catch (RuntimeException e) { + if (e.getMessage().contains(expected)) { + System.out.println("Got expected exception: " + e.getMessage()); + } else { + throw new AssertionError("Got unexpected exception: " + e.getMessage(), e); + } + } + } +} diff --git a/infra/util/testng-unit.xml b/infra/util/testng-unit.xml index 6a0d2c86ce9..464347988f7 100644 --- a/infra/util/testng-unit.xml +++ b/infra/util/testng-unit.xml @@ -25,6 +25,7 @@ + diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelDiagController.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelDiagController.java index 0dc8b6d1ed0..bf9f1c0bdaa 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelDiagController.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelDiagController.java @@ -24,6 +24,7 @@ import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer; import com.evolveum.midpoint.util.SystemUtil; import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.template.TemplateEngine; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.apache.commons.io.IOUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -659,7 +660,7 @@ private File getLogFile(OperationResult result) throws SchemaException { .filter(a -> rootLoggerAppender.equals(a.getName())).findFirst().orElse(null); if (rootFileAppender instanceof FileAppenderConfigurationType) { String fileName = ((FileAppenderConfigurationType) rootFileAppender).getFileName(); - return new File(MiscUtil.expandProperties(fileName)); + return new File(TemplateEngine.simpleExpandProperties(fileName)); } } } diff --git a/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/Initializer.java b/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/Initializer.java index 329603212f2..4ef73ab6ca3 100644 --- a/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/Initializer.java +++ b/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/Initializer.java @@ -25,9 +25,8 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; /** - * @author Pavol Mederly + * Initializes the task manager. */ - public class Initializer { private static final transient Trace LOGGER = TraceManager.getTrace(Initializer.class); @@ -47,7 +46,7 @@ public void init(OperationResult result) throws TaskManagerInitializationExcepti // get the configuration (general section + JDBC section as well) TaskManagerConfiguration configuration = taskManager.getConfiguration(); configuration.checkAllowedKeys(midpointConfiguration); - configuration.setBasicInformation(midpointConfiguration); + configuration.setBasicInformation(midpointConfiguration, result); configuration.validateBasicInformation(); LOGGER.info("Task Manager: Quartz Job Store: " diff --git a/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/NodeIdComputer.java b/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/NodeIdComputer.java new file mode 100644 index 00000000000..bdef411d6e5 --- /dev/null +++ b/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/NodeIdComputer.java @@ -0,0 +1,253 @@ +/* + * 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.task.quartzimpl; + +import com.evolveum.midpoint.common.configuration.api.MidpointConfiguration; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.schema.SearchResultList; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.TaskManagerConfigurationException; +import com.evolveum.midpoint.task.quartzimpl.cluster.NodeRegistrar; +import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.exception.SystemException; +import com.evolveum.midpoint.util.logging.LoggingUtils; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.util.template.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.NodeType; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; + +import java.net.UnknownHostException; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * Determines node ID for the current node based on configuration and currently registered nodes. + */ +class NodeIdComputer { + + private static final transient Trace LOGGER = TraceManager.getTrace(NodeIdComputer.class); + + private static final double DEFAULT_RANDOM_RANGE = 1000000000.0; + private static final String DEFAULT_NODE_ID = "DefaultNode"; + + private static final String NODE_ID_SOURCE_RANDOM = "random"; + private static final String NODE_ID_SOURCE_HOSTNAME = "hostname"; + private static final String NODE_ID_EXPRESSION_SEQUENCE = "sequence"; + + private static final int MAX_ITERATIONS = 100; + + private final PrismContext prismContext; + private final RepositoryService repositoryService; + + NodeIdComputer(PrismContext prismContext, RepositoryService repositoryService) { + this.prismContext = prismContext; + this.repositoryService = repositoryService; + } + + String determineNodeId(Configuration c, boolean clustered, OperationResult result) throws TaskManagerConfigurationException { + String id = c.getString(MidpointConfiguration.MIDPOINT_NODE_ID_PROPERTY, null); + if (StringUtils.isNotEmpty(id)) { + LOGGER.info("Using explicitly provided node ID of '{}'", id); + return id; + } + + String expression = c.getString(MidpointConfiguration.MIDPOINT_NODE_ID_EXPRESSION_PROPERTY, null); + if (StringUtils.isNotEmpty(expression)) { + String idFromExpression = getNodeIdFromExpression(expression, result); + if (StringUtils.isNotEmpty(idFromExpression)) { + LOGGER.info("Using node ID of '{}' as provided by the expression '{}'", idFromExpression, expression); + return idFromExpression; + } else { + LOGGER.warn("Node ID expression '{}' returned no value, continuing with other options", expression); + } + } + + String source = c.getString(MidpointConfiguration.MIDPOINT_NODE_ID_SOURCE_PROPERTY, null); + if (StringUtils.isNotEmpty(source)) { + String idFromSource = getNodeIdFromSource(source); + if (StringUtils.isNotEmpty(idFromSource)) { + LOGGER.info("Using node ID of '{}' as determined by the '{}' source", idFromSource, source); + return idFromSource; + } else { + LOGGER.warn("Node ID source '{}' provided no value, continuing with other options", source); + } + } + + if (!clustered) { + LOGGER.info("Using default node ID of '{}'", DEFAULT_NODE_ID); + return DEFAULT_NODE_ID; + } else { + throw new TaskManagerConfigurationException("Node ID must be set when running in clustered mode"); + } + } + + private class BuiltinResolver extends AbstractChainedResolver { + + private boolean iterationRequired = false; + private int iterationCounter = 0; + + private BuiltinResolver(ReferenceResolver upstreamResolver, boolean actsAsDefault) { + super(upstreamResolver, actsAsDefault); + } + + @Override + protected String resolveLocally(String reference, List parameters) { + if (NODE_ID_SOURCE_RANDOM.equals(reference)) { + double range; + if (parameters.isEmpty()) { + range = DEFAULT_RANDOM_RANGE; + } else if (parameters.size() == 1) { + range = Double.parseDouble(parameters.get(0)); + } else { + throw new IllegalArgumentException("Too many parameters for 'random' expression: " + parameters); + } + return getNodeIdAsRandomValue(range); + } else if (NODE_ID_SOURCE_HOSTNAME.equals(reference)) { + return getNodeIdAsHostName(); + } else if (NODE_ID_EXPRESSION_SEQUENCE.equals(reference)) { + iterationRequired = true; + int iteration = iterationCounter++; + String format; + int base; + if (parameters.size() >= 1) { + format = parameters.get(0); + } else { + format = "%d"; + } + if (parameters.size() >= 2) { + base = Integer.parseInt(parameters.get(1)); + } else { + base = 0; + } + if (parameters.size() >= 3) { + throw new IllegalArgumentException("Too many parameters for 'sequence' expression: " + parameters); + } + return String.format(format, base + iteration); + } else { + return null; + } + } + + @NotNull + @Override + protected Collection getScopes() { + return Collections.singleton(""); + } + } + + @NotNull + private String getNodeIdFromExpression(String expression, OperationResult result) { + BuiltinResolver builtinResolver = new BuiltinResolver(null, true); + AbstractChainedResolver propertiesResolver = new JavaPropertiesResolver(builtinResolver, true); + AbstractChainedResolver osEnvironmentResolver = new OsEnvironmentResolver(propertiesResolver, true); + TemplateEngine engine = new TemplateEngine(osEnvironmentResolver, true, true); + + for (;;) { + String candidateNodeId = engine.expand(expression); + if (builtinResolver.iterationRequired) { + try { + // Let us try to create node with given name. If we fail we know we need to iterate. + // If we succeed, we will (later) replace the node with the correct content. + NodeType node = new NodeType(prismContext) + .name(candidateNodeId); + repositoryService.addObject(node.asPrismObject(), null, result); + } catch (ObjectAlreadyExistsException e) { + // We have a conflict. But the node might be - in fact - dead. So let's try to reclaim it if possible. + String nodeIdNorm = prismContext.getDefaultPolyStringNormalizer().normalize(candidateNodeId); + SearchResultList> existingNodes; + try { + existingNodes = repositoryService.searchObjects(NodeType.class, + prismContext.queryFor(NodeType.class) + .item(NodeType.F_NAME).eqPoly(candidateNodeId, nodeIdNorm).matchingNorm() + .build(), + null, result); + } catch (SchemaException ex) { + throw new SystemException("Unexpected schema exception while looking for node '" + candidateNodeId + + "': " + e.getMessage(), e); + } + if (existingNodes.isEmpty()) { + // Strange. The node should have gone in the meanwhile. To be safe, let's try another one. + LOGGER.info("Node name '{}' seemed to be already reserved. But it cannot be found now. Iterating to the" + + " next one (if possible).", candidateNodeId); + } else if (existingNodes.size() > 1) { + LOGGER.warn("Strange: More than one node with the name of '{}': {}. Trying next name in the sequence" + + "(if possible).", candidateNodeId, existingNodes); + } else { + NodeType existingNode = existingNodes.get(0).asObjectable(); + if (Boolean.FALSE.equals(existingNode.isRunning())) { + LOGGER.info("Considering using the node name of '{}' that already exists but is marked as being down" + + " (OID {}). So deleting the node and trying again.", candidateNodeId, existingNode.getOid()); + try { + repositoryService.deleteObject(NodeType.class, existingNode.getOid(), result); + } catch (ObjectNotFoundException ex) { + LoggingUtils.logExceptionAsWarning(LOGGER, "Couldn't delete the node {}. Probably someone" + + " else is faster than us.", ex, existingNode); + } + builtinResolver.iterationCounter--; // will retry this node + } else { + LOGGER.info("Node name '{}' is already reserved. Iterating to next one (if possible).", candidateNodeId); + } + } + + if (builtinResolver.iterationCounter < MAX_ITERATIONS) { + continue; + } else { + throw new SystemException("Cannot acquire node name. Maximum number of iterations (" + + MAX_ITERATIONS + ") has been reached."); + } + } catch (SchemaException e) { + throw new SystemException("Unexpected schema exception while creating temporary node: " + e.getMessage(), e); + } + } + return candidateNodeId; + } + } + + @NotNull + private String getNodeIdFromSource(@NotNull String source) { + switch (source) { + case NODE_ID_SOURCE_RANDOM: + return getNodeIdAsRandomValue(DEFAULT_RANDOM_RANGE); + case NODE_ID_SOURCE_HOSTNAME: + return getNodeIdAsHostName(); + default: + throw new IllegalArgumentException("Unsupported node ID source: " + source); + } + } + + @NotNull + private String getNodeIdAsRandomValue(double range) { + return "node-" + Math.round(Math.random() * range); + } + + @NotNull + private String getNodeIdAsHostName() { + try { + String hostName = NodeRegistrar.getLocalHostNameFromOperatingSystem(); + if (hostName != null) { + return hostName; + } else { + LOGGER.error("Couldn't determine nodeId as host name couldn't be obtained from the operating system"); + throw new SystemException( + "Couldn't determine nodeId as host name couldn't be obtained from the operating system"); + } + } catch (UnknownHostException e) { + LoggingUtils.logUnexpectedException(LOGGER, "Couldn't determine nodeId as host name couldn't be obtained from the operating system", e); + throw new SystemException( + "Couldn't determine nodeId as host name couldn't be obtained from the operating system", e); + } + } +} \ No newline at end of file diff --git a/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/TaskManagerConfiguration.java b/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/TaskManagerConfiguration.java index 65bc8804e96..88a54be5c74 100644 --- a/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/TaskManagerConfiguration.java +++ b/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/TaskManagerConfiguration.java @@ -7,31 +7,23 @@ package com.evolveum.midpoint.task.quartzimpl; import com.evolveum.midpoint.common.configuration.api.MidpointConfiguration; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.repo.sql.SqlRepositoryConfiguration; +import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.TaskManagerConfigurationException; import com.evolveum.midpoint.task.api.UseThreadInterrupt; -import com.evolveum.midpoint.task.quartzimpl.cluster.NodeRegistrar; -import com.evolveum.midpoint.util.exception.SystemException; -import com.evolveum.midpoint.util.logging.LoggingUtils; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskExecutionLimitationsType; import org.apache.commons.configuration.Configuration; import org.apache.commons.lang3.StringUtils; -import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import java.net.UnknownHostException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; +import java.util.*; -import static com.evolveum.midpoint.repo.sql.SqlRepositoryConfiguration.*; +import static com.evolveum.midpoint.repo.sql.SqlRepositoryConfiguration.Database; /** * Task Manager configuration, derived from "taskManager" section of midPoint config, @@ -49,6 +41,9 @@ public class TaskManagerConfiguration { private static final transient Trace LOGGER = TraceManager.getTrace(TaskManagerConfiguration.class); + @Autowired private RepositoryService repositoryService; + @Autowired private PrismContext prismContext; + private static final String STOP_ON_INITIALIZATION_FAILURE_CONFIG_ENTRY = "stopOnInitializationFailure"; private static final String THREADS_CONFIG_ENTRY = "threads"; private static final String CLUSTERED_CONFIG_ENTRY = "clustered"; @@ -97,7 +92,6 @@ public class TaskManagerConfiguration { private static final int THREADS_DEFAULT = 10; private static final boolean CLUSTERED_DEFAULT = false; // do not change this value! private static final boolean CREATE_QUARTZ_TABLES_DEFAULT = true; - private static final String NODE_ID_DEFAULT = "DefaultNode"; @Deprecated private static final int JMX_PORT_DEFAULT = 20001; @Deprecated private static final int JMX_CONNECT_TIMEOUT_DEFAULT = 5; private static final String USE_THREAD_INTERRUPT_DEFAULT = "whenNecessary"; @@ -119,9 +113,6 @@ public class TaskManagerConfiguration { private static final long WORK_ALLOCATION_INITIAL_DELAY_DEFAULT = 5000L; private static final long WORK_ALLOCATION_DEFAULT_FREE_BUCKET_WAIT_INTERVAL_DEFAULT = 20000L; - private static final String NODE_ID_SOURCE_RANDOM = "random"; - private static final String NODE_ID_SOURCE_HOSTNAME = "hostname"; - private boolean stopOnInitializationFailure; private int threads; private boolean jdbcJobStore; @@ -255,7 +246,7 @@ private void checkAllowedKeys(Configuration c) throws TaskManagerConfigurationEx } } - void setBasicInformation(MidpointConfiguration masterConfig) throws TaskManagerConfigurationException { + void setBasicInformation(MidpointConfiguration masterConfig, OperationResult result) throws TaskManagerConfigurationException { Configuration root = masterConfig.getConfiguration(); Configuration c = masterConfig.getConfiguration(MidpointConfiguration.TASK_MANAGER_CONFIGURATION); @@ -265,16 +256,7 @@ void setBasicInformation(MidpointConfiguration masterConfig) throws TaskManagerC clustered = c.getBoolean(CLUSTERED_CONFIG_ENTRY, CLUSTERED_DEFAULT); jdbcJobStore = c.getBoolean(JDBC_JOB_STORE_CONFIG_ENTRY, clustered); - nodeId = root.getString(MidpointConfiguration.MIDPOINT_NODE_ID_PROPERTY, null); - if (StringUtils.isEmpty(nodeId)) { - String source = root.getString(MidpointConfiguration.MIDPOINT_NODE_ID_SOURCE_PROPERTY, null); - if (StringUtils.isEmpty(source)) { - nodeId = clustered ? null : NODE_ID_DEFAULT; - } else { - nodeId = provideNodeId(source); - LOGGER.info("Using node ID of '{}' as determined by the '{}' source", nodeId, source); - } - } + nodeId = new NodeIdComputer(prismContext, repositoryService).determineNodeId(root, clustered, result); hostName = root.getString(MidpointConfiguration.MIDPOINT_HOST_NAME_PROPERTY, null); jmxHostName = root.getString(MidpointConfiguration.MIDPOINT_JMX_HOST_NAME_PROPERTY, null); @@ -285,8 +267,8 @@ void setBasicInformation(MidpointConfiguration masterConfig) throws TaskManagerC } else { try { jmxPort = Integer.parseInt(jmxPortString); - } catch(NumberFormatException nfe) { - throw new TaskManagerConfigurationException("Cannot get JMX management port - invalid integer value of " + jmxPortString, nfe); + } catch (NumberFormatException e) { + throw new TaskManagerConfigurationException("Cannot get JMX management port - invalid integer value of " + jmxPortString, e); } } httpPort = root.getInteger(MidpointConfiguration.MIDPOINT_HTTP_PORT_PROPERTY, null); @@ -385,28 +367,6 @@ static TaskExecutionLimitationsType parseExecutionLimitations(String limitations } } - private String provideNodeId(@NotNull String source) { - switch (source) { - case NODE_ID_SOURCE_RANDOM: - return "node-" + Math.round(Math.random() * 1000000000.0); - case NODE_ID_SOURCE_HOSTNAME: - try { - String hostName = NodeRegistrar.getLocalHostNameFromOperatingSystem(); - if (hostName != null) { - return hostName; - } else { - LOGGER.error("Couldn't determine nodeId as host name couldn't be obtained from the operating system"); - throw new SystemException( - "Couldn't determine nodeId as host name couldn't be obtained from the operating system"); - } - } catch (UnknownHostException e) { - LoggingUtils.logUnexpectedException(LOGGER, "Couldn't determine nodeId as host name couldn't be obtained from the operating system", e); - } - default: - throw new IllegalArgumentException("Unsupported node ID source: " + source); - } - } - private static final Map SCHEMAS = new HashMap<>(); private static final Map DELEGATES = new HashMap<>(); @@ -492,8 +452,7 @@ void validateBasicInformation() throws TaskManagerConfigurationException { mustBeTrue(jdbcJobStore, "Clustered task manager requires JDBC Quartz job store."); } - notEmpty(nodeId, "Node identifier must be set when run in clustered mode."); - mustBeFalse(clustered && jmxPort == 0, "JMX port number must be known when run in clustered mode."); + notEmpty(nodeId, "Node identifier must be set."); mustBeTrue(quartzNodeRegistrationCycleTime > 1 && quartzNodeRegistrationCycleTime <= 600, "Quartz node registration cycle time must be between 1 and 600 seconds"); mustBeTrue(nodeRegistrationCycleTime > 1 && nodeRegistrationCycleTime <= 600, "Node registration cycle time must be between 1 and 600 seconds"); diff --git a/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/handlers/PartitioningTaskHandler.java b/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/handlers/PartitioningTaskHandler.java index 1c68763db84..60000b90e12 100644 --- a/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/handlers/PartitioningTaskHandler.java +++ b/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/handlers/PartitioningTaskHandler.java @@ -17,7 +17,7 @@ import com.evolveum.midpoint.task.api.TaskPartitionsDefinition.TaskPartitionDefinition; import com.evolveum.midpoint.task.api.TaskRunResult.TaskRunResultStatus; import com.evolveum.midpoint.task.quartzimpl.TaskManagerQuartzImpl; -import com.evolveum.midpoint.util.TemplateUtil; +import com.evolveum.midpoint.util.template.TemplateEngine; import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; @@ -242,7 +242,7 @@ private String createSubtask(int index, TaskPartitionsDefinition partitionsDefin p -> p.getName(masterTask), ps -> ps.getName(masterTask), "{masterTaskName} ({index})", partition, partitionsDefinition); - String name = TemplateUtil.replace(nameTemplate, replacements); + String name = TemplateEngine.simpleExpand(nameTemplate, replacements); subtask.setName(PolyStringType.fromOrig(name)); TaskWorkManagementType workManagement = applyDefaults( @@ -262,7 +262,7 @@ private String createSubtask(int index, TaskPartitionsDefinition partitionsDefin ps -> ps.getHandlerUri(masterTask), null, partition, partitionsDefinition); - String handlerUri = TemplateUtil.replace(handlerUriTemplate, replacements); + String handlerUri = TemplateEngine.simpleExpand(handlerUriTemplate, replacements); if (handlerUri == null) { // The default for coordinator-based partitions is to put default handler into workers configuration // - but only if both partition and workers handler URIs are null. This is to be revisited some day. @@ -270,10 +270,10 @@ private String createSubtask(int index, TaskPartitionsDefinition partitionsDefin handlerUri = TaskConstants.WORKERS_CREATION_TASK_HANDLER_URI; if (workManagement.getWorkers() != null && workManagement.getWorkers().getHandlerUri() == null) { workManagement = workManagement.clone(); - workManagement.getWorkers().setHandlerUri(TemplateUtil.replace(DEFAULT_HANDLER_URI, replacements)); + workManagement.getWorkers().setHandlerUri(TemplateEngine.simpleExpand(DEFAULT_HANDLER_URI, replacements)); } } else { - handlerUri = TemplateUtil.replace(DEFAULT_HANDLER_URI, replacements); + handlerUri = TemplateEngine.simpleExpand(DEFAULT_HANDLER_URI, replacements); } } subtask.setHandlerUri(handlerUri); diff --git a/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/work/workers/WorkersManager.java b/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/work/workers/WorkersManager.java index 18874dbaa75..857a0af751d 100644 --- a/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/work/workers/WorkersManager.java +++ b/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/work/workers/WorkersManager.java @@ -21,7 +21,7 @@ import com.evolveum.midpoint.schema.result.OperationResultStatus; import com.evolveum.midpoint.task.api.*; import com.evolveum.midpoint.util.MiscUtil; -import com.evolveum.midpoint.util.TemplateUtil; +import com.evolveum.midpoint.util.template.TemplateEngine; import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; @@ -382,10 +382,10 @@ private WorkerKey createWorkerKey(String nodeIdentifier, int index, WorkerTasksP nameTemplate = "{coordinatorTaskName} ({node}:{index})"; } - String name = TemplateUtil.replace(nameTemplate, replacements); + String name = TemplateEngine.simpleExpand(nameTemplate, replacements); String executionGroupTemplate = defaultIfNull(perNodeConfig.getExecutionGroup(), "{node}"); - String executionGroup = MiscUtil.nullIfEmpty(TemplateUtil.replace(executionGroupTemplate, replacements)); + String executionGroup = MiscUtil.nullIfEmpty(TemplateEngine.simpleExpand(executionGroupTemplate, replacements)); return new WorkerKey(executionGroup, name, scavenger); }