Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.nifi.parameter;

import java.util.List;

/**
* Utility methods related to parameter references in parameter values.
*/
public final class ParameterReferenceUtils {

private static final ParameterParser PARSER = new ExpressionLanguageAgnosticParameterParser();

private ParameterReferenceUtils() {
}

/**
* Returns the referenced parameter name if {@code value} is a one-to-one parameter reference,
* meaning its entire content is exactly a single parameter reference token such as
* {@code #{foo}} or {@code #{'My Parameter'}}. Returns {@code null} if {@code value} is not a
* one-to-one parameter reference (for example, if it is {@code null}, empty, contains literal
* text in addition to a reference, contains multiple references, contains an escaped
* reference, or is malformed).
*
* @param value the value to inspect
* @return the referenced parameter name, or {@code null} if {@code value} is not a one-to-one
* parameter reference
*/
public static String extractOneToOneParameterReference(final String value) {
if (value == null || value.isEmpty()) {
return null;
}
final List<ParameterToken> tokens = PARSER.parseTokens(value).toList();
if (tokens.size() != 1) {
return null;
}
final ParameterToken token = tokens.getFirst();
if (!token.isParameterReference()) {
return null;
}
if (token.getStartOffset() != 0 || token.getEndOffset() != value.length() - 1) {
return null;
}
return ((ParameterReference) token).getParameterName();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.nifi.parameter;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;

public class TestParameterReferenceUtils {

@Test
public void testExtractOneToOneParameterReferenceMatches() {
assertEquals("foo", ParameterReferenceUtils.extractOneToOneParameterReference("#{foo}"));
assertEquals("a-b_c.d", ParameterReferenceUtils.extractOneToOneParameterReference("#{a-b_c.d}"));
assertEquals("My Parameter", ParameterReferenceUtils.extractOneToOneParameterReference("#{'My Parameter'}"));
assertEquals("with.dot", ParameterReferenceUtils.extractOneToOneParameterReference("#{'with.dot'}"));
}

@Test
public void testExtractOneToOneParameterReferenceRejectsNonOneToOneValues() {
assertNull(ParameterReferenceUtils.extractOneToOneParameterReference(null));
assertNull(ParameterReferenceUtils.extractOneToOneParameterReference(""));
assertNull(ParameterReferenceUtils.extractOneToOneParameterReference("plain text"));
assertNull(ParameterReferenceUtils.extractOneToOneParameterReference("prefix #{foo}"));
assertNull(ParameterReferenceUtils.extractOneToOneParameterReference("#{foo}suffix"));
assertNull(ParameterReferenceUtils.extractOneToOneParameterReference("#{a}#{b}"));
assertNull(ParameterReferenceUtils.extractOneToOneParameterReference("##{foo}"));
assertNull(ParameterReferenceUtils.extractOneToOneParameterReference("#{abc"));
assertNull(ParameterReferenceUtils.extractOneToOneParameterReference("#{a}b}}"));
assertNull(ParameterReferenceUtils.extractOneToOneParameterReference("${#{foo}}"));
}
}
23 changes: 23 additions & 0 deletions nifi-docs/src/main/asciidoc/user-guide.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,29 @@ Sensitive properties may only reference sensitive Parameters. This is important
The value of a sensitive property must be set to a single Parameter reference. For example, values of `+#{password}123+` and `+#{password}#{suffix}+` are not allowed. Sending `+#{password}123+` would lead to exposing part of the sensitive property's value. This is in contrast to a non-sensitive property, where a value such as `+#{path}/child/file.txt+` is valid.


[[parameter-value-references]]
===== Parameter Value References

A Parameter's *value* may itself be a reference to another Parameter. When a Parameter's value is exactly `+#{otherParameter}+` (or `+#{'Other Parameter'}+` for names containing characters that are otherwise disallowed in unquoted references, such as spaces), the referencing Parameter is treated as a one-to-one alias of the referenced Parameter: at resolution time the alias's effective value is replaced with the referenced Parameter's value.

This is a one-to-one alias only. The Parameter's value must be a single reference and nothing else: literal text combined with a reference (`+jdbc://#{db_host}:3306+`) or multiple references (`+#{a}#{b}+`) are left as-is and not treated as aliases.

The referenced Parameter may be any Parameter visible in the bound Parameter Context's effective scope: a Parameter defined in the same Context, a Parameter inherited from another Parameter Context, or a Parameter sourced from a <<Parameter Providers,Parameter Provider>>. The referencing and referenced Parameters must have the same sensitivity; otherwise the reference is left unresolved.

Resolution is performed a single level deep -- aliases of aliases are not followed. For example, if Parameter `A` has value `+#{B}+` and Parameter `B` has value `+#{C}+`, then `A` resolves to the literal value `+#{C}+`, not to the value of `C`.

Updating the referenced Parameter's value cascades transparently: components configured with the alias are identified as affected components and are stopped, validated, and restarted as part of the Parameter Context update, just as if they had referenced the underlying Parameter directly.

To illustrate, assume a Parameter Context `Shared` defines `db_host` with value `myserver.example.com`, and a Parameter Context `App` inherits from `Shared` and defines an alias `host` with value `+#{db_host}+`. A processor bound to `App` configured with `+#{host}+` resolves to `myserver.example.com`.

|====
| *Aliasing Parameter (in App)* | *Referenced Parameter (in Shared)* | *Effective Property Value*
| `host` = `+#{db_host}+` | `db_host` = `myserver.example.com` | `myserver.example.com`
| `password` = `+#{secret_value}+` (sensitive) | `secret_value` = `s3cr3t` (sensitive) | `s3cr3t`
| `url` = `+jdbc://#{db_host}:3306+` | `db_host` = `myserver.example.com` | `+jdbc://#{db_host}:3306+` (not an alias - literal text plus reference)
|====


==== Parameter Providers

Parameter Providers allow parameters to be stored in sources external to NiFi (e.g. HashiCorp Vault). The parameters of a Parameter Provider can be fetched and applied to all referencing Parameter Contexts.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
import org.apache.nifi.parameter.ParameterContext;
import org.apache.nifi.parameter.ParameterDescriptor;
import org.apache.nifi.parameter.ParameterReference;
import org.apache.nifi.parameter.ParameterReferenceUtils;
import org.apache.nifi.parameter.ParameterUpdate;
import org.apache.nifi.parameter.StandardParameterUpdate;
import org.apache.nifi.processor.DataUnit;
Expand Down Expand Up @@ -3326,13 +3327,56 @@ public void setParameterContext(final ParameterContext parameterContext) {
public void onParameterContextUpdated(final Map<String, ParameterUpdate> updatedParameters) {
readLock.lock();
try {
getProcessors().forEach(proc -> proc.onParametersModified(updatedParameters));
getControllerServices(false).forEach(cs -> cs.onParametersModified(updatedParameters));
final Map<String, ParameterUpdate> effectiveUpdates = augmentWithParameterValueReferences(updatedParameters);
getProcessors().forEach(proc -> proc.onParametersModified(effectiveUpdates));
getControllerServices(false).forEach(cs -> cs.onParametersModified(effectiveUpdates));
} finally {
readLock.unlock();
}
}

/**
* Augments the given parameter update map with entries for local parameters whose values are
* one-to-one references to changed parameters. For example, if this group's context defines
* parameter X with value {@code #{db_host}} and db_host is in the update map, then X is added
* to the augmented map with the same old/new values, allowing components referencing X to be
* properly notified of the change. The referenced parameter may be any parameter visible in
* the bound context's effective scope (local, inherited from a user-managed context, or
* sourced from a Parameter Provider).
*/
private Map<String, ParameterUpdate> augmentWithParameterValueReferences(final Map<String, ParameterUpdate> updatedParameters) {
final ParameterContext context = getParameterContext();
if (context == null) {
return updatedParameters;
}

Map<String, ParameterUpdate> augmented = null;
for (final Map.Entry<ParameterDescriptor, Parameter> entry : context.getParameters().entrySet()) {
final Parameter localParam = entry.getValue();
final String referencedName = ParameterReferenceUtils.extractOneToOneParameterReference(localParam.getValue());
if (referencedName == null) {
continue;
}

final Optional<Parameter> referencedParam = context.getParameter(referencedName);
if (referencedParam.isEmpty()) {
continue;
}

final ParameterUpdate referencedUpdate = updatedParameters.get(referencedName);
if (referencedUpdate != null && localParam.getDescriptor().isSensitive() == referencedUpdate.isSensitive()) {
if (augmented == null) {
augmented = new HashMap<>(updatedParameters);
}
augmented.put(localParam.getDescriptor().getName(),
new StandardParameterUpdate(localParam.getDescriptor().getName(),
referencedUpdate.getPreviousValue(), referencedUpdate.getUpdatedValue(),
localParam.getDescriptor().isSensitive()));
}
}
return augmented != null ? augmented : updatedParameters;
}

private Map<String, ParameterUpdate> mapParameterUpdates(final ParameterContext previousParameterContext, final ParameterContext updatedParameterContext) {
if (previousParameterContext == null && updatedParameterContext == null) {
return Collections.emptyMap();
Expand Down
Loading
Loading