http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-flowfile-packager/src/main/java/org/apache/nifi/util/FlowFileUnpackagerV3.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-flowfile-packager/src/main/java/org/apache/nifi/util/FlowFileUnpackagerV3.java
 
b/nifi-commons/nifi-flowfile-packager/src/main/java/org/apache/nifi/util/FlowFileUnpackagerV3.java
new file mode 100644
index 0000000..f937585
--- /dev/null
+++ 
b/nifi-commons/nifi-flowfile-packager/src/main/java/org/apache/nifi/util/FlowFileUnpackagerV3.java
@@ -0,0 +1,161 @@
+/*
+ * 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.util;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+public class FlowFileUnpackagerV3 implements FlowFileUnpackager {
+
+    private byte[] nextHeader = null;
+    private boolean haveReadSomething = false;
+    private final byte readBuffer[] = new byte[8192];
+
+    @Override
+    public boolean hasMoreData() throws IOException {
+        return nextHeader != null || !haveReadSomething;
+    }
+
+    private byte[] readHeader(final InputStream in) throws IOException {
+        final byte[] header = new byte[FlowFilePackagerV3.MAGIC_HEADER.length];
+        for (int i = 0; i < header.length; i++) {
+            final int next = in.read();
+            if (next < 0) {
+                if (i == 0) {
+                    return null;
+                }
+
+                throw new IOException("Not in FlowFile-v3 format");
+            }
+            header[i] = (byte) (next & 0xFF);
+        }
+
+        return header;
+    }
+
+    @Override
+    public Map<String, String> unpackageFlowFile(final InputStream in, final 
OutputStream out) throws IOException {
+        final byte[] header = (nextHeader == null) ? readHeader(in) : 
nextHeader;
+        if (!Arrays.equals(header, FlowFilePackagerV3.MAGIC_HEADER)) {
+            throw new IOException("Not in FlowFile-v3 format");
+        }
+
+        final Map<String, String> attributes = readAttributes(in);
+        final long expectedNumBytes = readLong(in); // read length of payload
+        copy(in, out, expectedNumBytes); // read payload
+
+        nextHeader = readHeader(in);
+        haveReadSomething = true;
+
+        return attributes;
+    }
+
+    protected Map<String, String> readAttributes(final InputStream in) throws 
IOException {
+        final Map<String, String> attributes = new HashMap<>();
+        final Integer numAttributes = readFieldLength(in); //read number of 
attributes
+        if (numAttributes == null) {
+            return null;
+        }
+        if (numAttributes == 0) {
+            throw new IOException("flow files cannot have zero attributes");
+        }
+        for (int i = 0; i < numAttributes; i++) { //read each attribute 
key/value pair
+            final String key = readString(in);
+            final String value = readString(in);
+            attributes.put(key, value);
+        }
+
+        return attributes;
+    }
+
+    protected String readString(final InputStream in) throws IOException {
+        final Integer numBytes = readFieldLength(in);
+        if (numBytes == null) {
+            throw new EOFException();
+        }
+        final byte[] bytes = new byte[numBytes];
+        fillBuffer(in, bytes, numBytes);
+        return new String(bytes, "UTF-8");
+    }
+
+    private void fillBuffer(final InputStream in, final byte[] buffer, final 
int length) throws IOException {
+        int bytesRead;
+        int totalBytesRead = 0;
+        while ((bytesRead = in.read(buffer, totalBytesRead, length - 
totalBytesRead)) > 0) {
+            totalBytesRead += bytesRead;
+        }
+        if (totalBytesRead != length) {
+            throw new EOFException();
+        }
+    }
+
+    protected long copy(final InputStream in, final OutputStream out, final 
long numBytes) throws IOException {
+        int bytesRead;
+        long totalBytesRead = 0L;
+        while ((bytesRead = in.read(readBuffer, 0, (int) 
Math.min(readBuffer.length, numBytes - totalBytesRead))) > 0) {
+            out.write(readBuffer, 0, bytesRead);
+            totalBytesRead += bytesRead;
+        }
+
+        if (totalBytesRead < numBytes) {
+            throw new EOFException("Expected " + numBytes + " but received " + 
totalBytesRead);
+        }
+
+        return totalBytesRead;
+    }
+
+    protected long readLong(final InputStream in) throws IOException {
+        fillBuffer(in, readBuffer, 8);
+        return (((long) readBuffer[0] << 56)
+                + ((long) (readBuffer[1] & 255) << 48)
+                + ((long) (readBuffer[2] & 255) << 40)
+                + ((long) (readBuffer[3] & 255) << 32)
+                + ((long) (readBuffer[4] & 255) << 24)
+                + ((readBuffer[5] & 255) << 16)
+                + ((readBuffer[6] & 255) << 8)
+                + ((readBuffer[7] & 255)));
+    }
+
+    private Integer readFieldLength(final InputStream in) throws IOException {
+        final int firstValue = in.read();
+        final int secondValue = in.read();
+        if (firstValue < 0) {
+            return null;
+        }
+        if (secondValue < 0) {
+            throw new EOFException();
+        }
+        if (firstValue == 0xff && secondValue == 0xff) {
+            int ch1 = in.read();
+            int ch2 = in.read();
+            int ch3 = in.read();
+            int ch4 = in.read();
+            if ((ch1 | ch2 | ch3 | ch4) < 0) {
+                throw new EOFException();
+            }
+            return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4));
+        } else {
+            return ((firstValue << 8) + (secondValue));
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-flowfile-packager/src/main/java/org/apache/nifi/util/Unpackage.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-flowfile-packager/src/main/java/org/apache/nifi/util/Unpackage.java
 
b/nifi-commons/nifi-flowfile-packager/src/main/java/org/apache/nifi/util/Unpackage.java
new file mode 100644
index 0000000..19f702c
--- /dev/null
+++ 
b/nifi-commons/nifi-flowfile-packager/src/main/java/org/apache/nifi/util/Unpackage.java
@@ -0,0 +1,119 @@
+/*
+ * 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.util;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Map;
+import java.util.UUID;
+
+public class Unpackage {
+
+    private static void printUsage() {
+        System.out.println("java " + Unpackage.class.getCanonicalName() + " 
<version> <input file 1> [<input file 2> <input file 3> ... <input file N>]");
+        System.out.println("<version> : The version of the FlowFile Package 
format. Valid values are 1, 2, 3");
+        System.out.println("<input file X> : The FlowFile package to unpack");
+        System.out.println();
+    }
+
+    public static void main(final String[] args) throws IOException {
+        if (args.length < 2) {
+            printUsage();
+            return;
+        }
+
+        final String version = args[0];
+
+        int inputFileCount = 0;
+        int outputFileCount = 0;
+
+        for (int i = 1; i < args.length; i++) {
+            final String filename = args[i];
+            final File inFile = new File(filename);
+
+            if (inFile.isDirectory()) {
+                System.out.println("WARNING: input file " + inFile + " is a 
directory; skipping");
+                continue;
+            }
+
+            if (!inFile.exists() || !inFile.canRead()) {
+                System.out.println("ERROR: unable to read file " + inFile);
+                continue;
+            }
+
+            final File outputDir = new File(inFile.getAbsolutePath() + 
".unpacked");
+            if (!outputDir.exists() && !outputDir.mkdir()) {
+                System.out.println("ERROR: Unable to create directory " + 
outputDir);
+                continue;
+            }
+
+            final File tempFile = new File(outputDir, ".temp." + 
UUID.randomUUID().toString() + ".unpackage");
+            inputFileCount++;
+            try (final FileInputStream fis = new FileInputStream(inFile);
+                    final BufferedInputStream bufferedIn = new 
BufferedInputStream(fis)) {
+
+                final FlowFileUnpackager unpackager = 
createUnpackager(version);
+                while (unpackager.hasMoreData()) {
+                    outputFileCount++;
+                    final Map<String, String> attributes;
+
+                    try (final FileOutputStream fos = new 
FileOutputStream(tempFile);
+                            final BufferedOutputStream bufferedOut = new 
BufferedOutputStream(fos)) {
+                        attributes = unpackager.unpackageFlowFile(bufferedIn, 
bufferedOut);
+                    }
+
+                    String outputFilename = attributes.get("filename");
+                    if (outputFilename == null) {
+                        outputFilename = attributes.get("nf.file.name");
+                    }
+
+                    final File outputFile = new File(outputDir, 
outputFilename);
+                    tempFile.renameTo(outputFile);
+
+                    final File attributeFilename = new File(outputDir, 
outputFilename + ".attributes");
+                    try (final FileOutputStream fos = new 
FileOutputStream(attributeFilename);
+                            final BufferedOutputStream bufferedOut = new 
BufferedOutputStream(fos)) {
+
+                        for (final Map.Entry<String, String> entry : 
attributes.entrySet()) {
+                            bufferedOut.write((entry.getKey() + "=" + 
entry.getValue() + "\n").getBytes("UTF-8"));
+                        }
+                    }
+                }
+            }
+        }
+
+        System.out.println("Unpacked " + inputFileCount + " packages into " + 
outputFileCount + " files");
+    }
+
+    public static FlowFileUnpackager createUnpackager(final String version) {
+        switch (version) {
+            case "1":
+                return new FlowFileUnpackagerV1();
+            case "2":
+                return new FlowFileUnpackagerV2();
+            case "3":
+                return new FlowFileUnpackagerV3();
+            default:
+                System.out.println("ERROR: Invalid version: " + version + "; 
must be 1, 2, or 3");
+                return null;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-flowfile-packager/src/test/java/org/apache/nifi/util/TestPackageUnpackageV3.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-flowfile-packager/src/test/java/org/apache/nifi/util/TestPackageUnpackageV3.java
 
b/nifi-commons/nifi-flowfile-packager/src/test/java/org/apache/nifi/util/TestPackageUnpackageV3.java
new file mode 100644
index 0000000..24cd374
--- /dev/null
+++ 
b/nifi-commons/nifi-flowfile-packager/src/test/java/org/apache/nifi/util/TestPackageUnpackageV3.java
@@ -0,0 +1,56 @@
+/*
+ * 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.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Test;
+
+public class TestPackageUnpackageV3 {
+
+    @Test
+    public void test() throws IOException {
+        final FlowFilePackager packager = new FlowFilePackagerV3();
+        final FlowFileUnpackager unpackager = new FlowFileUnpackagerV3();
+
+        final byte[] data = "Hello, World!".getBytes("UTF-8");
+        final Map<String, String> map = new HashMap<>();
+        map.put("abc", "cba");
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        final ByteArrayInputStream in = new ByteArrayInputStream(data);
+        packager.packageFlowFile(in, baos, map, data.length);
+
+        final byte[] encoded = baos.toByteArray();
+        final ByteArrayInputStream encodedIn = new 
ByteArrayInputStream(encoded);
+        final ByteArrayOutputStream decodedOut = new ByteArrayOutputStream();
+        final Map<String, String> unpackagedAttributes = 
unpackager.unpackageFlowFile(encodedIn, decodedOut);
+        final byte[] decoded = decodedOut.toByteArray();
+
+        assertEquals(map, unpackagedAttributes);
+        assertTrue(Arrays.equals(data, decoded));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-hl7-query-language/.gitignore
----------------------------------------------------------------------
diff --git a/nifi-commons/nifi-hl7-query-language/.gitignore 
b/nifi-commons/nifi-hl7-query-language/.gitignore
new file mode 100644
index 0000000..e91d5c4
--- /dev/null
+++ b/nifi-commons/nifi-hl7-query-language/.gitignore
@@ -0,0 +1,3 @@
+/target/
+/target/
+/target/

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-hl7-query-language/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-commons/nifi-hl7-query-language/pom.xml 
b/nifi-commons/nifi-hl7-query-language/pom.xml
new file mode 100644
index 0000000..8b04e5c
--- /dev/null
+++ b/nifi-commons/nifi-hl7-query-language/pom.xml
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.nifi</groupId>
+        <artifactId>nifi-commons</artifactId>
+        <version>0.3.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>nifi-hl7-query-language</artifactId>
+    <packaging>jar</packaging>
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.7</source>
+                    <target>1.7</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.antlr</groupId>
+                <artifactId>antlr3-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>antlr</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <configuration>
+                    <excludes combine.children="append">
+                        <exclude>src/test/resources/hypoglycemia</exclude>
+                        <exclude>src/test/resources/hyperglycemia</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-checkstyle-plugin</artifactId>
+                <configuration>
+                    
<excludes>**/HL7QueryParser.java,**/HL7QueryLexer.java</excludes>
+                </configuration>
+            </plugin>            
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.antlr</groupId>
+            <artifactId>antlr-runtime</artifactId>
+        </dependency>
+       
+        <!-- HAPI to parse v2 messages -->
+        <dependency>
+            <groupId>ca.uhn.hapi</groupId>
+            <artifactId>hapi-base</artifactId>
+            <version>2.2</version>
+        </dependency>
+        <dependency>
+            <groupId>ca.uhn.hapi</groupId>
+            <artifactId>hapi-structures-v21</artifactId>
+            <version>2.2</version>
+        </dependency>
+        <dependency>
+            <groupId>ca.uhn.hapi</groupId>
+            <artifactId>hapi-structures-v22</artifactId>
+            <version>2.2</version>
+        </dependency>
+        <dependency>
+            <groupId>ca.uhn.hapi</groupId>
+            <artifactId>hapi-structures-v23</artifactId>
+            <version>2.2</version>
+        </dependency>
+        <dependency>
+            <groupId>ca.uhn.hapi</groupId>
+            <artifactId>hapi-structures-v231</artifactId>
+            <version>2.2</version>
+        </dependency>
+        <dependency>
+            <groupId>ca.uhn.hapi</groupId>
+            <artifactId>hapi-structures-v24</artifactId>
+            <version>2.2</version>
+        </dependency>
+        <dependency>
+            <groupId>ca.uhn.hapi</groupId>
+            <artifactId>hapi-structures-v25</artifactId>
+            <version>2.2</version>
+        </dependency>
+        <dependency>
+            <groupId>ca.uhn.hapi</groupId>
+            <artifactId>hapi-structures-v251</artifactId>
+            <version>2.2</version>
+        </dependency>
+        <dependency>
+            <groupId>ca.uhn.hapi</groupId>
+            <artifactId>hapi-structures-v26</artifactId>
+            <version>2.2</version>
+        </dependency>
+
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-hl7-query-language/src/main/antlr3/org/apache/nifi/hl7/query/antlr/HL7QueryLexer.g
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-hl7-query-language/src/main/antlr3/org/apache/nifi/hl7/query/antlr/HL7QueryLexer.g
 
b/nifi-commons/nifi-hl7-query-language/src/main/antlr3/org/apache/nifi/hl7/query/antlr/HL7QueryLexer.g
new file mode 100644
index 0000000..478028b
--- /dev/null
+++ 
b/nifi-commons/nifi-hl7-query-language/src/main/antlr3/org/apache/nifi/hl7/query/antlr/HL7QueryLexer.g
@@ -0,0 +1,173 @@
+/*
+ * 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.
+ */
+
+lexer grammar HL7QueryLexer;
+
+@header {
+       package org.apache.nifi.hl7.query.antlr;
+       import org.apache.nifi.hl7.query.exception.HL7QueryParsingException;
+}
+
+@rulecatch {
+  catch(final Exception e) {
+    throw new HL7QueryParsingException(e);
+  }
+}
+
+@members {
+  public void displayRecognitionError(String[] tokenNames, 
RecognitionException e) {
+    final StringBuilder sb = new StringBuilder();
+    if ( e.token == null ) {
+       sb.append("Unrecognized token ");
+    } else {
+       sb.append("Unexpected token '").append(e.token.getText()).append("' ");
+    }
+    sb.append("at line ").append(e.line);
+    if ( e.approximateLineInfo ) {
+       sb.append(" (approximately)");
+    }
+    sb.append(", column ").append(e.charPositionInLine);
+    sb.append(". Query: ").append(e.input.toString());
+    
+    throw new HL7QueryParsingException(sb.toString());
+  }
+
+  public void recover(RecognitionException e) {
+       final StringBuilder sb = new StringBuilder();
+    if ( e.token == null ) {
+       sb.append("Unrecognized token ");
+    } else {
+       sb.append("Unexpected token '").append(e.token.getText()).append("' ");
+    }
+    sb.append("at line ").append(e.line);
+    if ( e.approximateLineInfo ) {
+       sb.append(" (approximately)");
+    }
+    sb.append(", column ").append(e.charPositionInLine);
+    sb.append(". Query: ").append(e.input.toString());
+    
+    throw new HL7QueryParsingException(sb.toString());
+  } 
+}
+
+
+// PUNCTUATION & SPECIAL CHARACTERS
+WHITESPACE : (' '|'\t'|'\n'|'\r')+ { $channel = HIDDEN; };
+COMMENT : '#' ( ~('\n') )* '\n' { $channel = HIDDEN; };
+
+LPAREN : '(';
+RPAREN : ')';
+LBRACE  : '{';
+RBRACE  : '}';
+COLON  : ':';
+COMMA  : ',';
+DOT            : '.';
+SEMICOLON : ';';
+
+
+
+// OPERATORS
+EQUALS         : '=';
+NOT_EQUALS     : '!=';
+GT                     : '>';
+GE                     : '>=';
+LT                     : '<';
+LE                     : '<=';
+REGEX          : 'MATCHES REGEX';
+LIKE           : 'LIKE';
+IS_NULL                : 'IS NULL';
+NOT_NULL       : 'NOT NULL';
+
+
+// KEYWORDS
+AND                    : 'AND';
+OR                     : 'OR';
+NOT                    : 'NOT';
+
+TRUE   : 'true';
+FALSE  : 'false';
+
+SELECT         : 'select' | 'SELECT';
+DECLARE                : 'declare' | 'DECLARE';
+OPTIONAL       : 'optional' | 'OPTIONAL';
+REQUIRED       : 'required' | 'REQUIRED';
+AS                     : 'as' | 'AS';
+WHERE          : 'where' | 'WHERE';
+
+MESSAGE        : 'MESSAGE' | 'message';
+SEGMENT        : 'SEGMENT' | 'segment';
+
+
+SEGMENT_NAME : LETTER ALPHA_NUMERIC ALPHA_NUMERIC;
+
+
+NUMBER : ('0'..'9')+;
+fragment LETTER : 'A'..'Z';
+fragment ALPHA_NUMERIC : 'A'..'Z' | '0'..'9';
+
+
+// STRINGS
+STRING_LITERAL
+@init{StringBuilder lBuf = new StringBuilder();}
+       :
+               (
+                       '"'
+                               (
+                                       escaped=ESC {lBuf.append(getText());} |
+                                       normal = ~( '"' | '\\' | '\n' | '\r' | 
'\t' ) { lBuf.appendCodePoint(normal);} 
+                               )*
+                       '"'
+               )
+               {
+                       setText(lBuf.toString());
+               }
+               |
+               (
+                       '\''
+                               (
+                                       escaped=ESC {lBuf.append(getText());} |
+                                       normal = ~( '\'' | '\\' | '\n' | '\r' | 
'\t' ) { lBuf.appendCodePoint(normal);} 
+                               )*
+                       '\''
+               )
+               {
+                       setText(lBuf.toString());
+               }
+               ;
+
+
+fragment
+ESC
+       :       '\\'
+               (
+                               '"'             { setText("\""); }
+                       |       '\''    { setText("\'"); }
+                       |       'r'             { setText("\r"); }
+                       |       'n'             { setText("\n"); }
+                       |       't'             { setText("\t"); }
+                       |       '\\'    { setText("\\\\"); }
+                       |       nextChar = ~('"' | '\'' | 'r' | 'n' | 't' | 
'\\')               
+                               {
+                                       StringBuilder lBuf = new 
StringBuilder(); lBuf.append("\\\\").appendCodePoint(nextChar); 
setText(lBuf.toString());
+                               }
+               )
+       ;
+
+IDENTIFIER : (
+                                 ~('$' | '{' | '}' | '(' | ')' | '[' | ']' | 
',' | ':' | ';' | '/' | '*' | '\'' | ' ' | '\t' | '\r' | '\n' | '0'..'9' | '.')
+                                 ~('$' | '{' | '}' | '(' | ')' | '[' | ']' | 
',' | ':' | ';' | '/' | '*' | '\'' | ' ' | '\t' | '\r' | '\n' | '.')*
+                                );

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-hl7-query-language/src/main/antlr3/org/apache/nifi/hl7/query/antlr/HL7QueryParser.g
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-hl7-query-language/src/main/antlr3/org/apache/nifi/hl7/query/antlr/HL7QueryParser.g
 
b/nifi-commons/nifi-hl7-query-language/src/main/antlr3/org/apache/nifi/hl7/query/antlr/HL7QueryParser.g
new file mode 100644
index 0000000..f051872
--- /dev/null
+++ 
b/nifi-commons/nifi-hl7-query-language/src/main/antlr3/org/apache/nifi/hl7/query/antlr/HL7QueryParser.g
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ */
+
+parser grammar HL7QueryParser;
+
+options {
+       output=AST;
+       tokenVocab=HL7QueryLexer;
+}
+
+tokens {
+       QUERY;
+       DECLARATION;
+}
+
+@header {
+       package org.apache.nifi.hl7.query.antlr;
+       import org.apache.nifi.hl7.query.exception.HL7QueryParsingException;
+}
+
+@members {
+  public void displayRecognitionError(String[] tokenNames, 
RecognitionException e) {
+       final StringBuilder sb = new StringBuilder();
+    if ( e.token == null ) {
+       sb.append("Unrecognized token ");
+    } else {
+       sb.append("Unexpected token '").append(e.token.getText()).append("' ");
+    }
+    sb.append("at line ").append(e.line);
+    if ( e.approximateLineInfo ) {
+       sb.append(" (approximately)");
+    }
+    sb.append(", column ").append(e.charPositionInLine);
+    sb.append(". Query: ").append(e.input.toString());
+    
+    throw new HL7QueryParsingException(sb.toString());
+  }
+
+  public void recover(final RecognitionException e) {
+       final StringBuilder sb = new StringBuilder();
+    if ( e.token == null ) {
+       sb.append("Unrecognized token ");
+    } else {
+       sb.append("Unexpected token '").append(e.token.getText()).append("' ");
+    }
+    sb.append("at line ").append(e.line);
+    if ( e.approximateLineInfo ) {
+       sb.append(" (approximately)");
+    }
+    sb.append(", column ").append(e.charPositionInLine);
+    sb.append(". Query: ").append(e.input.toString());
+    
+    throw new HL7QueryParsingException(sb.toString());
+  } 
+}
+
+
+declareClause : DECLARE^ declaration (COMMA! declaration)*;
+
+requiredOrOptional : REQUIRED | OPTIONAL;
+declaration : IDENTIFIER AS requiredOrOptional SEGMENT_NAME ->
+       ^(DECLARATION IDENTIFIER requiredOrOptional SEGMENT_NAME);
+
+
+selectClause : SELECT^ selectableClause;
+selectableClause : selectable (COMMA! selectable)*;
+selectable : (MESSAGE | ref | field)^ (AS! IDENTIFIER^)?;
+
+
+whereClause : WHERE^ conditions;
+
+conditions : condition ((AND^ | OR^) condition)*;
+
+condition : NOT^ condition | LPAREN! conditions RPAREN! | evaluation;
+
+evaluation : expression
+                        (
+                               unaryOperator^
+                               | (binaryOperator^ expression)
+                        );
+
+expression : (LPAREN! expr RPAREN!) | expr;
+expr : ref | field | STRING_LITERAL | NUMBER;
+
+unaryOperator : IS_NULL | NOT_NULL;
+binaryOperator : EQUALS | NOT_EQUALS | LT | GT | LE | GE;
+
+ref : (SEGMENT_NAME | IDENTIFIER);
+field : ref DOT^ NUMBER 
+       (DOT^ NUMBER (DOT^ NUMBER (DOT^ NUMBER)?)?)?;
+
+
+query : declareClause? selectClause whereClause? EOF ->
+       ^(QUERY declareClause? selectClause whereClause?);

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/hapi/EmptyField.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/hapi/EmptyField.java
 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/hapi/EmptyField.java
new file mode 100644
index 0000000..dc73efb
--- /dev/null
+++ 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/hapi/EmptyField.java
@@ -0,0 +1,37 @@
+/*
+ * 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.hl7.hapi;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.nifi.hl7.model.HL7Component;
+import org.apache.nifi.hl7.model.HL7Field;
+
+public class EmptyField implements HL7Field {
+
+    @Override
+    public String getValue() {
+        return null;
+    }
+
+    @Override
+    public List<HL7Component> getComponents() {
+        return Collections.emptyList();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/hapi/HapiField.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/hapi/HapiField.java
 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/hapi/HapiField.java
new file mode 100644
index 0000000..94cce5c
--- /dev/null
+++ 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/hapi/HapiField.java
@@ -0,0 +1,84 @@
+/*
+ * 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.hl7.hapi;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.nifi.hl7.model.HL7Component;
+import org.apache.nifi.hl7.model.HL7Field;
+
+import ca.uhn.hl7v2.model.Composite;
+import ca.uhn.hl7v2.model.ExtraComponents;
+import ca.uhn.hl7v2.model.Primitive;
+import ca.uhn.hl7v2.model.Type;
+import ca.uhn.hl7v2.model.Varies;
+import ca.uhn.hl7v2.parser.EncodingCharacters;
+import ca.uhn.hl7v2.parser.PipeParser;
+
+public class HapiField implements HL7Field, HL7Component {
+
+    private final String value;
+    private final List<HL7Component> components;
+
+    public HapiField(final Type type) {
+        this.value = PipeParser.encode(type, 
EncodingCharacters.defaultInstance());
+
+        final List<HL7Component> componentList = new ArrayList<>();
+        if (type instanceof Composite) {
+            final Composite composite = (Composite) type;
+
+            for (final Type component : composite.getComponents()) {
+                componentList.add(new HapiField(component));
+            }
+        }
+
+        final ExtraComponents extra = type.getExtraComponents();
+        if (extra != null && extra.numComponents() > 0) {
+            final String singleFieldValue;
+            if (type instanceof Primitive) {
+                singleFieldValue = ((Primitive) type).getValue();
+            } else {
+                singleFieldValue = this.value;
+            }
+            componentList.add(new SingleValueField(singleFieldValue));
+
+            for (int i = 0; i < extra.numComponents(); i++) {
+                final Varies varies = extra.getComponent(i);
+                componentList.add(new HapiField(varies));
+            }
+        }
+
+        this.components = Collections.unmodifiableList(componentList);
+    }
+
+    @Override
+    public String getValue() {
+        return value;
+    }
+
+    @Override
+    public List<HL7Component> getComponents() {
+        return components;
+    }
+
+    @Override
+    public String toString() {
+        return value;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/hapi/HapiMessage.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/hapi/HapiMessage.java
 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/hapi/HapiMessage.java
new file mode 100644
index 0000000..74a8f6c
--- /dev/null
+++ 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/hapi/HapiMessage.java
@@ -0,0 +1,95 @@
+/*
+ * 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.hl7.hapi;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.nifi.hl7.model.HL7Message;
+import org.apache.nifi.hl7.model.HL7Segment;
+
+import ca.uhn.hl7v2.HL7Exception;
+import ca.uhn.hl7v2.model.Group;
+import ca.uhn.hl7v2.model.Message;
+import ca.uhn.hl7v2.model.Segment;
+import ca.uhn.hl7v2.model.Structure;
+
+public class HapiMessage implements HL7Message {
+
+    private final Message message;
+    private final List<HL7Segment> allSegments;
+    private final Map<String, List<HL7Segment>> segmentMap;
+
+    public HapiMessage(final Message message) throws HL7Exception {
+        this.message = message;
+
+        allSegments = new ArrayList<>();
+        populateSegments(message, allSegments);
+
+        segmentMap = new HashMap<>();
+        for (final HL7Segment segment : allSegments) {
+            final String segmentName = segment.getName();
+            List<HL7Segment> segmentList = segmentMap.get(segmentName);
+            if (segmentList == null) {
+                segmentList = new ArrayList<>();
+                segmentMap.put(segmentName, segmentList);
+            }
+
+            segmentList.add(segment);
+        }
+    }
+
+    private void populateSegments(final Group group, final List<HL7Segment> 
segments) throws HL7Exception {
+        for (final String structureName : group.getNames()) {
+            final Structure[] structures = group.getAll(structureName);
+            if (group.isGroup(structureName)) {
+                for (final Structure structure : structures) {
+                    populateSegments((Group) structure, segments);
+                }
+            } else {
+                for (final Structure structure : structures) {
+                    final Segment segment = (Segment) structure;
+                    final HapiSegment hapiSegment = new HapiSegment(segment);
+                    segments.add(hapiSegment);
+                }
+            }
+        }
+    }
+
+    @Override
+    public List<HL7Segment> getSegments() {
+        return Collections.unmodifiableList(allSegments);
+    }
+
+    @Override
+    public List<HL7Segment> getSegments(final String segmentType) {
+        final List<HL7Segment> segments = segmentMap.get(segmentType);
+        if (segments == null) {
+            return Collections.emptyList();
+        }
+
+        return Collections.unmodifiableList(segments);
+    }
+
+    @Override
+    public String toString() {
+        return message.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/hapi/HapiSegment.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/hapi/HapiSegment.java
 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/hapi/HapiSegment.java
new file mode 100644
index 0000000..20e646c
--- /dev/null
+++ 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/hapi/HapiSegment.java
@@ -0,0 +1,69 @@
+/*
+ * 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.hl7.hapi;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.nifi.hl7.model.HL7Field;
+import org.apache.nifi.hl7.model.HL7Segment;
+
+import ca.uhn.hl7v2.HL7Exception;
+import ca.uhn.hl7v2.model.Segment;
+import ca.uhn.hl7v2.model.Type;
+
+public class HapiSegment implements HL7Segment {
+
+    private final Segment segment;
+    private final List<HL7Field> fields;
+
+    public HapiSegment(final Segment segment) throws HL7Exception {
+        this.segment = segment;
+
+        final List<HL7Field> fieldList = new ArrayList<>();
+        for (int i = 1; i <= segment.numFields(); i++) {
+            final Type[] types = segment.getField(i);
+
+            if (types == null || types.length == 0) {
+                fieldList.add(new EmptyField());
+                continue;
+            }
+
+            for (final Type type : types) {
+                fieldList.add(new HapiField(type));
+            }
+        }
+
+        this.fields = Collections.unmodifiableList(fieldList);
+    }
+
+    @Override
+    public String getName() {
+        return segment.getName();
+    }
+
+    @Override
+    public List<HL7Field> getFields() {
+        return fields;
+    }
+
+    @Override
+    public String toString() {
+        return segment.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/hapi/SingleValueField.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/hapi/SingleValueField.java
 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/hapi/SingleValueField.java
new file mode 100644
index 0000000..b8a6351
--- /dev/null
+++ 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/hapi/SingleValueField.java
@@ -0,0 +1,43 @@
+/*
+ * 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.hl7.hapi;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.nifi.hl7.model.HL7Component;
+import org.apache.nifi.hl7.model.HL7Field;
+
+public class SingleValueField implements HL7Field {
+
+    private final String value;
+
+    public SingleValueField(final String value) {
+        this.value = value;
+    }
+
+    @Override
+    public String getValue() {
+        return value;
+    }
+
+    @Override
+    public List<HL7Component> getComponents() {
+        return Collections.emptyList();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/io/HL7Reader.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/io/HL7Reader.java
 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/io/HL7Reader.java
new file mode 100644
index 0000000..4d2f276
--- /dev/null
+++ 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/io/HL7Reader.java
@@ -0,0 +1,27 @@
+/*
+ * 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.hl7.io;
+
+import java.io.IOException;
+
+import org.apache.nifi.hl7.model.HL7Message;
+
+public interface HL7Reader {
+
+    HL7Message nextMessage() throws IOException;
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/io/exception/InvalidHL7Exception.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/io/exception/InvalidHL7Exception.java
 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/io/exception/InvalidHL7Exception.java
new file mode 100644
index 0000000..c0c74f9
--- /dev/null
+++ 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/io/exception/InvalidHL7Exception.java
@@ -0,0 +1,41 @@
+/*
+ * 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.hl7.io.exception;
+
+import java.io.IOException;
+
+public class InvalidHL7Exception extends IOException {
+
+    private static final long serialVersionUID = -5675416667224562441L;
+
+    public InvalidHL7Exception() {
+        super();
+    }
+
+    public InvalidHL7Exception(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public InvalidHL7Exception(String message) {
+        super(message);
+    }
+
+    public InvalidHL7Exception(Throwable cause) {
+        super(cause);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/model/HL7Component.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/model/HL7Component.java
 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/model/HL7Component.java
new file mode 100644
index 0000000..d8c1495
--- /dev/null
+++ 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/model/HL7Component.java
@@ -0,0 +1,26 @@
+/*
+ * 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.hl7.model;
+
+import java.util.List;
+
+public interface HL7Component {
+
+    String getValue();
+
+    List<HL7Component> getComponents();
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/model/HL7Field.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/model/HL7Field.java
 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/model/HL7Field.java
new file mode 100644
index 0000000..ff58d96
--- /dev/null
+++ 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/model/HL7Field.java
@@ -0,0 +1,20 @@
+/*
+ * 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.hl7.model;
+
+public interface HL7Field extends HL7Component {
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/model/HL7Message.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/model/HL7Message.java
 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/model/HL7Message.java
new file mode 100644
index 0000000..dc68c2b
--- /dev/null
+++ 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/model/HL7Message.java
@@ -0,0 +1,27 @@
+/*
+ * 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.hl7.model;
+
+import java.util.List;
+
+public interface HL7Message {
+
+    List<HL7Segment> getSegments();
+
+    List<HL7Segment> getSegments(String segmentType);
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/model/HL7Segment.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/model/HL7Segment.java
 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/model/HL7Segment.java
new file mode 100644
index 0000000..d5d67ae
--- /dev/null
+++ 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/model/HL7Segment.java
@@ -0,0 +1,27 @@
+/*
+ * 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.hl7.model;
+
+import java.util.List;
+
+public interface HL7Segment {
+
+    String getName();
+
+    List<HL7Field> getFields();
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/Declaration.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/Declaration.java
 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/Declaration.java
new file mode 100644
index 0000000..94998e7
--- /dev/null
+++ 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/Declaration.java
@@ -0,0 +1,29 @@
+/*
+ * 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.hl7.query;
+
+import org.apache.nifi.hl7.model.HL7Message;
+
+public interface Declaration {
+
+    String getAlias();
+
+    boolean isRequired();
+
+    Object getDeclaredValue(HL7Message message);
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/HL7Query.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/HL7Query.java
 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/HL7Query.java
new file mode 100644
index 0000000..3734b4f
--- /dev/null
+++ 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/HL7Query.java
@@ -0,0 +1,384 @@
+/*
+ * 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.hl7.query;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.antlr.runtime.ANTLRStringStream;
+import org.antlr.runtime.CharStream;
+import org.antlr.runtime.CommonTokenStream;
+import org.antlr.runtime.tree.Tree;
+import org.apache.nifi.hl7.model.HL7Message;
+import org.apache.nifi.hl7.query.evaluator.BooleanEvaluator;
+import org.apache.nifi.hl7.query.evaluator.Evaluator;
+import org.apache.nifi.hl7.query.evaluator.IntegerEvaluator;
+import org.apache.nifi.hl7.query.evaluator.comparison.EqualsEvaluator;
+import org.apache.nifi.hl7.query.evaluator.comparison.GreaterThanEvaluator;
+import 
org.apache.nifi.hl7.query.evaluator.comparison.GreaterThanOrEqualEvaluator;
+import org.apache.nifi.hl7.query.evaluator.comparison.IsNullEvaluator;
+import org.apache.nifi.hl7.query.evaluator.comparison.LessThanEvaluator;
+import org.apache.nifi.hl7.query.evaluator.comparison.LessThanOrEqualEvaluator;
+import org.apache.nifi.hl7.query.evaluator.comparison.NotEqualsEvaluator;
+import org.apache.nifi.hl7.query.evaluator.comparison.NotEvaluator;
+import org.apache.nifi.hl7.query.evaluator.comparison.NotNullEvaluator;
+import org.apache.nifi.hl7.query.evaluator.literal.IntegerLiteralEvaluator;
+import org.apache.nifi.hl7.query.evaluator.literal.StringLiteralEvaluator;
+import org.apache.nifi.hl7.query.evaluator.logic.AndEvaluator;
+import org.apache.nifi.hl7.query.evaluator.logic.OrEvaluator;
+import org.apache.nifi.hl7.query.evaluator.message.DeclaredReferenceEvaluator;
+import org.apache.nifi.hl7.query.evaluator.message.DotEvaluator;
+import org.apache.nifi.hl7.query.evaluator.message.MessageEvaluator;
+import org.apache.nifi.hl7.query.evaluator.message.SegmentEvaluator;
+import org.apache.nifi.hl7.query.exception.HL7QueryParsingException;
+import org.apache.nifi.hl7.query.result.MissedResult;
+import org.apache.nifi.hl7.query.result.StandardQueryResult;
+
+import org.apache.nifi.hl7.query.antlr.HL7QueryLexer;
+import org.apache.nifi.hl7.query.antlr.HL7QueryParser;
+import static org.apache.nifi.hl7.query.antlr.HL7QueryParser.AND;
+import static org.apache.nifi.hl7.query.antlr.HL7QueryParser.DECLARE;
+import static org.apache.nifi.hl7.query.antlr.HL7QueryParser.DOT;
+import static org.apache.nifi.hl7.query.antlr.HL7QueryParser.EQUALS;
+import static org.apache.nifi.hl7.query.antlr.HL7QueryParser.GE;
+import static org.apache.nifi.hl7.query.antlr.HL7QueryParser.GT;
+import static org.apache.nifi.hl7.query.antlr.HL7QueryParser.IDENTIFIER;
+import static org.apache.nifi.hl7.query.antlr.HL7QueryParser.IS_NULL;
+import static org.apache.nifi.hl7.query.antlr.HL7QueryParser.LE;
+import static org.apache.nifi.hl7.query.antlr.HL7QueryParser.LT;
+import static org.apache.nifi.hl7.query.antlr.HL7QueryParser.MESSAGE;
+import static org.apache.nifi.hl7.query.antlr.HL7QueryParser.NOT;
+import static org.apache.nifi.hl7.query.antlr.HL7QueryParser.NOT_EQUALS;
+import static org.apache.nifi.hl7.query.antlr.HL7QueryParser.NOT_NULL;
+import static org.apache.nifi.hl7.query.antlr.HL7QueryParser.NUMBER;
+import static org.apache.nifi.hl7.query.antlr.HL7QueryParser.OR;
+import static org.apache.nifi.hl7.query.antlr.HL7QueryParser.REQUIRED;
+import static org.apache.nifi.hl7.query.antlr.HL7QueryParser.SEGMENT_NAME;
+import static org.apache.nifi.hl7.query.antlr.HL7QueryParser.SELECT;
+import static org.apache.nifi.hl7.query.antlr.HL7QueryParser.STRING_LITERAL;
+import static org.apache.nifi.hl7.query.antlr.HL7QueryParser.WHERE;
+
+public class HL7Query {
+
+    private final Tree tree;
+    private final String query;
+    private final Set<Declaration> declarations = new HashSet<>();
+
+    private final List<Selection> selections;
+    private final BooleanEvaluator whereEvaluator;
+
+    private HL7Query(final Tree tree, final String query) {
+        this.tree = tree;
+        this.query = query;
+
+        List<Selection> select = null;
+        BooleanEvaluator where = null;
+        for (int i = 0; i < tree.getChildCount(); i++) {
+            final Tree child = tree.getChild(i);
+
+            switch (child.getType()) {
+                case DECLARE:
+                    processDeclare(child);
+                    break;
+                case SELECT:
+                    select = processSelect(child);
+                    break;
+                case WHERE:
+                    where = processWhere(child);
+                    break;
+                default:
+                    throw new HL7QueryParsingException("Found unexpected 
clause at root level: " + tree.getText());
+            }
+        }
+
+        this.whereEvaluator = where;
+        this.selections = select;
+    }
+
+    private void processDeclare(final Tree declare) {
+        for (int i = 0; i < declare.getChildCount(); i++) {
+            final Tree declarationTree = declare.getChild(i);
+
+            final String identifier = declarationTree.getChild(0).getText();
+            final Tree requiredOrOptionalTree = declarationTree.getChild(1);
+            final boolean required = requiredOrOptionalTree.getType() == 
REQUIRED;
+
+            final String segmentName = declarationTree.getChild(2).getText();
+
+            final Declaration declaration = new Declaration() {
+                @Override
+                public String getAlias() {
+                    return identifier;
+                }
+
+                @Override
+                public boolean isRequired() {
+                    return required;
+                }
+
+                @Override
+                public Object getDeclaredValue(final HL7Message message) {
+                    if (message == null) {
+                        return null;
+                    }
+
+                    return message.getSegments(segmentName);
+                }
+            };
+
+            declarations.add(declaration);
+        }
+    }
+
+    private List<Selection> processSelect(final Tree select) {
+        final List<Selection> selections = new ArrayList<>();
+
+        for (int i = 0; i < select.getChildCount(); i++) {
+            final Tree selectable = select.getChild(i);
+
+            final String alias = getSelectedName(selectable);
+            final Evaluator<?> selectionEvaluator = 
buildReferenceEvaluator(selectable);
+            final Selection selection = new Selection(selectionEvaluator, 
alias);
+            selections.add(selection);
+        }
+
+        return selections;
+    }
+
+    private String getSelectedName(final Tree selectable) {
+        if (selectable.getChildCount() == 0) {
+            return selectable.getText();
+        } else if (selectable.getType() == DOT) {
+            return getSelectedName(selectable.getChild(0)) + "." + 
getSelectedName(selectable.getChild(1));
+        } else {
+            return selectable.getChild(selectable.getChildCount() - 
1).getText();
+        }
+    }
+
+    private BooleanEvaluator processWhere(final Tree where) {
+        return buildBooleanEvaluator(where.getChild(0));
+    }
+
+    private Evaluator<?> buildReferenceEvaluator(final Tree tree) {
+        switch (tree.getType()) {
+            case MESSAGE:
+                return new MessageEvaluator();
+            case SEGMENT_NAME:
+                return new SegmentEvaluator(new 
StringLiteralEvaluator(tree.getText()));
+            case IDENTIFIER:
+                return new DeclaredReferenceEvaluator(new 
StringLiteralEvaluator(tree.getText()));
+            case DOT:
+                final Tree firstChild = tree.getChild(0);
+                final Tree secondChild = tree.getChild(1);
+                return new DotEvaluator(buildReferenceEvaluator(firstChild), 
buildIntegerEvaluator(secondChild));
+            case STRING_LITERAL:
+                return new StringLiteralEvaluator(tree.getText());
+            case NUMBER:
+                return new 
IntegerLiteralEvaluator(Integer.parseInt(tree.getText()));
+            default:
+                throw new HL7QueryParsingException("Failed to build evaluator 
for " + tree.getText());
+        }
+    }
+
+    private IntegerEvaluator buildIntegerEvaluator(final Tree tree) {
+        switch (tree.getType()) {
+            case NUMBER:
+                return new 
IntegerLiteralEvaluator(Integer.parseInt(tree.getText()));
+            default:
+                throw new HL7QueryParsingException("Failed to build Integer 
Evaluator for " + tree.getText());
+        }
+    }
+
+    private BooleanEvaluator buildBooleanEvaluator(final Tree tree) {
+        // TODO: add Date comparisons
+        // LT/GT/GE/GE should allow for dates based on Field's Type
+        // BETWEEN
+        // DATE('2015/01/01')
+        // DATE('2015/01/01 12:00:00')
+        // DATE('24 HOURS AGO')
+        // DATE('YESTERDAY')
+
+        switch (tree.getType()) {
+            case EQUALS:
+                return new 
EqualsEvaluator(buildReferenceEvaluator(tree.getChild(0)), 
buildReferenceEvaluator(tree.getChild(1)));
+            case NOT_EQUALS:
+                return new 
NotEqualsEvaluator(buildReferenceEvaluator(tree.getChild(0)), 
buildReferenceEvaluator(tree.getChild(1)));
+            case GT:
+                return new 
GreaterThanEvaluator(buildReferenceEvaluator(tree.getChild(0)), 
buildReferenceEvaluator(tree.getChild(1)));
+            case LT:
+                return new 
LessThanEvaluator(buildReferenceEvaluator(tree.getChild(0)), 
buildReferenceEvaluator(tree.getChild(1)));
+            case GE:
+                return new 
GreaterThanOrEqualEvaluator(buildReferenceEvaluator(tree.getChild(0)), 
buildReferenceEvaluator(tree.getChild(1)));
+            case LE:
+                return new 
LessThanOrEqualEvaluator(buildReferenceEvaluator(tree.getChild(0)), 
buildReferenceEvaluator(tree.getChild(1)));
+            case NOT:
+                return new 
NotEvaluator(buildBooleanEvaluator(tree.getChild(0)));
+            case AND:
+                return new 
AndEvaluator(buildBooleanEvaluator(tree.getChild(0)), 
buildBooleanEvaluator(tree.getChild(1)));
+            case OR:
+                return new 
OrEvaluator(buildBooleanEvaluator(tree.getChild(0)), 
buildBooleanEvaluator(tree.getChild(1)));
+            case IS_NULL:
+                return new 
IsNullEvaluator(buildReferenceEvaluator(tree.getChild(0)));
+            case NOT_NULL:
+                return new 
NotNullEvaluator(buildReferenceEvaluator(tree.getChild(0)));
+            default:
+                throw new HL7QueryParsingException("Cannot build boolean 
evaluator for '" + tree.getText() + "'");
+        }
+    }
+
+    Tree getTree() {
+        return tree;
+    }
+
+    public String getQuery() {
+        return query;
+    }
+
+    @Override
+    public String toString() {
+        return "HL7Query[" + query + "]";
+    }
+
+    public static HL7Query compile(final String query) {
+        try {
+            final CommonTokenStream lexerTokenStream = 
createTokenStream(query);
+            final HL7QueryParser parser = new HL7QueryParser(lexerTokenStream);
+            final Tree tree = (Tree) parser.query().getTree();
+
+            return new HL7Query(tree, query);
+        } catch (final HL7QueryParsingException e) {
+            throw e;
+        } catch (final Exception e) {
+            throw new HL7QueryParsingException(e);
+        }
+    }
+
+    private static CommonTokenStream createTokenStream(final String 
expression) throws HL7QueryParsingException {
+        final CharStream input = new ANTLRStringStream(expression);
+        final HL7QueryLexer lexer = new HL7QueryLexer(input);
+        return new CommonTokenStream(lexer);
+    }
+
+    public List<Class<?>> getReturnTypes() {
+        final List<Class<?>> returnTypes = new ArrayList<>();
+
+        for (final Selection selection : selections) {
+            returnTypes.add(selection.getEvaluator().getType());
+        }
+
+        return returnTypes;
+    }
+
+    @SuppressWarnings("unchecked")
+    public QueryResult evaluate(final HL7Message message) {
+
+        int totalIterations = 1;
+        final LinkedHashMap<String, List<Object>> possibleValueMap = new 
LinkedHashMap<>();
+        for (final Declaration declaration : declarations) {
+            final Object value = declaration.getDeclaredValue(message);
+            if (value == null && declaration.isRequired()) {
+                return new MissedResult(selections);
+            }
+
+            final List<Object> possibleValues;
+            if (value instanceof List) {
+                possibleValues = (List<Object>) value;
+            } else if (value instanceof Collection) {
+                possibleValues = new ArrayList<Object>((Collection<Object>) 
value);
+            } else {
+                possibleValues = new ArrayList<>(1);
+                possibleValues.add(value);
+            }
+
+            if (possibleValues.isEmpty()) {
+                return new MissedResult(selections);
+            }
+
+            possibleValueMap.put(declaration.getAlias(), possibleValues);
+            totalIterations *= possibleValues.size();
+        }
+
+        final Set<Map<String, Object>> resultSet = new HashSet<>();
+        for (int i = 0; i < totalIterations; i++) {
+            final Map<String, Object> aliasValues = 
assignAliases(possibleValueMap, i);
+
+            aliasValues.put(Evaluator.MESSAGE_KEY, message);
+            if (whereEvaluator == null || 
Boolean.TRUE.equals(whereEvaluator.evaluate(aliasValues))) {
+                final Map<String, Object> resultMap = new HashMap<>();
+
+                for (final Selection selection : selections) {
+                    final Object value = 
selection.getEvaluator().evaluate(aliasValues);
+                    resultMap.put(selection.getName(), value);
+                }
+
+                resultSet.add(resultMap);
+            }
+        }
+
+        return new StandardQueryResult(selections, resultSet);
+    }
+
+    /**
+     * assigns one of the possible values to each alias, based on which 
iteration this is.
+     * require LinkedHashMap just to be very clear and explicit that the order 
of the Map MUST be guaranteed
+     * between multiple invocations of this method.
+     * package protected for testing visibility
+     */
+    static Map<String, Object> assignAliases(final LinkedHashMap<String, 
List<Object>> possibleValues, final int iteration) {
+        final Map<String, Object> aliasMap = new HashMap<>();
+
+        int divisor = 1;
+        for (final Map.Entry<String, List<Object>> entry : 
possibleValues.entrySet()) {
+            final String alias = entry.getKey();
+            final List<Object> validValues = entry.getValue();
+
+            final int idx = (iteration / divisor) % validValues.size();
+            final Object obj = validValues.get(idx);
+            aliasMap.put(alias, obj);
+
+            divisor *= validValues.size();
+        }
+
+        return aliasMap;
+    }
+
+    public String toTreeString() {
+        final StringBuilder sb = new StringBuilder();
+        toTreeString(tree, sb, 0);
+        return sb.toString();
+    }
+
+    private void toTreeString(final Tree tree, final StringBuilder sb, final 
int indentLevel) {
+        final String nodeName = tree.getText();
+        for (int i = 0; i < indentLevel; i++) {
+            sb.append(" ");
+        }
+        sb.append(nodeName);
+        sb.append("\n");
+
+        for (int i = 0; i < tree.getChildCount(); i++) {
+            final Tree child = tree.getChild(i);
+            toTreeString(child, sb, indentLevel + 2);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/QueryResult.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/QueryResult.java
 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/QueryResult.java
new file mode 100644
index 0000000..033ac0a
--- /dev/null
+++ 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/QueryResult.java
@@ -0,0 +1,30 @@
+/*
+ * 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.hl7.query;
+
+import java.util.List;
+
+public interface QueryResult {
+
+    boolean isMatch();
+
+    List<String> getLabels();
+
+    int getHitCount();
+
+    ResultHit nextHit();
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/ResultHit.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/ResultHit.java
 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/ResultHit.java
new file mode 100644
index 0000000..9eb1d8f
--- /dev/null
+++ 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/ResultHit.java
@@ -0,0 +1,26 @@
+/*
+ * 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.hl7.query;
+
+import java.util.Map;
+
+public interface ResultHit {
+
+    Object getValue(String label);
+
+    Map<String, Object> getSelectedValues();
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/Selection.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/Selection.java
 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/Selection.java
new file mode 100644
index 0000000..7b5b7b3
--- /dev/null
+++ 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/Selection.java
@@ -0,0 +1,38 @@
+/*
+ * 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.hl7.query;
+
+import org.apache.nifi.hl7.query.evaluator.Evaluator;
+
+public class Selection {
+
+    private final Evaluator<?> evaluator;
+    private final String name;
+
+    public Selection(final Evaluator<?> evaluator, final String name) {
+        this.evaluator = evaluator;
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public Evaluator<?> getEvaluator() {
+        return evaluator;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/evaluator/BooleanEvaluator.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/evaluator/BooleanEvaluator.java
 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/evaluator/BooleanEvaluator.java
new file mode 100644
index 0000000..98b077e
--- /dev/null
+++ 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/evaluator/BooleanEvaluator.java
@@ -0,0 +1,24 @@
+/*
+ * 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.hl7.query.evaluator;
+
+public abstract class BooleanEvaluator implements Evaluator<Boolean> {
+
+    public Class<? extends Boolean> getType() {
+        return Boolean.class;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/evaluator/Evaluator.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/evaluator/Evaluator.java
 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/evaluator/Evaluator.java
new file mode 100644
index 0000000..2d1ddc7
--- /dev/null
+++ 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/evaluator/Evaluator.java
@@ -0,0 +1,28 @@
+/*
+ * 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.hl7.query.evaluator;
+
+import java.util.Map;
+
+public interface Evaluator<T> {
+
+    public static final String MESSAGE_KEY = "message";
+
+    T evaluate(Map<String, Object> objectMap);
+
+    Class<? extends T> getType();
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/evaluator/IntegerEvaluator.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/evaluator/IntegerEvaluator.java
 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/evaluator/IntegerEvaluator.java
new file mode 100644
index 0000000..ed56d80
--- /dev/null
+++ 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/evaluator/IntegerEvaluator.java
@@ -0,0 +1,25 @@
+/*
+ * 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.hl7.query.evaluator;
+
+public abstract class IntegerEvaluator implements Evaluator<Integer> {
+
+    public Class<? extends Integer> getType() {
+        return Integer.class;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/evaluator/StringEvaluator.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/evaluator/StringEvaluator.java
 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/evaluator/StringEvaluator.java
new file mode 100644
index 0000000..53ab004
--- /dev/null
+++ 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/evaluator/StringEvaluator.java
@@ -0,0 +1,25 @@
+/*
+ * 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.hl7.query.evaluator;
+
+public abstract class StringEvaluator implements Evaluator<String> {
+
+    public Class<? extends String> getType() {
+        return String.class;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/evaluator/comparison/AbstractComparisonEvaluator.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/evaluator/comparison/AbstractComparisonEvaluator.java
 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/evaluator/comparison/AbstractComparisonEvaluator.java
new file mode 100644
index 0000000..45edf64
--- /dev/null
+++ 
b/nifi-commons/nifi-hl7-query-language/src/main/java/org/apache/nifi/hl7/query/evaluator/comparison/AbstractComparisonEvaluator.java
@@ -0,0 +1,111 @@
+/*
+ * 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.hl7.query.evaluator.comparison;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.nifi.hl7.model.HL7Field;
+import org.apache.nifi.hl7.query.evaluator.BooleanEvaluator;
+import org.apache.nifi.hl7.query.evaluator.Evaluator;
+
+public abstract class AbstractComparisonEvaluator extends BooleanEvaluator {
+
+    private final Evaluator<?> lhs;
+    private final Evaluator<?> rhs;
+
+    public AbstractComparisonEvaluator(final Evaluator<?> lhs, final 
Evaluator<?> rhs) {
+        this.lhs = lhs;
+        this.rhs = rhs;
+    }
+
+    @Override
+    public final Boolean evaluate(final Map<String, Object> objectMap) {
+        final Object lhsValue = lhs.evaluate(objectMap);
+        if (lhsValue == null) {
+            return false;
+        }
+
+        final Object rhsValue = rhs.evaluate(objectMap);
+        if (rhsValue == null) {
+            return false;
+        }
+
+        return compareRaw(lhsValue, rhsValue);
+    }
+
+    private Boolean compareRaw(Object lhsValue, Object rhsValue) {
+        if (lhsValue == null || rhsValue == null) {
+            return false;
+        }
+
+        if (lhsValue instanceof HL7Field) {
+            lhsValue = ((HL7Field) lhsValue).getValue();
+        }
+
+        if (rhsValue instanceof HL7Field) {
+            rhsValue = ((HL7Field) rhsValue).getValue();
+        }
+
+        if (lhsValue == null || rhsValue == null) {
+            return false;
+        }
+
+        /**
+         * both are collections, and compare(lhsValue, rhsValue) is false.
+         * this would be the case, for instance, if we compared field 1 of one 
segment to
+         * a field in another segment, and both fields had components.
+         */
+        if (lhsValue instanceof Collection && rhsValue instanceof Collection) {
+            return false;
+        }
+
+        /**
+         * if one side is a collection but the other is not, check if any 
element in that
+         * collection compares to the other element in a way that satisfies 
the condition.
+         * this would happen, for instance, if we check Segment1.Field5 = 'X' 
and field 5 repeats
+         * with a value "A~B~C~X~Y~Z"; in this case we do want to consider 
Field 5 = X as true.
+         */
+        if (lhsValue instanceof Collection) {
+            for (final Object lhsObject : (Collection<?>) lhsValue) {
+                if (compareRaw(lhsObject, rhsValue)) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        if (rhsValue instanceof Collection) {
+            for (final Object rhsObject : (Collection<?>) rhsValue) {
+                if (compareRaw(rhsObject, lhsValue)) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        if (lhsValue != null && rhsValue != null && compare(lhsValue, 
rhsValue)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    protected abstract boolean compare(Object lhs, Object rhs);
+}

Reply via email to