Repository: nifi
Updated Branches:
  refs/heads/master 0bddcfe73 -> 32314d70f


http://git-wip-us.apache.org/repos/asf/nifi/blob/32314d70/nifi-commons/nifi-record-path/src/test/java/org/apache/nifi/record/path/TestRecordPath.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-record-path/src/test/java/org/apache/nifi/record/path/TestRecordPath.java
 
b/nifi-commons/nifi-record-path/src/test/java/org/apache/nifi/record/path/TestRecordPath.java
index 4f9b53d..51aca43 100644
--- 
a/nifi-commons/nifi-record-path/src/test/java/org/apache/nifi/record/path/TestRecordPath.java
+++ 
b/nifi-commons/nifi-record-path/src/test/java/org/apache/nifi/record/path/TestRecordPath.java
@@ -28,6 +28,7 @@ import java.util.Optional;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
+import org.apache.nifi.record.path.exception.RecordPathException;
 import org.apache.nifi.serialization.SimpleRecordSchema;
 import org.apache.nifi.serialization.record.DataType;
 import org.apache.nifi.serialization.record.MapRecord;
@@ -45,6 +46,19 @@ public class TestRecordPath {
         System.out.println(RecordPath.compile("/person[2]"));
         System.out.println(RecordPath.compile("//person[2]"));
         
System.out.println(RecordPath.compile("/person/child[1]//sibling/name"));
+
+        // contains is a 'filter function' so can be used as the predicate
+        RecordPath.compile("/name[contains(., 'hello')]");
+
+        // substring is not a filter function so cannot be used as a predicate
+        try {
+            RecordPath.compile("/name[substring(., 1, 2)]");
+        } catch (final RecordPathException e) {
+            // expected
+        }
+
+        // substring is not a filter function so can be used as *part* of a 
predicate but not as the entire predicate
+        RecordPath.compile("/name[substring(., 1, 2) = 'e']");
     }
 
     @Test
@@ -682,7 +696,7 @@ public class TestRecordPath {
 
         final FieldValue recordFieldValue = new StandardFieldValue(record, new 
RecordField("record", RecordFieldType.RECORD.getDataType()), null);
 
-        final List<FieldValue> fieldValues = 
RecordPath.compile("./name").evaluate(recordFieldValue).getSelectedFields().collect(Collectors.toList());
+        final List<FieldValue> fieldValues = 
RecordPath.compile("./name").evaluate(record, 
recordFieldValue).getSelectedFields().collect(Collectors.toList());
         assertEquals(1, fieldValues.size());
 
         final FieldValue fieldValue = fieldValues.get(0);
@@ -702,7 +716,7 @@ public class TestRecordPath {
         final FieldValue recordFieldValue = new StandardFieldValue(record, new 
RecordField("root", 
RecordFieldType.RECORD.getRecordDataType(record.getSchema())), null);
         final FieldValue nameFieldValue = new StandardFieldValue("John Doe", 
new RecordField("name", RecordFieldType.STRING.getDataType()), 
recordFieldValue);
 
-        final List<FieldValue> fieldValues = 
RecordPath.compile(".").evaluate(nameFieldValue).getSelectedFields().collect(Collectors.toList());
+        final List<FieldValue> fieldValues = 
RecordPath.compile(".").evaluate(record, 
nameFieldValue).getSelectedFields().collect(Collectors.toList());
         assertEquals(1, fieldValues.size());
 
         final FieldValue fieldValue = fieldValues.get(0);
@@ -714,6 +728,277 @@ public class TestRecordPath {
         assertEquals("Jane Doe", record.getValue("name"));
     }
 
+    @Test
+    public void testSubstringFunction() {
+        final List<RecordField> fields = new ArrayList<>();
+        fields.add(new RecordField("id", RecordFieldType.INT.getDataType()));
+        fields.add(new RecordField("name", 
RecordFieldType.STRING.getDataType()));
+
+        final RecordSchema schema = new SimpleRecordSchema(fields);
+
+        final Map<String, Object> values = new HashMap<>();
+        values.put("id", 48);
+        values.put("name", "John Doe");
+        final Record record = new MapRecord(schema, values);
+
+        final FieldValue fieldValue = RecordPath.compile("substring(/name, 0, 
4)").evaluate(record).getSelectedFields().findFirst().get();
+        assertEquals("John", fieldValue.getValue());
+
+        assertEquals("John", RecordPath.compile("substring(/name, 0, 
-5)").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals("", RecordPath.compile("substring(/name, 1000, 
1005)").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals("", RecordPath.compile("substring(/name, 4, 
3)").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals("John Doe", RecordPath.compile("substring(/name, 0, 
10000)").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals("", RecordPath.compile("substring(/name, -50, 
-1)").evaluate(record).getSelectedFields().findFirst().get().getValue());
+    }
+
+    @Test
+    public void testSubstringBeforeFunction() {
+        final List<RecordField> fields = new ArrayList<>();
+        fields.add(new RecordField("id", RecordFieldType.INT.getDataType()));
+        fields.add(new RecordField("name", 
RecordFieldType.STRING.getDataType()));
+
+        final RecordSchema schema = new SimpleRecordSchema(fields);
+
+        final Map<String, Object> values = new HashMap<>();
+        values.put("id", 48);
+        values.put("name", "John Doe");
+        final Record record = new MapRecord(schema, values);
+
+        assertEquals("John", RecordPath.compile("substringBefore(/name, ' 
')").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals("John Doe", RecordPath.compile("substringBefore(/name, 
'XYZ')").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals("John Doe", RecordPath.compile("substringBefore(/name, 
'')").evaluate(record).getSelectedFields().findFirst().get().getValue());
+
+        assertEquals("John D", RecordPath.compile("substringBeforeLast(/name, 
'o')").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals("John Doe", 
RecordPath.compile("substringBeforeLast(/name, 
'XYZ')").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals("John Doe", 
RecordPath.compile("substringBeforeLast(/name, 
'')").evaluate(record).getSelectedFields().findFirst().get().getValue());
+    }
+
+    @Test
+    public void testSubstringAfterFunction() {
+        final List<RecordField> fields = new ArrayList<>();
+        fields.add(new RecordField("id", RecordFieldType.INT.getDataType()));
+        fields.add(new RecordField("name", 
RecordFieldType.STRING.getDataType()));
+
+        final RecordSchema schema = new SimpleRecordSchema(fields);
+
+        final Map<String, Object> values = new HashMap<>();
+        values.put("id", 48);
+        values.put("name", "John Doe");
+        final Record record = new MapRecord(schema, values);
+
+        assertEquals("hn Doe", RecordPath.compile("substringAfter(/name, 
'o')").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals("John Doe", RecordPath.compile("substringAfter(/name, 
'XYZ')").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals("John Doe", RecordPath.compile("substringAfter(/name, 
'')").evaluate(record).getSelectedFields().findFirst().get().getValue());
+
+        assertEquals("e", RecordPath.compile("substringAfterLast(/name, 
'o')").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals("John Doe", RecordPath.compile("substringAfterLast(/name, 
'XYZ')").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals("John Doe", RecordPath.compile("substringAfterLast(/name, 
'')").evaluate(record).getSelectedFields().findFirst().get().getValue());
+    }
+
+    @Test
+    public void testContains() {
+        final Record record = createSimpleRecord();
+        assertEquals("John Doe", RecordPath.compile("/name[contains(., 
'o')]").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals(0L, RecordPath.compile("/name[contains(., 
'x')]").evaluate(record).getSelectedFields().count());
+
+        record.setValue("name", "John Doe 48");
+        assertEquals("John Doe 48", RecordPath.compile("/name[contains(., 
/id)]").evaluate(record).getSelectedFields().findFirst().get().getValue());
+    }
+
+    @Test
+    public void testStartsWith() {
+        final Record record = createSimpleRecord();
+        assertEquals("John Doe", RecordPath.compile("/name[startsWith(., 
'J')]").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals(0L, RecordPath.compile("/name[startsWith(., 
'x')]").evaluate(record).getSelectedFields().count());
+        assertEquals("John Doe", RecordPath.compile("/name[startsWith(., 
'')]").evaluate(record).getSelectedFields().findFirst().get().getValue());
+    }
+
+    @Test
+    public void testEndsWith() {
+        final Record record = createSimpleRecord();
+        assertEquals("John Doe", RecordPath.compile("/name[endsWith(., 
'e')]").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals(0L, RecordPath.compile("/name[endsWith(., 
'x')]").evaluate(record).getSelectedFields().count());
+        assertEquals("John Doe", RecordPath.compile("/name[endsWith(., 
'')]").evaluate(record).getSelectedFields().findFirst().get().getValue());
+    }
+
+    @Test
+    public void testIsEmpty() {
+        final Record record = createSimpleRecord();
+        assertEquals("John Doe", 
RecordPath.compile("/name[isEmpty(../missing)]").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals("John Doe", 
RecordPath.compile("/name[isEmpty(/missing)]").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals(0L, 
RecordPath.compile("/name[isEmpty(../id)]").evaluate(record).getSelectedFields().count());
+
+        record.setValue("missing", "   ");
+        assertEquals(0L, 
RecordPath.compile("/name[isEmpty(/missing)]").evaluate(record).getSelectedFields().count());
+    }
+
+
+    @Test
+    public void testIsBlank() {
+        final Record record = createSimpleRecord();
+        assertEquals("John Doe", 
RecordPath.compile("/name[isBlank(../missing)]").evaluate(record).getSelectedFields().findFirst().get().getValue());
+
+        record.setValue("missing", "   ");
+        assertEquals("John Doe", 
RecordPath.compile("/name[isBlank(../missing)]").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals("John Doe", 
RecordPath.compile("/name[isBlank(/missing)]").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals(0L, 
RecordPath.compile("/name[isBlank(../id)]").evaluate(record).getSelectedFields().count());
+    }
+
+    @Test
+    public void testContainsRegex() {
+        final List<RecordField> fields = new ArrayList<>();
+        fields.add(new RecordField("id", RecordFieldType.INT.getDataType()));
+        fields.add(new RecordField("name", 
RecordFieldType.STRING.getDataType()));
+
+        final RecordSchema schema = new SimpleRecordSchema(fields);
+
+        final Map<String, Object> values = new HashMap<>();
+        values.put("id", 48);
+        values.put("name", "John Doe");
+        final Record record = new MapRecord(schema, values);
+
+        assertEquals("John Doe", RecordPath.compile("/name[containsRegex(., 
'o')]").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals("John Doe", RecordPath.compile("/name[containsRegex(., 
'[xo]')]").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals(0L, RecordPath.compile("/name[containsRegex(., 
'x')]").evaluate(record).getSelectedFields().count());
+    }
+
+    @Test
+    public void testNot() {
+        final List<RecordField> fields = new ArrayList<>();
+        fields.add(new RecordField("id", RecordFieldType.INT.getDataType()));
+        fields.add(new RecordField("name", 
RecordFieldType.STRING.getDataType()));
+
+        final RecordSchema schema = new SimpleRecordSchema(fields);
+
+        final Map<String, Object> values = new HashMap<>();
+        values.put("id", 48);
+        values.put("name", "John Doe");
+        final Record record = new MapRecord(schema, values);
+
+        assertEquals("John Doe", RecordPath.compile("/name[not(contains(., 
'x'))]").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals(0L, RecordPath.compile("/name[not(. = 'John 
Doe')]").evaluate(record).getSelectedFields().count());
+        assertEquals("John Doe", RecordPath.compile("/name[not(. = 'Jane 
Doe')]").evaluate(record).getSelectedFields().findFirst().get().getValue());
+    }
+
+    @Test
+    public void testChainingFunctions() {
+        final List<RecordField> fields = new ArrayList<>();
+        fields.add(new RecordField("id", RecordFieldType.INT.getDataType()));
+        fields.add(new RecordField("name", 
RecordFieldType.STRING.getDataType()));
+
+        final RecordSchema schema = new SimpleRecordSchema(fields);
+
+        final Map<String, Object> values = new HashMap<>();
+        values.put("id", 48);
+        values.put("name", "John Doe");
+        final Record record = new MapRecord(schema, values);
+
+        assertEquals("John Doe", 
RecordPath.compile("/name[contains(substringAfter(., 'o'), 
'h')]").evaluate(record).getSelectedFields().findFirst().get().getValue());
+    }
+
+
+
+    @Test
+    public void testMatchesRegex() {
+        final List<RecordField> fields = new ArrayList<>();
+        fields.add(new RecordField("id", RecordFieldType.INT.getDataType()));
+        fields.add(new RecordField("name", 
RecordFieldType.STRING.getDataType()));
+
+        final RecordSchema schema = new SimpleRecordSchema(fields);
+
+        final Map<String, Object> values = new HashMap<>();
+        values.put("id", 48);
+        values.put("name", "John Doe");
+        final Record record = new MapRecord(schema, values);
+
+        assertEquals(0L, RecordPath.compile("/name[matchesRegex(., 'John 
D')]").evaluate(record).getSelectedFields().count());
+        assertEquals("John Doe", RecordPath.compile("/name[matchesRegex(., 
'[John 
Doe]{8}')]").evaluate(record).getSelectedFields().findFirst().get().getValue());
+    }
+
+    @Test
+    public void testReplace() {
+        final List<RecordField> fields = new ArrayList<>();
+        fields.add(new RecordField("id", RecordFieldType.INT.getDataType()));
+        fields.add(new RecordField("name", 
RecordFieldType.STRING.getDataType()));
+
+        final RecordSchema schema = new SimpleRecordSchema(fields);
+
+        final Map<String, Object> values = new HashMap<>();
+        values.put("id", 48);
+        values.put("name", "John Doe");
+        final Record record = new MapRecord(schema, values);
+
+        assertEquals("John Doe", RecordPath.compile("/name[replace(../id, 48, 
18) = 18]").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals(0L, RecordPath.compile("/name[replace(../id, 48, 18) = 
48]").evaluate(record).getSelectedFields().count());
+
+        assertEquals("Jane Doe", RecordPath.compile("replace(/name, 'ohn', 
'ane')").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals("John Doe", RecordPath.compile("replace(/name, 'ohnny', 
'ane')").evaluate(record).getSelectedFields().findFirst().get().getValue());
+
+        assertEquals("John 48", RecordPath.compile("replace(/name, 'Doe', 
/id)").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals("23", RecordPath.compile("replace(/id, 48, 
23)").evaluate(record).getSelectedFields().findFirst().get().getValue());
+    }
+
+    @Test
+    public void testReplaceRegex() {
+        final List<RecordField> fields = new ArrayList<>();
+        fields.add(new RecordField("id", RecordFieldType.INT.getDataType()));
+        fields.add(new RecordField("name", 
RecordFieldType.STRING.getDataType()));
+
+        final RecordSchema schema = new SimpleRecordSchema(fields);
+
+        final Map<String, Object> values = new HashMap<>();
+        values.put("id", 48);
+        values.put("name", "John Doe");
+        final Record record = new MapRecord(schema, values);
+
+        assertEquals("ohn oe", RecordPath.compile("replaceRegex(/name, '[JD]', 
'')").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals("John Doe", RecordPath.compile("replaceRegex(/name, 
'ohnny', 
'ane')").evaluate(record).getSelectedFields().findFirst().get().getValue());
+
+        assertEquals("11", RecordPath.compile("replaceRegex(/id, '[0-9]', 
1)").evaluate(record).getSelectedFields().findFirst().get().getValue());
+
+        assertEquals("Jxohn Dxoe", RecordPath.compile("replaceRegex(/name, 
'([JD])', 
'$1x')").evaluate(record).getSelectedFields().findFirst().get().getValue());
+
+        assertEquals("Jxohn Dxoe", RecordPath.compile("replaceRegex(/name, 
'(?<hello>[JD])', 
'${hello}x')").evaluate(record).getSelectedFields().findFirst().get().getValue());
+
+        assertEquals("48ohn 48oe", RecordPath.compile("replaceRegex(/name, 
'(?<hello>[JD])', 
/id)").evaluate(record).getSelectedFields().findFirst().get().getValue());
+    }
+
+    @Test
+    public void testReplaceNull() {
+        final List<RecordField> fields = new ArrayList<>();
+        fields.add(new RecordField("id", RecordFieldType.INT.getDataType()));
+        fields.add(new RecordField("name", 
RecordFieldType.STRING.getDataType()));
+        fields.add(new RecordField("missing", 
RecordFieldType.LONG.getDataType()));
+
+        final RecordSchema schema = new SimpleRecordSchema(fields);
+
+        final Map<String, Object> values = new HashMap<>();
+        values.put("id", 48);
+        values.put("name", "John Doe");
+        final Record record = new MapRecord(schema, values);
+
+        assertEquals(48, RecordPath.compile("replaceNull(/missing, 
/id)").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals(14, RecordPath.compile("replaceNull(/missing, 
14)").evaluate(record).getSelectedFields().findFirst().get().getValue());
+        assertEquals(48, RecordPath.compile("replaceNull(/id, 
14)").evaluate(record).getSelectedFields().findFirst().get().getValue());
+    }
+
+    @Test
+    public void testConcat() {
+        final List<RecordField> fields = new ArrayList<>();
+        fields.add(new RecordField("fullName", 
RecordFieldType.INT.getDataType()));
+        fields.add(new RecordField("lastName", 
RecordFieldType.STRING.getDataType()));
+        fields.add(new RecordField("firstName", 
RecordFieldType.LONG.getDataType()));
+
+        final RecordSchema schema = new SimpleRecordSchema(fields);
+
+        final Map<String, Object> values = new HashMap<>();
+        values.put("lastName", "Doe");
+        values.put("firstName", "John");
+        final Record record = new MapRecord(schema, values);
+
+        assertEquals("John Doe: 48", RecordPath.compile("concat(/firstName, ' 
', /lastName, ': ', 
48)").evaluate(record).getSelectedFields().findFirst().get().getValue());
+    }
+
     private List<RecordField> getDefaultFields() {
         final List<RecordField> fields = new ArrayList<>();
         fields.add(new RecordField("id", RecordFieldType.INT.getDataType()));
@@ -738,4 +1023,19 @@ public class TestRecordPath {
         return accountSchema;
     }
 
+    private Record createSimpleRecord() {
+        final List<RecordField> fields = new ArrayList<>();
+        fields.add(new RecordField("id", RecordFieldType.INT.getDataType()));
+        fields.add(new RecordField("name", 
RecordFieldType.STRING.getDataType()));
+        fields.add(new RecordField("missing", 
RecordFieldType.STRING.getDataType()));
+
+        final RecordSchema schema = new SimpleRecordSchema(fields);
+
+        final Map<String, Object> values = new HashMap<>();
+        values.put("id", 48);
+        values.put("name", "John Doe");
+        final Record record = new MapRecord(schema, values);
+        return record;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/32314d70/nifi-docs/src/main/asciidoc/record-path-guide.adoc
----------------------------------------------------------------------
diff --git a/nifi-docs/src/main/asciidoc/record-path-guide.adoc 
b/nifi-docs/src/main/asciidoc/record-path-guide.adoc
index d38a5d3..c5ba3ff 100644
--- a/nifi-docs/src/main/asciidoc/record-path-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/record-path-guide.adoc
@@ -157,6 +157,15 @@ when we reference an Array field and want to only 
reference some of the elements
 specific entries in the Map; or when we want to reference a Record only if it 
adheres to some criteria. We can accomplish this by providing our criteria to 
the
 RecordPath within square brackets (using the `[` and `]` characters). We will 
go over each of these cases below.
 
+[[function_usage]]
+== Function Usage
+
+In addition to retrieving a field from a Record, as outlined above in the 
<<filters>> section, we sometimes need to refine which fields we want to 
select. Or we
+may want to return a modified version of a field. To do this, we rely on 
functions. The syntax for a function is <function name> <open parenthesis> 
<args> <close parenthesis>,
+where <args> represents one or more arguments separated by commas. An argument 
may be a string literal (such as `'hello'`) or a number literal (such as `48`), 
or could be
+a relative or absolute RecordPath (such as `./name` or `/id`). Additionally, 
we can use functions within a filter. For example, we could use a RecordPath 
such as
+`/person[ isEmpty('name') ]/id` to retrieve the `id` field of any person whose 
name is empty. A listing of functions that are available and their 
corresponding documentation
+can be found below in the <<functions>> section.
 
 [[arrays]]
 === Arrays
@@ -291,3 +300,246 @@ value of the `preferredState` field.
 Additionally, we can write a RecordPath that references the "city" field of 
any record whose state is "NJ" by using the parent operator (`..`): 
`/*/city[../state = 'NJ']`.
 
 
+
+[[functions]]
+== Functions
+
+In the <<function_usage>> section above, we describe how and why to use a 
function in RecordPath. Here, we will describe the different functions that are 
available,
+what they do, and how they work. Functions can be divided into two groups: 
<<standalone_functions>>, which can be the 'root' of a RecordPath, such as 
`substringAfter( /name, ' ' )`
+and <<filter_functions>>, which are to be used as a filter, such as `/name[ 
contains('John') ]`. A Standalone Function can also be used within a filter but 
does not return a `boolean`
+(`true` or `false` value) and therefore cannot itself be an entire filter. For 
example, we can use a path such as `/name[ substringAfter(., ' ') = 'Doe']` but 
we cannot simply use
+`/name[ substringAfter(., ' ') ]` because doing so doesn't really make sense, 
as filters must be boolean values.
+
+Unless otherwise noted, all of the examples below are written to operate on 
the following Record:
+
+----
+{
+       "name": "John Doe",
+       "workAddress": {
+               "number": "123",
+               "street": "5th Avenue",
+               "city": "New York",
+               "state": "NY",
+               "zip": "10020"
+       },
+       "homeAddress": {
+               "number": "456",
+               "street": "Grand St",
+               "city": "Jersey City",
+               "state": "NJ",
+               "zip": "07304"
+       },
+       "details": {
+               "position": "Dataflow Engineer",
+               "preferredState": "NY",
+               "employer": "",
+               "vehicle": null,
+               "phrase": "   "
+       }
+}
+----
+
+
+[[standalone_functions]]
+== Standalone Functions
+
+=== substring
+
+The substring function returns a portion of a String value. The function 
requires 3 arguments: The value to take a portion of, the 0-based start index 
(inclusive),
+and the 0-based end index (exclusive). The start index and end index can be 
`0` to indicate the first character of a String, a positive integer to indicate 
the nth index
+into the string, or a negative integer. If the value is a negative integer, 
say `-n`, then this represents the `n`th character for the end. A value of `-1` 
indicates the last
+character in the String. So, for example, `substring( 'hello world', 0, -1 )` 
means to take the string `hello`, and return characters 0 through the last 
character, so the return
+value will be `hello world`.
+
+|==========================================================
+| RecordPath | Return value
+| `substring( /name, 0, -1 )` | John Doe
+| `substring( /name, 0, -5 )` | John
+| `substring( /name, 1000, 1005 )` | <empty string>
+| `substring( /name, 0, 1005)` | John Doe
+| `substring( /name, -50, -1)` | <empty string>
+|==========================================================
+
+
+
+=== substringAfter
+
+Returns the portion of a String value that occurs after the first occurrence 
of some other value.
+
+|==========================================================
+| RecordPath | Return value
+| `substringAfter( /name, ' ' )` | Doe
+| `substringAfter( /name, 'o' )` | hn Doe
+| `substringAfter( /name, '' )` | John Doe
+| `substringAfter( /name, 'xyz' )` | John Doe
+|==========================================================
+
+
+=== substringAfterLast
+
+Returns the portion of a String value that occurs after the last occurrence of 
some other value.
+
+|==========================================================
+| RecordPath | Return value
+| `substringAfterLast( /name, ' ' )` | Doe
+| `substringAfterLast( /name, 'o' )` | e
+| `substringAfterLast( /name, '' )` | John Doe
+| `substringAfterLast( /name, 'xyz' )` | John Doe
+|==========================================================
+
+
+
+=== substringBefore
+
+Returns the portion of a String value that occurs before the first occurrence 
of some other value.
+
+|==========================================================
+| RecordPath | Return value
+| `substringBefore( /name, ' ' )` | John
+| `substringBefore( /name, 'o' )` | J
+| `substringBefore( /name, '' )` | John Doe
+| `substringBefore( /name, 'xyz' )` | John Doe
+|==========================================================
+
+
+
+=== substringBeforeLast
+
+Returns the portion of a String value that occurs before the last occurrence 
of some other value.
+
+|==========================================================
+| RecordPath | Return value
+| `substringBeforeLast( /name, ' ' )` | John
+| `substringBeforeLast( /name, 'o' )` | John D
+| `substringBeforeLast( /name, '' )` | John Doe
+| `substringBeforeLast( /name, 'xyz' )` | John Doe
+|==========================================================
+
+
+
+=== replace
+
+Replaces all occurrences of a String with another String.
+
+|==========================================================
+| RecordPath | Return value
+| `replace( /name, 'o', 'x' )` | Jxhn Dxe
+| `replace( /name, 'o', 'xyz' )` | Jxyzhn Dxyze
+| `replace( /name, 'xyz', 'zyx' )` | John Doe
+| `replace( /name, 'Doe', /workAddress/city )` | John New York
+|==========================================================
+
+
+
+=== replaceRegex
+
+Evaluates a Regular Expression against the contents of a String value and 
replaces any match with another value.
+This function requires 3 arguments: the String to run the regular expression 
against, the regular expression to run,
+and the replacement value. The replacement value may optionally use 
back-references, such as `$1` and `${named_group}`
+
+|==================================================================
+| RecordPath | Return value
+| `replaceRegex( /name, 'o', 'x' )` | Jxhn Dxe
+| `replaceRegex( /name, 'o', 'xyz' )` | Jxyzhn Dxyze
+| `replaceRegex( /name, 'xyz', 'zyx' )` | John Doe
+| `replaceRegex( /name, '\s+.*', /workAddress/city )` | John New York
+| `replaceRegex(/name, '([JD])', '$1x')` | Jxohn Dxoe
+| `replaceRegex(/name, '(?<hello>[JD])', '${hello}x')` | Jxohn Dxoe 
+|==================================================================
+
+
+
+[[filter_functions]]
+== Filter Functions
+
+=== contains
+
+Returns `true` if a String value contains the provided substring, `false` 
otherwise
+
+|==============================================================================
+| RecordPath | Return value
+| `/name[contains(., 'o')]` | John Doe
+| `/name[contains(., 'x')]` | <returns no results>
+| `/name[contains( ../workAddress/state, /details/preferredState )]` | John Doe
+|==============================================================================
+
+
+
+=== matchesRegex
+
+Evaluates a Regular Expression against the contents of a String value and 
returns `true` if the Regular Expression
+exactly matches the String value, `false` otherwise.
+This function requires 2 arguments: the String to run the regular expression 
against, and the regular expression to run.
+
+|==============================================================================
+| RecordPath | Return value
+| `/name[matchesRegex(., 'John Doe')]` | John Doe
+| `/name[matchesRegex(., 'John')]` | <returns no results>
+| `/name[matchesRegex(., '.* Doe' )]` | John Doe
+|==============================================================================
+
+
+
+=== startsWith
+
+Returns `true` if a String value starts with the provided substring, `false` 
otherwise
+
+|==============================================================================
+| RecordPath | Return value
+| `/name[startsWith(., 'J')]` | John Doe
+| `/name[startsWith(., 'x')]` | <returns no results>
+| `/name[startsWith(., 'xyz')]` | <returns no results>
+| `/name[startsWith(., '')]` | John Doe
+|==============================================================================
+
+
+=== endsWith
+
+Returns `true` if a String value ends with the provided substring, `false` 
otherwise
+
+|==============================================================================
+| RecordPath | Return value
+| `/name[endsWith(., 'e')]` | John Doe
+| `/name[endsWith(., 'x')]` | <returns no results>
+| `/name[endsWith(., 'xyz')]` | <returns no results>
+| `/name[endsWith(., '')]` | John Doe
+|==============================================================================
+
+
+=== not
+
+Inverts the value of the function or expression that is passed into the `not` 
function.
+
+|==============================================================================
+| RecordPath | Return value
+| `/name[not(endsWith(., 'x'))]` | John Doe
+| `/name[not(contains(., 'x'))]` | John Doe
+| `/name[not(endsWith(., 'e'))]` | <returns no results>
+|==============================================================================
+
+
+=== isEmpty
+
+Returns `true` if the provided value is either null or is an empty string.
+
+|==============================================================================
+| RecordPath | Return value
+| `/name[isEmpty(/details/employer)]` | John Doe
+| `/name[isEmpty(/details/vehicle)]` | John Doe
+| `/name[isEmpty(/details/phase)]` | <returns no results>
+| `/name[isEmpty(.)]` | <returns no results>
+|==============================================================================
+
+
+=== isBlank
+
+Returns `true` if the provided value is either null or is an empty string or a 
string that consists
+only of white space (spaces, tabs, carriage returns, and new-line characters).
+
+|==============================================================================
+| RecordPath | Return value
+| `/name[isBlank(/details/employer)]` | John Doe
+| `/name[isBlank(/details/vehicle)]` | John Doe
+| `/name[isBlank(/details/phase)]` | John Doe
+| `/name[isBlank(.)]` | <returns no results>
+|==============================================================================

http://git-wip-us.apache.org/repos/asf/nifi/blob/32314d70/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/UpdateRecord.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/UpdateRecord.java
 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/UpdateRecord.java
index 6acc789..abe29a2 100644
--- 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/UpdateRecord.java
+++ 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/UpdateRecord.java
@@ -188,7 +188,7 @@ public class UpdateRecord extends AbstractRecordProcessor {
 
     private void processRelativePath(final RecordPath replacementRecordPath, 
final Stream<FieldValue> destinationFields, final Record record, final String 
replacementValue) {
         destinationFields.forEach(fieldVal -> {
-            final RecordPathResult replacementResult = 
replacementRecordPath.evaluate(fieldVal);
+            final RecordPathResult replacementResult = 
replacementRecordPath.evaluate(record, fieldVal);
             final Object replacementObject = 
getReplacementObject(replacementResult, replacementValue);
             fieldVal.updateValue(replacementObject);
         });

Reply via email to