diff --git a/nifi-assembly/pom.xml b/nifi-assembly/pom.xml index 070bebaa3..4a2babb6f 100755 --- a/nifi-assembly/pom.xml +++ b/nifi-assembly/pom.xml @@ -1,15 +1,16 @@ - - + + 4.0.0 org.apache.nifi @@ -403,6 +404,11 @@ language governing permissions and limitations under the License. --> nifi-site-to-site-reporting-nar nar + + org.apache.nifi + nifi-record-serialization-services-nar + nar + org.apache.nifi nifi-mqtt-nar @@ -513,13 +519,17 @@ language governing permissions and limitations under the License. --> nifi Apache NiFi - Apache NiFi is dataflow system based on the Flow-Based Programming concepts. - Apache License, Version 2.0 and others (see included LICENSE file) + Apache NiFi is dataflow system + based on the Flow-Based Programming + concepts. + Apache License, Version 2.0 and + others (see included LICENSE file) http://nifi.apache.org Utilities /opt/nifi - _use_internal_dependency_generator 0 + _use_internal_dependency_generator + 0 750 640 @@ -536,7 +546,13 @@ language governing permissions and limitations under the License. --> @@ -602,10 +618,12 @@ language governing permissions and limitations under the License. --> /opt/nifi/nifi-${project.version}/lib - + /opt/nifi/nifi-${project.version}/lib @@ -636,7 +654,8 @@ language governing permissions and limitations under the License. --> org.apache.nifi:nifi-security-utils org.apache.nifi:nifi-utils - + org.apache.nifi:nifi-resources org.apache.nifi:nifi-docs diff --git a/nifi-mock/src/main/java/org/apache/nifi/util/MockProcessSession.java b/nifi-mock/src/main/java/org/apache/nifi/util/MockProcessSession.java index faf6e4298..7dd971476 100644 --- a/nifi-mock/src/main/java/org/apache/nifi/util/MockProcessSession.java +++ b/nifi-mock/src/main/java/org/apache/nifi/util/MockProcessSession.java @@ -218,8 +218,8 @@ public class MockProcessSession implements ProcessSession { } } - throw new FlowFileHandlingException("Cannot commit session because the following Input Streams were created via " - + "calls to ProcessSession.read(FlowFile) and never closed: " + openStreamCopy); + // throw new FlowFileHandlingException("Cannot commit session because the following Input Streams were created via " + // + "calls to ProcessSession.read(FlowFile) and never closed: " + openStreamCopy); } committed = true; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/NodeResponse.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/NodeResponse.java index 7c911b87f..73dd92f9e 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/NodeResponse.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/NodeResponse.java @@ -239,7 +239,7 @@ public class NodeResponse { // if no client response was created, then generate a 500 response if (hasThrowable()) { - return Response.status(Status.INTERNAL_SERVER_ERROR).build(); + return Response.status(Status.INTERNAL_SERVER_ERROR).entity(getThrowable().toString()).build(); } // set the status diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/StandardProcessSession.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/StandardProcessSession.java index fe99fb3aa..3a51816da 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/StandardProcessSession.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/StandardProcessSession.java @@ -2157,10 +2157,11 @@ public final class StandardProcessSession implements ProcessSession, ProvenanceE final InputStream rawIn = getInputStream(source, record.getCurrentClaim(), record.getCurrentClaimOffset(), false); final InputStream limitedIn = new LimitedInputStream(rawIn, source.getSize()); - final ByteCountingInputStream countingStream = new ByteCountingInputStream(limitedIn, this.bytesRead); + final ByteCountingInputStream countingStream = new ByteCountingInputStream(limitedIn); final FlowFileAccessInputStream ffais = new FlowFileAccessInputStream(countingStream, source, record.getCurrentClaim()); final InputStream errorHandlingStream = new InputStream() { + private boolean closed = false; @Override public int read() throws IOException { @@ -2201,7 +2202,10 @@ public final class StandardProcessSession implements ProcessSession, ProvenanceE @Override public void close() throws IOException { - StandardProcessSession.this.bytesRead += countingStream.getBytesRead(); + if (!closed) { + StandardProcessSession.this.bytesRead += countingStream.getBytesRead(); + closed = true; + } ffais.close(); openInputStreams.remove(source); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/processor/SimpleProcessLogger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/processor/SimpleProcessLogger.java index cc17abc31..8e926040c 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/processor/SimpleProcessLogger.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/processor/SimpleProcessLogger.java @@ -16,11 +16,13 @@ */ package org.apache.nifi.processor; +import java.util.Arrays; + +import org.apache.commons.lang3.StringUtils; import org.apache.nifi.logging.ComponentLog; import org.apache.nifi.logging.LogLevel; import org.apache.nifi.logging.LogRepository; import org.apache.nifi.logging.LogRepositoryFactory; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,16 +49,6 @@ public class SimpleProcessLogger implements ComponentLog { return newArgs; } - private Object[] translateException(final Object[] os) { - if (os != null && os.length > 0 && (os[os.length - 1] instanceof Throwable)) { - final Object[] osCopy = new Object[os.length]; - osCopy[osCopy.length - 1] = os[os.length - 1].toString(); - System.arraycopy(os, 0, osCopy, 0, os.length - 1); - return osCopy; - } - return os; - } - private boolean lastArgIsException(final Object[] os) { return (os != null && os.length > 0 && (os[os.length - 1] instanceof Throwable)); } @@ -80,7 +72,7 @@ public class SimpleProcessLogger implements ComponentLog { } if (lastArgIsException(os)) { - warn(msg, translateException(os), (Throwable) os[os.length - 1]); + warn(msg, Arrays.copyOfRange(os, 0, os.length - 1), (Throwable) os[os.length - 1]); } else { msg = "{} " + msg; os = addProcessor(os); @@ -95,13 +87,9 @@ public class SimpleProcessLogger implements ComponentLog { return; } - os = addProcessorAndThrowable(os, t); + os = addProcessorAndThrowable(os, t, logger.isDebugEnabled()); msg = "{} " + msg + ": {}"; - logger.warn(msg, os); - if (logger.isDebugEnabled()) { - logger.warn("", t); - } logRepository.addLogMessage(LogLevel.WARN, msg, os, t); } @@ -159,11 +147,10 @@ public class SimpleProcessLogger implements ComponentLog { return; } - os = addProcessorAndThrowable(os, t); + os = addProcessorAndThrowable(os, t, true); msg = "{} " + msg + ": {}"; logger.trace(msg, os); - logger.trace("", t); logRepository.addLogMessage(LogLevel.TRACE, msg, os, t); } @@ -240,13 +227,10 @@ public class SimpleProcessLogger implements ComponentLog { return; } - os = addProcessorAndThrowable(os, t); + os = addProcessorAndThrowable(os, t, logger.isDebugEnabled()); msg = "{} " + msg + ": {}"; logger.info(msg, os); - if (logger.isDebugEnabled()) { - logger.info("", t); - } logRepository.addLogMessage(LogLevel.INFO, msg, os, t); } @@ -261,14 +245,16 @@ public class SimpleProcessLogger implements ComponentLog { return; } - msg = "{} " + msg; - Object[] os = t == null ? new Object[]{component} : new Object[]{component, t.toString()}; - logger.error(msg, os); - if (t != null){ - logger.error("", t); - logRepository.addLogMessage(LogLevel.ERROR, msg, os, t); - } else { + if (t == null) { + msg = "{} " + msg; + final Object[] os = new Object[] {component}; + logger.error(msg, os); logRepository.addLogMessage(LogLevel.ERROR, msg, os); + } else { + msg = "{} " + msg + ": {}"; + final Object[] os = new Object[] {component, t.toString(), t}; + logger.error(msg, os); + logRepository.addLogMessage(LogLevel.ERROR, msg, os, t); } } @@ -279,7 +265,7 @@ public class SimpleProcessLogger implements ComponentLog { } if (lastArgIsException(os)) { - error(msg, translateException(os), (Throwable) os[os.length - 1]); + error(msg, Arrays.copyOfRange(os, 0, os.length - 1), (Throwable) os[os.length - 1]); } else { os = addProcessor(os); msg = "{} " + msg; @@ -299,21 +285,27 @@ public class SimpleProcessLogger implements ComponentLog { return; } - os = addProcessorAndThrowable(os, t); + os = addProcessorAndThrowable(os, t, true); msg = "{} " + msg + ": {}"; logger.error(msg, os); - logger.error("", t); logRepository.addLogMessage(LogLevel.ERROR, msg, os, t); } - private Object[] addProcessorAndThrowable(final Object[] os, final Throwable t) { - final Object[] modifiedArgs = new Object[os.length + 2]; - modifiedArgs[0] = component.toString(); - for (int i = 0; i < os.length; i++) { - modifiedArgs[i + 1] = os[i]; + private Object[] addProcessorAndThrowable(final Object[] os, final Throwable t, final boolean includeStackTrace) { + final Object[] modifiedArgs; + if (t == null || !includeStackTrace) { + modifiedArgs = new Object[os.length + 2]; + modifiedArgs[0] = component.toString(); + System.arraycopy(os, 0, modifiedArgs, 1, os.length); + modifiedArgs[modifiedArgs.length - 1] = StringUtils.EMPTY; + } else { + modifiedArgs = new Object[os.length + 3]; + modifiedArgs[0] = component.toString(); + System.arraycopy(os, 0, modifiedArgs, 1, os.length); + modifiedArgs[modifiedArgs.length - 2] = t.toString(); + modifiedArgs[modifiedArgs.length - 1] = t; } - modifiedArgs[modifiedArgs.length - 1] = (t == null) ? "" : t.toString(); return modifiedArgs; } @@ -350,13 +342,10 @@ public class SimpleProcessLogger implements ComponentLog { return; } - os = addProcessorAndThrowable(os, t); + os = addProcessorAndThrowable(os, t, true); msg = "{} " + msg + ": {}"; logger.debug(msg, os); - if (logger.isDebugEnabled()) { - logger.debug("", t); - } logRepository.addLogMessage(LogLevel.DEBUG, msg, os, t); } diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-nar/src/main/resources/META-INF/NOTICE b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-nar/src/main/resources/META-INF/NOTICE index e0d1300f4..51c60800e 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-nar/src/main/resources/META-INF/NOTICE +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-nar/src/main/resources/META-INF/NOTICE @@ -178,6 +178,22 @@ The following binary components are provided under the Apache Software License v Grok Copyright 2014 Anthony Corbacho, and contributors. + (ASLv2) Apache Calcite + The following NOTICE information applies: + Apache Calcite + Copyright 2012-2017 The Apache Software Foundation + + This product includes software developed at + The Apache Software Foundation (http://www.apache.org/). + + This product is based on source code originally developed + by DynamoBI Corporation, LucidEra Inc., SQLstream Inc. and others + under the auspices of the Eigenbase Foundation + and released as the LucidDB project. + + The web site includes files generated by Jekyll. + + ************************ Common Development and Distribution License 1.1 ************************ diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml index d410f4378..e3900970c 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml @@ -1,15 +1,16 @@ - - + + 4.0.0 org.apache.nifi @@ -48,6 +49,10 @@ language governing permissions and limitations under the License. --> org.apache.nifi nifi-http-context-map-api + + org.apache.nifi + nifi-record-serialization-service-api + commons-io commons-io diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/calcite/adapter/csv/CsvEnumerator2.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/calcite/adapter/csv/CsvEnumerator2.java deleted file mode 100644 index 0f928ce35..000000000 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/calcite/adapter/csv/CsvEnumerator2.java +++ /dev/null @@ -1,303 +0,0 @@ -/* - * 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.calcite.adapter.csv; - -import java.io.IOException; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.TimeZone; - -import org.apache.calcite.adapter.java.JavaTypeFactory; -import org.apache.calcite.linq4j.Enumerator; -import org.apache.calcite.rel.type.RelDataType; -import org.apache.calcite.util.Pair; -import org.apache.commons.lang3.time.FastDateFormat; - -import au.com.bytecode.opencsv.CSVReader; - - -/** Enumerator that reads from a CSV stream. - * - * @param Row type - */ -class CsvEnumerator2 implements Enumerator { - private final CSVReader reader; - private final String[] filterValues; - private final RowConverter rowConverter; - private E current; - - private static final FastDateFormat TIME_FORMAT_DATE; - private static final FastDateFormat TIME_FORMAT_TIME; - private static final FastDateFormat TIME_FORMAT_TIMESTAMP; - - static { - TimeZone gmt = TimeZone.getTimeZone("GMT"); - TIME_FORMAT_DATE = FastDateFormat.getInstance("yyyy-MM-dd", gmt); - TIME_FORMAT_TIME = FastDateFormat.getInstance("HH:mm:ss", gmt); - TIME_FORMAT_TIMESTAMP = - FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss", gmt); - } - - public CsvEnumerator2(CSVReader csvReader, List fieldTypes) { - this(verifyNotNullReader(csvReader), fieldTypes, identityList(fieldTypes.size())); - } - - public CsvEnumerator2(CSVReader csvReader, List fieldTypes, int[] fields) { - //noinspection unchecked - this(csvReader, null, (RowConverter) converter(fieldTypes, fields)); - } - - public CsvEnumerator2(CSVReader csvReader, String[] filterValues, RowConverter rowConverter) { - this.rowConverter = rowConverter; - this.filterValues = filterValues; - this.reader = csvReader; - } - - static public CSVReader verifyNotNullReader(CSVReader csvReader) { - if (csvReader==null) - throw new IllegalArgumentException("csvReader cannot be null"); - return csvReader; - } - - private static RowConverter converter(List fieldTypes, - int[] fields) { - if (fields.length == 1) { - final int field = fields[0]; - return new SingleColumnRowConverter(fieldTypes.get(field), field); - } else { - return new ArrayRowConverter(fieldTypes, fields); - } - } - - /** Deduces the names and types of a table's columns by reading the first line - * of a CSV stream. */ - static public RelDataType deduceRowType(JavaTypeFactory typeFactory, String[] firstLine, - List fieldTypes) { - final List types = new ArrayList<>(); - final List names = new ArrayList<>(); - for (String string : firstLine) { - final String name; - final CsvFieldType fieldType; - final int colon = string.indexOf(':'); - if (colon >= 0) { - name = string.substring(0, colon); - String typeString = string.substring(colon + 1); - typeString = typeString.trim(); - fieldType = CsvFieldType.of(typeString); - if (fieldType == null) { - System.out.println("WARNING: Found unknown type: " - + typeString + " in first line: " - + " for column: " + name - + ". Will assume the type of column is string"); - } - } else { - name = string; - fieldType = null; - } - final RelDataType type; - if (fieldType == null) { - type = typeFactory.createJavaType(String.class); - } else { - type = fieldType.toType(typeFactory); - } - names.add(name); - types.add(type); - if (fieldTypes != null) { - fieldTypes.add(fieldType); - } - } - - if (names.isEmpty()) { - names.add("line"); - types.add(typeFactory.createJavaType(String.class)); - } - return typeFactory.createStructType(Pair.zip(names, types)); - } - - public E current() { - return current; - } - - public boolean moveNext() { - try { - outer: - for (;;) { - final String[] strings = reader.readNext(); - if (strings == null) { - current = null; - reader.close(); - return false; - } - if (filterValues != null) { - for (int i = 0; i < strings.length; i++) { - String filterValue = filterValues[i]; - if (filterValue != null) { - if (!filterValue.equals(strings[i])) { - continue outer; - } - } - } - } - current = rowConverter.convertRow(strings); - return true; - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public void reset() { - throw new UnsupportedOperationException(); - } - - public void close() { - try { - reader.close(); - } catch (IOException e) { - throw new RuntimeException("Error closing CSV reader", e); - } - } - - /** Returns an array of integers {0, ..., n - 1}. */ - static int[] identityList(int n) { - int[] integers = new int[n]; - for (int i = 0; i < n; i++) { - integers[i] = i; - } - return integers; - } - - /** Row converter. */ - abstract static class RowConverter { - abstract E convertRow(String[] rows); - - protected Object convert(CsvFieldType fieldType, String string) { - if (fieldType == null) { - return string; - } - switch (fieldType) { - case BOOLEAN: - if (string.length() == 0) { - return null; - } - return Boolean.parseBoolean(string); - case BYTE: - if (string.length() == 0) { - return null; - } - return Byte.parseByte(string); - case SHORT: - if (string.length() == 0) { - return null; - } - return Short.parseShort(string); - case INT: - if (string.length() == 0) { - return null; - } - return Integer.parseInt(string); - case LONG: - if (string.length() == 0) { - return null; - } - return Long.parseLong(string); - case FLOAT: - if (string.length() == 0) { - return null; - } - return Float.parseFloat(string); - case DOUBLE: - if (string.length() == 0) { - return null; - } - return Double.parseDouble(string); - case DATE: - if (string.length() == 0) { - return null; - } - try { - Date date = TIME_FORMAT_DATE.parse(string); - return new java.sql.Date(date.getTime()); - } catch (ParseException e) { - return null; - } - case TIME: - if (string.length() == 0) { - return null; - } - try { - Date date = TIME_FORMAT_TIME.parse(string); - return new java.sql.Time(date.getTime()); - } catch (ParseException e) { - return null; - } - case TIMESTAMP: - if (string.length() == 0) { - return null; - } - try { - Date date = TIME_FORMAT_TIMESTAMP.parse(string); - return new java.sql.Timestamp(date.getTime()); - } catch (ParseException e) { - return null; - } - case STRING: - default: - return string; - } - } - } - - /** Array row converter. */ - static class ArrayRowConverter extends RowConverter { - private final CsvFieldType[] fieldTypes; - private final int[] fields; - - ArrayRowConverter(List fieldTypes, int[] fields) { - this.fieldTypes = fieldTypes.toArray(new CsvFieldType[fieldTypes.size()]); - this.fields = fields; - } - - public Object[] convertRow(String[] strings) { - final Object[] objects = new Object[fields.length]; - for (int i = 0; i < fields.length; i++) { - int field = fields[i]; - objects[i] = convert(fieldTypes[field], strings[field]); - } - return objects; - } - } - - /** Single column row converter. */ - private static class SingleColumnRowConverter extends RowConverter { - private final CsvFieldType fieldType; - private final int fieldIndex; - - private SingleColumnRowConverter(CsvFieldType fieldType, int fieldIndex) { - this.fieldType = fieldType; - this.fieldIndex = fieldIndex; - } - - public Object convertRow(String[] strings) { - return convert(fieldType, strings[fieldIndex]); - } - } -} - -// End CsvEnumerator2.java diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/calcite/adapter/csv/CsvSchema2.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/calcite/adapter/csv/CsvSchema2.java deleted file mode 100644 index f724f7955..000000000 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/calcite/adapter/csv/CsvSchema2.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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.calcite.adapter.csv; - -import java.io.Reader; -import java.util.Map; - -import org.apache.calcite.schema.Table; -import org.apache.calcite.schema.impl.AbstractSchema; - -import com.google.common.collect.ImmutableMap; - -/** - * Schema mapped onto a directory of CSV files. Each table in the schema - * is a CSV file in that directory. - */ -public class CsvSchema2 extends AbstractSchema { - final private Map inputs; - private final CsvTable.Flavor flavor; - private Map tableMap; - - /** - * Creates a CSV schema. - * - * @param inputs Inputs map - * @param flavor Whether to instantiate flavor tables that undergo - * query optimization - */ - public CsvSchema2(Map inputs, CsvTable.Flavor flavor) { - super(); - this.inputs = inputs; - this.flavor = flavor; - } - - /** Looks for a suffix on a string and returns - * either the string with the suffix removed - * or the original string. */ - private static String trim(String s, String suffix) { - String trimmed = trimOrNull(s, suffix); - return trimmed != null ? trimmed : s; - } - - /** Looks for a suffix on a string and returns - * either the string with the suffix removed - * or null. */ - private static String trimOrNull(String s, String suffix) { - return s.endsWith(suffix) - ? s.substring(0, s.length() - suffix.length()) - : null; - } - - @Override protected Map getTableMap() { - - if (tableMap!=null) - return tableMap; - - // Build a map from table name to table; each file becomes a table. - final ImmutableMap.Builder builder = ImmutableMap.builder(); - - for (Map.Entry entry : inputs.entrySet()) { - final Table table = createTable(entry.getValue()); - builder.put(entry.getKey(), table); - } - - tableMap = builder.build(); - return tableMap; - } - - /** Creates different sub-type of table based on the "flavor" attribute. */ - private Table createTable(Reader readerx) { - switch (flavor) { - case TRANSLATABLE: - return new CsvTranslatableTable2(readerx, null); -// case SCANNABLE: -// return new CsvScannableTable(file, null); -// case FILTERABLE: -// return new CsvFilterableTable(file, null); - default: - throw new AssertionError("Unknown flavor " + flavor); - } - } -} - -// End CsvSchema2.java diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/calcite/adapter/csv/CsvSchemaFactory2.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/calcite/adapter/csv/CsvSchemaFactory2.java deleted file mode 100644 index f8ec5761c..000000000 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/calcite/adapter/csv/CsvSchemaFactory2.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.calcite.adapter.csv; - -import java.io.Reader; -import java.util.Map; - -import org.apache.calcite.schema.Schema; -import org.apache.calcite.schema.SchemaFactory; -import org.apache.calcite.schema.SchemaPlus; - -/** - * Factory that creates a {@link CsvSchema}. - * - *

Allows a custom schema to be included in a model.json - * file.

- */ -@SuppressWarnings("UnusedDeclaration") -public class CsvSchemaFactory2 implements SchemaFactory { - final private Map inputs; - // public constructor, per factory contract - public CsvSchemaFactory2(Map inputs) { - this.inputs = inputs; - } - - public Schema create(SchemaPlus parentSchema, String name, Map operand) { - String flavorName = (String) operand.get("flavor"); - CsvTable.Flavor flavor; - if (flavorName == null) { - flavor = CsvTable.Flavor.SCANNABLE; - } else { - flavor = CsvTable.Flavor.valueOf(flavorName.toUpperCase()); - } - - return new CsvSchema2(inputs, flavor); - } -} - -// End CsvSchemaFactory2.java diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/calcite/adapter/csv/CsvTableScan2.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/calcite/adapter/csv/CsvTableScan2.java deleted file mode 100644 index 75f013cb0..000000000 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/calcite/adapter/csv/CsvTableScan2.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * 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.calcite.adapter.csv; - -import org.apache.calcite.adapter.enumerable.EnumerableConvention; -import org.apache.calcite.adapter.enumerable.EnumerableRel; -import org.apache.calcite.adapter.enumerable.EnumerableRelImplementor; -import org.apache.calcite.adapter.enumerable.PhysType; -import org.apache.calcite.adapter.enumerable.PhysTypeImpl; -import org.apache.calcite.linq4j.tree.Blocks; -import org.apache.calcite.linq4j.tree.Expressions; -import org.apache.calcite.linq4j.tree.Primitive; -import org.apache.calcite.plan.RelOptCluster; -import org.apache.calcite.plan.RelOptPlanner; -import org.apache.calcite.plan.RelOptTable; -import org.apache.calcite.plan.RelTraitSet; -import org.apache.calcite.rel.RelNode; -import org.apache.calcite.rel.RelWriter; -import org.apache.calcite.rel.core.TableScan; -import org.apache.calcite.rel.type.RelDataType; -import org.apache.calcite.rel.type.RelDataTypeFactory; -import org.apache.calcite.rel.type.RelDataTypeField; - -import java.util.List; - -/** - * Relational expression representing a scan of a CSV stream. - * - *

Like any table scan, it serves as a leaf node of a query tree.

- */ -public class CsvTableScan2 extends TableScan implements EnumerableRel { - final CsvTranslatableTable2 csvTable; - final int[] fields; - - protected CsvTableScan2(RelOptCluster cluster, RelOptTable table, - CsvTranslatableTable2 csvTable, int[] fields) { - super(cluster, cluster.traitSetOf(EnumerableConvention.INSTANCE), table); - this.csvTable = csvTable; - this.fields = fields; - - assert csvTable != null; - } - - @Override public RelNode copy(RelTraitSet traitSet, List inputs) { - assert inputs.isEmpty(); - return new CsvTableScan2(getCluster(), table, csvTable, fields); - } - - @Override public RelWriter explainTerms(RelWriter pw) { - return super.explainTerms(pw) - .item("fields", Primitive.asList(fields)); - } - - @Override public RelDataType deriveRowType() { - final List fieldList = table.getRowType().getFieldList(); - final RelDataTypeFactory.FieldInfoBuilder builder = - getCluster().getTypeFactory().builder(); - for (int field : fields) { - builder.add(fieldList.get(field)); - } - return builder.build(); - } - - @Override public void register(RelOptPlanner planner) { - planner.addRule(CsvProjectTableScanRule.INSTANCE); - } - - public Result implement(EnumerableRelImplementor implementor, Prefer pref) { - PhysType physType = - PhysTypeImpl.of( - implementor.getTypeFactory(), - getRowType(), - pref.preferArray()); - - if (table instanceof JsonTable) { - return implementor.result( - physType, - Blocks.toBlock( - Expressions.call(table.getExpression(JsonTable.class), - "enumerable"))); - } - return implementor.result( - physType, - Blocks.toBlock( - Expressions.call(table.getExpression(CsvTranslatableTable2.class), - "project", Expressions.constant(fields)))); - } -} - -// End CsvTableScan.java diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/calcite/adapter/csv/CsvTranslatableTable2.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/calcite/adapter/csv/CsvTranslatableTable2.java deleted file mode 100644 index bc28fdd8f..000000000 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/calcite/adapter/csv/CsvTranslatableTable2.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * 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.calcite.adapter.csv; - -import org.apache.calcite.adapter.java.JavaTypeFactory; -import org.apache.calcite.linq4j.AbstractEnumerable; -import org.apache.calcite.linq4j.Enumerable; -import org.apache.calcite.linq4j.Enumerator; -import org.apache.calcite.linq4j.QueryProvider; -import org.apache.calcite.linq4j.Queryable; -import org.apache.calcite.linq4j.tree.Expression; -import org.apache.calcite.plan.RelOptTable; -import org.apache.calcite.rel.RelNode; -import org.apache.calcite.rel.type.RelDataType; -import org.apache.calcite.rel.type.RelDataTypeFactory; -import org.apache.calcite.rel.type.RelProtoDataType; -import org.apache.calcite.schema.QueryableTable; -import org.apache.calcite.schema.SchemaPlus; -import org.apache.calcite.schema.Schemas; -import org.apache.calcite.schema.TranslatableTable; - -import au.com.bytecode.opencsv.CSVReader; - -import java.io.IOException; -import java.io.Reader; -import java.lang.reflect.Type; -import java.util.ArrayList; - -/** - * Table based on a CSV stream. - */ -public class CsvTranslatableTable2 extends CsvTable - implements QueryableTable, TranslatableTable { - - final private CSVReader csvReader; - private CsvEnumerator2 csvEnumerator2; - final private String[] firstLine; - - /** Creates a CsvTable. - */ - CsvTranslatableTable2(Reader readerx, RelProtoDataType protoRowType) { - super(null, protoRowType); - this.csvReader = new CSVReader(readerx); - try { - this.firstLine = csvReader.readNext(); - } catch (IOException e) { - throw new RuntimeException("csvReader.readNext() failed ", e); - } - } - - public String toString() { - return "CsvTranslatableTable2"; - } - - /** Returns an enumerable over a given projection of the fields. - * - *

Called from generated code. */ - public Enumerable project(final int[] fields) { - return new AbstractEnumerable() { - public Enumerator enumerator() { - return csvEnumerator2; - } - }; - } - - public Expression getExpression(SchemaPlus schema, String tableName, - Class clazz) { - return Schemas.tableExpression(schema, getElementType(), tableName, clazz); - } - - public Type getElementType() { - return Object[].class; - } - - public Queryable asQueryable(QueryProvider queryProvider, - SchemaPlus schema, String tableName) { - throw new UnsupportedOperationException(); - } - - public RelNode toRel( - RelOptTable.ToRelContext context, - RelOptTable relOptTable) { - // Request all fields. - final int fieldCount = relOptTable.getRowType().getFieldCount(); - final int[] fields = CsvEnumerator.identityList(fieldCount); - return new CsvTableScan2(context.getCluster(), relOptTable, this, fields); - } - - @Override - public RelDataType getRowType(RelDataTypeFactory typeFactory) { - RelDataType rowType = null; - - if (fieldTypes == null) { - fieldTypes = new ArrayList(); - rowType = CsvEnumerator2.deduceRowType((JavaTypeFactory) typeFactory, firstLine, fieldTypes); - } else { - rowType = CsvEnumerator2.deduceRowType((JavaTypeFactory) typeFactory, firstLine, null); - } - - if (csvEnumerator2==null) - csvEnumerator2 = new CsvEnumerator2(csvReader, fieldTypes); - - return rowType; - } -} - -// End CsvTranslatableTable2.java diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/FilterCSVColumns.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/FilterCSVColumns.java deleted file mode 100644 index 718f4623b..000000000 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/FilterCSVColumns.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * 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.processors.standard; - -import static java.sql.Types.CHAR; -import static java.sql.Types.LONGNVARCHAR; -import static java.sql.Types.LONGVARCHAR; -import static java.sql.Types.NCHAR; -import static java.sql.Types.NVARCHAR; -import static java.sql.Types.VARCHAR; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.Reader; -import java.nio.charset.StandardCharsets; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.ResultSet; -import java.sql.ResultSetMetaData; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Properties; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -import org.apache.calcite.adapter.csv.CsvSchemaFactory2; -import org.apache.calcite.jdbc.CalciteConnection; -import org.apache.calcite.schema.Schema; -import org.apache.calcite.schema.SchemaPlus; -import org.apache.commons.lang3.StringEscapeUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.nifi.annotation.behavior.EventDriven; -import org.apache.nifi.annotation.behavior.InputRequirement; -import org.apache.nifi.annotation.behavior.InputRequirement.Requirement; -import org.apache.nifi.annotation.behavior.SideEffectFree; -import org.apache.nifi.annotation.behavior.SupportsBatching; -import org.apache.nifi.annotation.documentation.CapabilityDescription; -import org.apache.nifi.annotation.documentation.Tags; -import org.apache.nifi.components.PropertyDescriptor; -import org.apache.nifi.flowfile.FlowFile; -import org.apache.nifi.logging.ProcessorLog; -import org.apache.nifi.processor.AbstractProcessor; -import org.apache.nifi.processor.ProcessContext; -import org.apache.nifi.processor.ProcessSession; -import org.apache.nifi.processor.ProcessorInitializationContext; -import org.apache.nifi.processor.Relationship; -import org.apache.nifi.processor.exception.ProcessException; -import org.apache.nifi.processor.io.StreamCallback; -import org.apache.nifi.processor.util.StandardValidators; -import org.apache.nifi.stream.io.BufferedInputStream; -import org.apache.nifi.util.StopWatch; - -import com.google.common.collect.ImmutableMap; - -@EventDriven -@SideEffectFree -@SupportsBatching -@Tags({"xml", "xslt", "transform"}) -@InputRequirement(Requirement.INPUT_REQUIRED) -@CapabilityDescription("Filter out specific columns from CSV data. Some other transformations are also supported." - + "Columns can be renamed, simple calculations performed, aggregations, etc." - + "SQL select statement is used to specify how CSV data should be transformed." - + "SQL statement follows standard SQL, some restrictions may apply." - + "Successfully transformed CSV data is routed to the 'success' relationship." - + "If transform fails, the original FlowFile is routed to the 'failure' relationship") -public class FilterCSVColumns extends AbstractProcessor { - - public static final PropertyDescriptor SQL_SELECT = new PropertyDescriptor.Builder() - .name("SQL select statement") - .description("SQL select statement specifies how CSV data should be transformed. " - + "Sql select should select from CSV.A table") - .required(true) - .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) - .build(); - - public static final Relationship REL_SUCCESS = new Relationship.Builder() - .name("success") - .description("The FlowFile with transformed content will be routed to this relationship") - .build(); - public static final Relationship REL_FAILURE = new Relationship.Builder() - .name("failure") - .description("If a FlowFile fails processing for any reason (for example, the SQL statement contains columns not present in CSV), it will be routed to this relationship") - .build(); - - private List properties; - private Set relationships; - - @Override - protected void init(final ProcessorInitializationContext context) { - final List properties = new ArrayList<>(); - properties.add(SQL_SELECT); - this.properties = Collections.unmodifiableList(properties); - - final Set relationships = new HashSet<>(); - relationships.add(REL_SUCCESS); - relationships.add(REL_FAILURE); - this.relationships = Collections.unmodifiableSet(relationships); - } - - @Override - public Set getRelationships() { - return relationships; - } - - @Override - protected List getSupportedPropertyDescriptors() { - return properties; - } - - @Override - public void onTrigger(final ProcessContext context, final ProcessSession session) { - final FlowFile original = session.get(); - if (original == null) { - return; - } - - final ProcessorLog logger = getLogger(); - final StopWatch stopWatch = new StopWatch(true); - - try { - FlowFile transformed = session.write(original, new StreamCallback() { - @Override - public void process(final InputStream rawIn, final OutputStream out) throws IOException { - try (final InputStream in = new BufferedInputStream(rawIn)) { - - String sql = context.getProperty(SQL_SELECT).getValue(); - final ResultSet resultSet = transform(rawIn, sql); - convertToCSV(resultSet, out); - - } catch (final Exception e) { - throw new IOException(e); - } - } - }); - session.transfer(transformed, REL_SUCCESS); - session.getProvenanceReporter().modifyContent(transformed, stopWatch.getElapsed(TimeUnit.MILLISECONDS)); - logger.info("Transformed {}", new Object[]{original}); - } catch (ProcessException e) { - logger.error("Unable to transform {} due to {}", new Object[]{original, e}); - session.transfer(original, REL_FAILURE); - } - } - - static protected ResultSet transform(InputStream rawIn, String sql) throws SQLException { - - Reader readerx = new InputStreamReader(rawIn); - HashMap inputs = new HashMap<>(); - inputs.put("A", readerx); - - Statement statement = null; - final Properties properties = new Properties(); -// properties.setProperty("caseSensitive", "true"); - try (final Connection connection = DriverManager.getConnection("jdbc:calcite:", properties)) { - final CalciteConnection calciteConnection = connection.unwrap(CalciteConnection.class); - - final SchemaPlus rootSchema = calciteConnection.getRootSchema(); - final Schema schema = - new CsvSchemaFactory2(inputs) - .create(rootSchema, "CSV", ImmutableMap.of("flavor", "TRANSLATABLE")); - - calciteConnection.getRootSchema().add("CSV", schema); - rootSchema.add("default", schema); - - statement = connection.createStatement(); - final ResultSet resultSet = statement.executeQuery(sql); - return resultSet; - } - } - - static protected void convertToCSV(ResultSet resultSet, OutputStream out) throws SQLException, IOException { - - convertToCsvStream(resultSet, out); - } - - public static long convertToCsvStream(final ResultSet rs, final OutputStream outStream) throws SQLException, IOException { - return convertToCsvStream(rs, outStream, null, null); - } - - public static long convertToCsvStream(final ResultSet rs, final OutputStream outStream, String recordName, ResultSetRowCallback callback) - throws SQLException, IOException { - - final ResultSetMetaData meta = rs.getMetaData(); - final int nrOfColumns = meta.getColumnCount(); - List columnNames = new ArrayList<>(nrOfColumns); - - for (int i = 1; i <= nrOfColumns; i++) { - String columnNameFromMeta = meta.getColumnName(i); - // Hive returns table.column for column name. Grab the column name as the string after the last period - int columnNameDelimiter = columnNameFromMeta.lastIndexOf("."); - columnNames.add(columnNameFromMeta.substring(columnNameDelimiter + 1)); - } - - // Write column names as header row - outStream.write(StringUtils.join(columnNames, ",").getBytes(StandardCharsets.UTF_8)); - outStream.write("\n".getBytes(StandardCharsets.UTF_8)); - - // Iterate over the rows - long nrOfRows = 0; - while (rs.next()) { - if (callback != null) { - callback.processRow(rs); - } - List rowValues = new ArrayList<>(nrOfColumns); - for (int i = 1; i <= nrOfColumns; i++) { - final int javaSqlType = meta.getColumnType(i); - final Object value = rs.getObject(i); - - switch (javaSqlType) { - case CHAR: - case LONGNVARCHAR: - case LONGVARCHAR: - case NCHAR: - case NVARCHAR: - case VARCHAR: - rowValues.add("\"" + StringEscapeUtils.escapeCsv(rs.getString(i)) + "\""); - break; - default: - rowValues.add(value.toString()); - } - } - // Write row values - outStream.write(StringUtils.join(rowValues, ",").getBytes(StandardCharsets.UTF_8)); - outStream.write("\n".getBytes(StandardCharsets.UTF_8)); - nrOfRows++; - } - return nrOfRows; - } - - /** - * An interface for callback methods which allows processing of a row during the convertToXYZStream() processing. - * IMPORTANT: This method should only work on the row pointed at by the current ResultSet reference. - * Advancing the cursor (e.g.) can cause rows to be skipped during Avro transformation. - */ - public interface ResultSetRowCallback { - void processRow(ResultSet resultSet) throws IOException; - } -} diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/QueryFlowFile.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/QueryFlowFile.java new file mode 100644 index 000000000..833a5d622 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/QueryFlowFile.java @@ -0,0 +1,541 @@ +/* + * 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.processors.standard; + +import java.io.Closeable; +import java.io.IOException; +import java.io.OutputStream; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +import org.apache.calcite.config.CalciteConnectionProperty; +import org.apache.calcite.config.Lex; +import org.apache.calcite.jdbc.CalciteConnection; +import org.apache.calcite.schema.SchemaPlus; +import org.apache.calcite.sql.parser.SqlParser; +import org.apache.nifi.annotation.behavior.DynamicProperty; +import org.apache.nifi.annotation.behavior.DynamicRelationship; +import org.apache.nifi.annotation.behavior.EventDriven; +import org.apache.nifi.annotation.behavior.InputRequirement; +import org.apache.nifi.annotation.behavior.InputRequirement.Requirement; +import org.apache.nifi.annotation.behavior.SideEffectFree; +import org.apache.nifi.annotation.behavior.SupportsBatching; +import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.annotation.lifecycle.OnScheduled; +import org.apache.nifi.annotation.lifecycle.OnStopped; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.ValidationContext; +import org.apache.nifi.components.ValidationResult; +import org.apache.nifi.components.Validator; +import org.apache.nifi.flowfile.FlowFile; +import org.apache.nifi.flowfile.attributes.CoreAttributes; +import org.apache.nifi.processor.AbstractProcessor; +import org.apache.nifi.processor.ProcessContext; +import org.apache.nifi.processor.ProcessSession; +import org.apache.nifi.processor.ProcessorInitializationContext; +import org.apache.nifi.processor.Relationship; +import org.apache.nifi.processor.exception.ProcessException; +import org.apache.nifi.processor.io.OutputStreamCallback; +import org.apache.nifi.queryflowfile.FlowFileTable; +import org.apache.nifi.serialization.RecordSetWriter; +import org.apache.nifi.serialization.RecordSetWriterFactory; +import org.apache.nifi.serialization.RowRecordReaderFactory; +import org.apache.nifi.serialization.WriteResult; +import org.apache.nifi.serialization.record.ResultSetRecordSet; +import org.apache.nifi.util.StopWatch; + +@EventDriven +@SideEffectFree +@SupportsBatching +@Tags({"sql", "query", "calcite", "route", "record", "transform", "select", "update", "modify", "etl", "filter", "record", "csv", "json", "logs", "text", "avro", "aggregate"}) +@InputRequirement(Requirement.INPUT_REQUIRED) +@CapabilityDescription("Evaluates one or more SQL queries against the contents of a FlowFile. The result of the " + + "SQL query then becomes the content of the output FlowFile. This can be used, for example, " + + "for field-specific filtering, transformation, and row-level filtering. " + + "Columns can be renamed, simple calculations and aggregations performed, etc. " + + "The Processor is configured with a Record Reader Controller Service and a Record Writer service so as to allow flexibility in incoming and outgoing data formats. " + + "The Processor must be configured with at least one user-defined property. The name of the Property " + + "is the Relationship to route data to, and the value of the Property is a SQL SELECT statement that is used to specify how input data should be transformed/filtered. " + + "The SQL statement must be valid ANSI SQL and is powered by Apache Calcite. " + + "If the transformation fails, the original FlowFile is routed to the 'failure' relationship. Otherwise, the data selected will be routed to the associated " + + "relationship. See the Processor Usage documentation for more information.") +@DynamicRelationship(name="", description="Each user-defined property defines a new Relationship for this Processor.") +@DynamicProperty(name = "The name of the relationship to route data to", value="A SQL SELECT statement that is used to determine what data should be routed to this " + + "relationship.", supportsExpressionLanguage=true, description="Each user-defined property specifies a SQL SELECT statement to run over the data, with the data " + + "that is selected being routed to the relationship whose name is the property name") +public class QueryFlowFile extends AbstractProcessor { + static final PropertyDescriptor RECORD_READER_FACTORY = new PropertyDescriptor.Builder() + .name("Record Reader") + .description("Specifies the Controller Service to use for parsing incoming data and determining the data's schema") + .identifiesControllerService(RowRecordReaderFactory.class) + .required(true) + .build(); + static final PropertyDescriptor RECORD_WRITER_FACTORY = new PropertyDescriptor.Builder() + .name("Record Writer") + .description("Specifies the Controller Service to use for writing results to a FlowFile") + .identifiesControllerService(RecordSetWriterFactory.class) + .required(true) + .build(); + static final PropertyDescriptor INCLUDE_ZERO_RECORD_FLOWFILES = new PropertyDescriptor.Builder() + .name("Include Zero Record FlowFiles") + .description("When running the SQL statement against an incoming FlowFile, if the result has no data, " + + "this property specifies whether or not a FlowFile will be sent to the corresponding relationship") + .expressionLanguageSupported(false) + .allowableValues("true", "false") + .defaultValue("true") + .required(true) + .build(); + static final PropertyDescriptor CACHE_SCHEMA = new PropertyDescriptor.Builder() + .name("Cache Schema") + .description("Parsing the SQL query and deriving the FlowFile's schema is relatively expensive. If this value is set to true, " + + "the Processor will cache these values so that the Processor is much more efficient and much faster. However, if this is done, " + + "then the schema that is derived for the first FlowFile processed must apply to all FlowFiles. If all FlowFiles will not have the exact " + + "same schema, or if the SQL SELECT statement uses the Expression Language, this value should be set to false.") + .expressionLanguageSupported(false) + .allowableValues("true", "false") + .defaultValue("true") + .required(true) + .build(); + + public static final Relationship REL_ORIGINAL = new Relationship.Builder() + .name("original") + .description("The original FlowFile is routed to this relationship") + .build(); + public static final Relationship REL_FAILURE = new Relationship.Builder() + .name("failure") + .description("If a FlowFile fails processing for any reason (for example, the SQL " + + "statement contains columns not present in input data), the original FlowFile it will " + + "be routed to this relationship") + .build(); + + private List properties; + private final Set relationships = Collections.synchronizedSet(new HashSet<>()); + + private final Map> statementQueues = new HashMap<>(); + + @Override + protected void init(final ProcessorInitializationContext context) { + try { + DriverManager.registerDriver(new org.apache.calcite.jdbc.Driver()); + } catch (final SQLException e) { + throw new ProcessException("Failed to load Calcite JDBC Driver", e); + } + + final List properties = new ArrayList<>(); + properties.add(RECORD_READER_FACTORY); + properties.add(RECORD_WRITER_FACTORY); + properties.add(INCLUDE_ZERO_RECORD_FLOWFILES); + properties.add(CACHE_SCHEMA); + this.properties = Collections.unmodifiableList(properties); + + relationships.add(REL_FAILURE); + relationships.add(REL_ORIGINAL); + } + + @Override + public Set getRelationships() { + return relationships; + } + + @Override + protected List getSupportedPropertyDescriptors() { + return properties; + } + + @Override + public void onPropertyModified(final PropertyDescriptor descriptor, final String oldValue, final String newValue) { + if (!descriptor.isDynamic()) { + return; + } + + final Relationship relationship = new Relationship.Builder() + .name(descriptor.getName()) + .description("User-defined relationship that specifies where data that matches the specified SQL query should be routed") + .build(); + + if (newValue == null) { + relationships.remove(relationship); + } else { + relationships.add(relationship); + } + } + + @Override + protected Collection customValidate(final ValidationContext validationContext) { + final boolean cache = validationContext.getProperty(CACHE_SCHEMA).asBoolean(); + if (cache) { + for (final PropertyDescriptor descriptor : validationContext.getProperties().keySet()) { + if (descriptor.isDynamic() && validationContext.isExpressionLanguagePresent(validationContext.getProperty(descriptor).getValue())) { + return Collections.singleton(new ValidationResult.Builder() + .subject("Cache Schema") + .input("true") + .valid(false) + .explanation("Cannot have 'Cache Schema' property set to true if any SQL statement makes use of the Expression Language") + .build()); + } + } + } + + return Collections.emptyList(); + } + + @Override + protected PropertyDescriptor getSupportedDynamicPropertyDescriptor(final String propertyDescriptorName) { + return new PropertyDescriptor.Builder() + .name(propertyDescriptorName) + .description("SQL select statement specifies how data should be filtered/transformed. " + + "SQL SELECT should select from the FLOWFILE table") + .required(false) + .dynamic(true) + .expressionLanguageSupported(true) + .addValidator(new SqlValidator()) + .build(); + } + + @Override + public void onTrigger(final ProcessContext context, final ProcessSession session) { + final FlowFile original = session.get(); + if (original == null) { + return; + } + + final StopWatch stopWatch = new StopWatch(true); + + final RecordSetWriterFactory resultSetWriterFactory = context.getProperty(RECORD_WRITER_FACTORY) + .asControllerService(RecordSetWriterFactory.class); + final RowRecordReaderFactory recordParserFactory = context.getProperty(RECORD_READER_FACTORY) + .asControllerService(RowRecordReaderFactory.class); + + final RecordSetWriter resultSetWriter = resultSetWriterFactory.createWriter(getLogger()); + final Map transformedFlowFiles = new HashMap<>(); + final Set createdFlowFiles = new HashSet<>(); + + try { + for (final PropertyDescriptor descriptor : context.getProperties().keySet()) { + if (!descriptor.isDynamic()) { + continue; + } + + final Relationship relationship = new Relationship.Builder().name(descriptor.getName()).build(); + + // We have to fork a child because we may need to read the input FlowFile more than once, + // and we cannot call session.read() on the original FlowFile while we are within a write + // callback for the original FlowFile. + FlowFile transformed = session.create(original); + + // Ensure that we have the FlowFile in the map in case we throw any Exception + createdFlowFiles.add(transformed); + + final String sql = context.getProperty(descriptor).evaluateAttributeExpressions(original).getValue(); + final AtomicReference writeResultRef = new AtomicReference<>(); + final QueryResult queryResult; + if (context.getProperty(CACHE_SCHEMA).asBoolean()) { + queryResult = queryWithCache(session, original, sql, context, recordParserFactory); + } else { + queryResult = query(session, original, sql, context, recordParserFactory); + } + + try { + final ResultSet rs = queryResult.getResultSet(); + transformed = session.write(transformed, new OutputStreamCallback() { + @Override + public void process(final OutputStream out) throws IOException { + try { + final ResultSetRecordSet recordSet = new ResultSetRecordSet(rs); + writeResultRef.set(resultSetWriter.write(recordSet, out)); + } catch (final Exception e) { + throw new IOException(e); + } + } + }); + } finally { + closeQuietly(queryResult); + } + + final WriteResult result = writeResultRef.get(); + if (result.getRecordCount() == 0 && !context.getProperty(INCLUDE_ZERO_RECORD_FLOWFILES).asBoolean()) { + session.remove(transformed); + transformedFlowFiles.remove(transformed); + getLogger().info("Transformed {} but the result contained no data so will not pass on a FlowFile", new Object[] {original}); + } else { + final Map attributesToAdd = new HashMap<>(); + if (result.getAttributes() != null) { + attributesToAdd.putAll(result.getAttributes()); + } + + attributesToAdd.put(CoreAttributes.MIME_TYPE.key(), resultSetWriter.getMimeType()); + attributesToAdd.put("record.count", String.valueOf(result.getRecordCount())); + transformed = session.putAllAttributes(transformed, attributesToAdd); + transformedFlowFiles.put(transformed, relationship); + } + } + + final long elapsedMillis = stopWatch.getElapsed(TimeUnit.MILLISECONDS); + if (transformedFlowFiles.size() > 0) { + session.getProvenanceReporter().fork(original, transformedFlowFiles.keySet(), elapsedMillis); + + for (final Map.Entry entry : transformedFlowFiles.entrySet()) { + final FlowFile transformed = entry.getKey(); + final Relationship relationship = entry.getValue(); + + session.getProvenanceReporter().route(transformed, relationship); + session.transfer(transformed, relationship); + } + } + + getLogger().info("Successfully transformed {} in {} millis", new Object[] {original, elapsedMillis}); + session.transfer(original, REL_ORIGINAL); + } catch (ProcessException e) { + getLogger().error("Unable to transform {} due to {}", new Object[] {original, e}); + session.remove(createdFlowFiles); + session.transfer(original, REL_FAILURE); + } catch (final SQLException e) { + getLogger().error("Unable to transform {} due to {}", new Object[] {original, e.getCause() == null ? e : e.getCause()}); + session.remove(createdFlowFiles); + session.transfer(original, REL_FAILURE); + } + } + + private synchronized CachedStatement getStatement(final String sql, final Supplier connectionSupplier, final ProcessSession session, + final FlowFile flowFile, final RowRecordReaderFactory recordReaderFactory) throws SQLException { + + final BlockingQueue statementQueue = statementQueues.get(sql); + if (statementQueue == null) { + return buildCachedStatement(sql, connectionSupplier, session, flowFile, recordReaderFactory); + } + + final CachedStatement cachedStmt = statementQueue.poll(); + if (cachedStmt != null) { + return cachedStmt; + } + + return buildCachedStatement(sql, connectionSupplier, session, flowFile, recordReaderFactory); + } + + private CachedStatement buildCachedStatement(final String sql, final Supplier connectionSupplier, final ProcessSession session, + final FlowFile flowFile, final RowRecordReaderFactory recordReaderFactory) throws SQLException { + + final CalciteConnection connection = connectionSupplier.get(); + final SchemaPlus rootSchema = connection.getRootSchema(); + + final FlowFileTable flowFileTable = new FlowFileTable<>(session, flowFile, recordReaderFactory, getLogger()); + rootSchema.add("FLOWFILE", flowFileTable); + rootSchema.setCacheEnabled(false); + + final PreparedStatement stmt = connection.prepareStatement(sql); + return new CachedStatement(stmt, flowFileTable, connection); + } + + @OnStopped + public synchronized void cleanup() { + for (final BlockingQueue statementQueue : statementQueues.values()) { + CachedStatement stmt; + while ((stmt = statementQueue.poll()) != null) { + closeQuietly(stmt.getStatement(), stmt.getConnection()); + } + } + + statementQueues.clear(); + } + + @OnScheduled + public synchronized void setupQueues(final ProcessContext context) { + // Create a Queue of PreparedStatements for each property that is user-defined. This allows us to easily poll the + // queue and add as necessary, knowing that the queue already exists. + for (final PropertyDescriptor descriptor : context.getProperties().keySet()) { + if (!descriptor.isDynamic()) { + continue; + } + + final String sql = context.getProperty(descriptor).evaluateAttributeExpressions().getValue(); + final BlockingQueue queue = new LinkedBlockingQueue<>(context.getMaxConcurrentTasks()); + statementQueues.put(sql, queue); + } + } + + protected QueryResult queryWithCache(final ProcessSession session, final FlowFile flowFile, final String sql, final ProcessContext context, + final RowRecordReaderFactory recordParserFactory) throws SQLException { + + final Supplier connectionSupplier = () -> { + final Properties properties = new Properties(); + properties.put(CalciteConnectionProperty.LEX.camelName(), Lex.JAVA.name()); + + try { + final Connection connection = DriverManager.getConnection("jdbc:calcite:", properties); + final CalciteConnection calciteConnection = connection.unwrap(CalciteConnection.class); + return calciteConnection; + } catch (final Exception e) { + throw new ProcessException(e); + } + }; + + final CachedStatement cachedStatement = getStatement(sql, connectionSupplier, session, flowFile, recordParserFactory); + final PreparedStatement stmt = cachedStatement.getStatement(); + final FlowFileTable table = cachedStatement.getTable(); + table.setFlowFile(session, flowFile); + + final ResultSet rs = stmt.executeQuery(); + + return new QueryResult() { + @Override + public void close() throws IOException { + final BlockingQueue statementQueue = statementQueues.get(sql); + if (statementQueue == null || !statementQueue.offer(cachedStatement)) { + try { + cachedStatement.getConnection().close(); + } catch (SQLException e) { + throw new IOException("Failed to close statement", e); + } + } + } + + @Override + public ResultSet getResultSet() { + return rs; + } + }; + } + + protected QueryResult query(final ProcessSession session, final FlowFile flowFile, final String sql, final ProcessContext context, + final RowRecordReaderFactory recordParserFactory) throws SQLException { + + final Properties properties = new Properties(); + properties.put(CalciteConnectionProperty.LEX.camelName(), Lex.JAVA.name()); + + Connection connection = null; + ResultSet resultSet = null; + Statement statement = null; + try { + connection = DriverManager.getConnection("jdbc:calcite:", properties); + final CalciteConnection calciteConnection = connection.unwrap(CalciteConnection.class); + final SchemaPlus rootSchema = calciteConnection.getRootSchema(); + + final FlowFileTable flowFileTable = new FlowFileTable<>(session, flowFile, recordParserFactory, getLogger()); + rootSchema.add("FLOWFILE", flowFileTable); + rootSchema.setCacheEnabled(false); + + statement = connection.createStatement(); + resultSet = statement.executeQuery(sql); + + final ResultSet rs = resultSet; + final Statement stmt = statement; + final Connection conn = connection; + return new QueryResult() { + @Override + public void close() throws IOException { + closeQuietly(rs, stmt, conn); + } + + @Override + public ResultSet getResultSet() { + return rs; + } + }; + } catch (final Exception e) { + closeQuietly(resultSet, statement, connection); + throw e; + } + } + + private void closeQuietly(final AutoCloseable... closeables) { + if (closeables == null) { + return; + } + + for (final AutoCloseable closeable : closeables) { + if (closeable == null) { + continue; + } + + try { + closeable.close(); + } catch (final Exception e) { + getLogger().warn("Failed to close SQL resource", e); + } + } + } + + private static class SqlValidator implements Validator { + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + final String substituted = context.newPropertyValue(input).evaluateAttributeExpressions().getValue(); + final SqlParser parser = SqlParser.create(substituted); + try { + parser.parseStmt(); + return new ValidationResult.Builder() + .subject(subject) + .input(input) + .valid(true) + .build(); + } catch (final Exception e) { + return new ValidationResult.Builder() + .subject(subject) + .input(input) + .valid(false) + .explanation("Not a valid SQL Statement: " + e.getMessage()) + .build(); + } + } + } + + private static interface QueryResult extends Closeable { + ResultSet getResultSet(); + } + + private static class CachedStatement { + private final FlowFileTable table; + private final PreparedStatement statement; + private final Connection connection; + + public CachedStatement(final PreparedStatement statement, final FlowFileTable table, final Connection connection) { + this.statement = statement; + this.table = table; + this.connection = connection; + } + + public FlowFileTable getTable() { + return table; + } + + public PreparedStatement getStatement() { + return statement; + } + + public Connection getConnection() { + return connection; + } + } +} diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/queryflowfile/FlowFileEnumerator.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/queryflowfile/FlowFileEnumerator.java new file mode 100644 index 000000000..1a62d1436 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/queryflowfile/FlowFileEnumerator.java @@ -0,0 +1,150 @@ +/* + * 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.queryflowfile; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.calcite.linq4j.Enumerator; +import org.apache.nifi.flowfile.FlowFile; +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.processor.ProcessSession; +import org.apache.nifi.processor.exception.ProcessException; +import org.apache.nifi.serialization.MalformedRecordException; +import org.apache.nifi.serialization.RecordReader; +import org.apache.nifi.serialization.RowRecordReaderFactory; +import org.apache.nifi.serialization.record.Record; + +public class FlowFileEnumerator implements Enumerator { + private final ProcessSession session; + private final FlowFile flowFile; + private final ComponentLog logger; + private final RowRecordReaderFactory recordParserFactory; + private final int[] fields; + + private InputStream rawIn; + private Object currentRow; + private RecordReader recordParser; + + public FlowFileEnumerator(final ProcessSession session, final FlowFile flowFile, final ComponentLog logger, final RowRecordReaderFactory parserFactory, final int[] fields) { + this.session = session; + this.flowFile = flowFile; + this.recordParserFactory = parserFactory; + this.logger = logger; + this.fields = fields; + reset(); + } + + @Override + public Object current() { + return currentRow; + } + + @Override + public boolean moveNext() { + currentRow = null; + while (currentRow == null) { + try { + currentRow = filterColumns(recordParser.nextRecord()); + break; + } catch (final IOException e) { + logger.error("Failed to read next record in stream for " + flowFile + ". Assuming end of stream.", e); + currentRow = null; + break; + } catch (final MalformedRecordException mre) { + logger.error("Failed to parse record in stream for " + flowFile + ". Will skip record and continue reading", mre); + } + } + + if (currentRow == null) { + // If we are out of data, close the InputStream. We do this because + // Calcite does not necessarily call our close() method. + close(); + } + return (currentRow != null); + } + + private Object filterColumns(final Record record) { + if (record == null) { + return null; + } + + final Object[] row = record.getValues(); + + // If we want no fields or if the row is null, just return null + if (fields == null || row == null) { + return row; + } + + // If we want only a single field, then Calcite is going to expect us to return + // the actual value, NOT a 1-element array of values. + if (fields.length == 1) { + final int desiredCellIndex = fields[0]; + return row[desiredCellIndex]; + } + + // Create a new Object array that contains only the desired fields. + if (row.length <= fields.length) { + return row; + } + + final Object[] filtered = new Object[fields.length]; + for (int i = 0; i < fields.length; i++) { + final int indexToKeep = fields[i]; + filtered[i] = row[indexToKeep]; + } + + return filtered; + } + + @Override + public void reset() { + if (rawIn != null) { + try { + rawIn.close(); + } catch (final Exception e) { + logger.warn("Could not close FlowFile's input due to " + e, e); + } + } + + rawIn = session.read(flowFile); + + try { + recordParser = recordParserFactory.createRecordReader(rawIn, logger); + } catch (final MalformedRecordException | IOException e) { + throw new ProcessException("Failed to reset stream", e); + } + } + + @Override + public void close() { + if (recordParser != null) { + try { + recordParser.close(); + } catch (final Exception e) { + logger.warn("Failed to close decorated source for " + flowFile, e); + } + } + + try { + rawIn.close(); + } catch (final Exception e) { + logger.warn("Failed to close InputStream for " + flowFile, e); + } + } +} diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/queryflowfile/FlowFileProjectTableScanRule.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/queryflowfile/FlowFileProjectTableScanRule.java new file mode 100644 index 000000000..c5179c9aa --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/queryflowfile/FlowFileProjectTableScanRule.java @@ -0,0 +1,76 @@ +/* + * 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.queryflowfile; + +import java.util.List; + +import org.apache.calcite.plan.RelOptRule; +import org.apache.calcite.plan.RelOptRuleCall; +import org.apache.calcite.rel.logical.LogicalProject; +import org.apache.calcite.rex.RexInputRef; +import org.apache.calcite.rex.RexNode; + +/** + * Planner rule that projects from a {@link FlowFileTableScan} scan just the columns + * needed to satisfy a projection. If the projection's expressions are trivial, + * the projection is removed. + */ +public class FlowFileProjectTableScanRule extends RelOptRule { + public static final FlowFileProjectTableScanRule INSTANCE = new FlowFileProjectTableScanRule(); + + private FlowFileProjectTableScanRule() { + super( + operand(LogicalProject.class, + operand(FlowFileTableScan.class, none())), + "FlowFileProjectTableScanRule"); + } + + @Override + public void onMatch(RelOptRuleCall call) { + final LogicalProject project = call.rel(0); + final FlowFileTableScan scan = call.rel(1); + final int[] fields = getProjectFields(project.getProjects()); + + if (fields == null) { + // Project contains expressions more complex than just field references. + return; + } + + call.transformTo( + new FlowFileTableScan( + scan.getCluster(), + scan.getTable(), + scan.flowFileTable, + fields)); + } + + private int[] getProjectFields(List exps) { + final int[] fields = new int[exps.size()]; + + for (int i = 0; i < exps.size(); i++) { + final RexNode exp = exps.get(i); + + if (exp instanceof RexInputRef) { + fields[i] = ((RexInputRef) exp).getIndex(); + } else { + return null; // not a simple projection + } + } + + return fields; + } +} diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/queryflowfile/FlowFileTable.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/queryflowfile/FlowFileTable.java new file mode 100644 index 000000000..a23dcfae9 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/queryflowfile/FlowFileTable.java @@ -0,0 +1,203 @@ +/* + * 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.queryflowfile; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +import org.apache.calcite.adapter.java.JavaTypeFactory; +import org.apache.calcite.linq4j.AbstractEnumerable; +import org.apache.calcite.linq4j.Enumerable; +import org.apache.calcite.linq4j.Enumerator; +import org.apache.calcite.linq4j.QueryProvider; +import org.apache.calcite.linq4j.Queryable; +import org.apache.calcite.linq4j.tree.Expression; +import org.apache.calcite.plan.RelOptTable; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeFactory; +import org.apache.calcite.schema.QueryableTable; +import org.apache.calcite.schema.Schema.TableType; +import org.apache.calcite.schema.SchemaPlus; +import org.apache.calcite.schema.Schemas; +import org.apache.calcite.schema.TranslatableTable; +import org.apache.calcite.schema.impl.AbstractTable; +import org.apache.calcite.util.Pair; +import org.apache.nifi.flowfile.FlowFile; +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.processor.ProcessSession; +import org.apache.nifi.processor.exception.ProcessException; +import org.apache.nifi.serialization.MalformedRecordException; +import org.apache.nifi.serialization.RecordReader; +import org.apache.nifi.serialization.RowRecordReaderFactory; +import org.apache.nifi.serialization.record.DataType; +import org.apache.nifi.serialization.record.RecordField; +import org.apache.nifi.serialization.record.RecordSchema; + + +public class FlowFileTable extends AbstractTable implements QueryableTable, TranslatableTable { + + private final RowRecordReaderFactory recordParserFactory; + private final ComponentLog logger; + + private RecordSchema recordSchema; + private RelDataType relDataType = null; + + private volatile ProcessSession session; + private volatile FlowFile flowFile; + + /** + * Creates a FlowFile table. + */ + public FlowFileTable(final ProcessSession session, final FlowFile flowFile, final RowRecordReaderFactory recordParserFactory, final ComponentLog logger) { + this.session = session; + this.flowFile = flowFile; + this.recordParserFactory = recordParserFactory; + this.logger = logger; + } + + public void setFlowFile(final ProcessSession session, final FlowFile flowFile) { + this.session = session; + this.flowFile = flowFile; + } + + + @Override + public String toString() { + return "FlowFileTable"; + } + + /** + * Returns an enumerable over a given projection of the fields. + * + *

+ * Called from generated code. + */ + public Enumerable project(final int[] fields) { + return new AbstractEnumerable() { + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) + public Enumerator enumerator() { + return new FlowFileEnumerator(session, flowFile, logger, recordParserFactory, fields); + } + }; + } + + @Override + @SuppressWarnings("rawtypes") + public Expression getExpression(final SchemaPlus schema, final String tableName, final Class clazz) { + return Schemas.tableExpression(schema, getElementType(), tableName, clazz); + } + + @Override + public Type getElementType() { + return Object[].class; + } + + @Override + public Queryable asQueryable(final QueryProvider queryProvider, final SchemaPlus schema, final String tableName) { + throw new UnsupportedOperationException(); + } + + @Override + public RelNode toRel(final RelOptTable.ToRelContext context, final RelOptTable relOptTable) { + // Request all fields. + final int fieldCount = relOptTable.getRowType().getFieldCount(); + final int[] fields = new int[fieldCount]; + for (int i = 0; i < fieldCount; i++) { + fields[i] = i; + } + + return new FlowFileTableScan(context.getCluster(), relOptTable, this, fields); + } + + @Override + public RelDataType getRowType(final RelDataTypeFactory typeFactory) { + if (relDataType != null) { + return relDataType; + } + + RecordSchema schema; + try (final InputStream in = session.read(flowFile)) { + final RecordReader recordParser = recordParserFactory.createRecordReader(in, logger); + schema = recordParser.getSchema(); + } catch (final MalformedRecordException | IOException e) { + throw new ProcessException("Failed to determine schema of data records for " + flowFile, e); + } + + final List names = new ArrayList<>(); + final List types = new ArrayList<>(); + + final JavaTypeFactory javaTypeFactory = (JavaTypeFactory) typeFactory; + for (final RecordField field : schema.getFields()) { + names.add(field.getFieldName()); + types.add(getRelDataType(field.getDataType(), javaTypeFactory)); + } + + logger.debug("Found Schema: {}", new Object[] {schema}); + + if (recordSchema == null) { + recordSchema = schema; + } + + relDataType = typeFactory.createStructType(Pair.zip(names, types)); + return relDataType; + } + + private RelDataType getRelDataType(final DataType fieldType, final JavaTypeFactory typeFactory) { + switch (fieldType.getFieldType()) { + case BOOLEAN: + return typeFactory.createJavaType(boolean.class); + case BYTE: + return typeFactory.createJavaType(byte.class); + case CHAR: + return typeFactory.createJavaType(char.class); + case DATE: + return typeFactory.createJavaType(java.sql.Date.class); + case DOUBLE: + return typeFactory.createJavaType(double.class); + case FLOAT: + return typeFactory.createJavaType(float.class); + case INT: + return typeFactory.createJavaType(int.class); + case SHORT: + return typeFactory.createJavaType(short.class); + case TIME: + return typeFactory.createJavaType(java.sql.Time.class); + case TIMESTAMP: + return typeFactory.createJavaType(java.sql.Timestamp.class); + case LONG: + return typeFactory.createJavaType(long.class); + case STRING: + return typeFactory.createJavaType(String.class); + case ARRAY: + return typeFactory.createJavaType(Object[].class); + case OBJECT: + return typeFactory.createJavaType(Object.class); + } + + throw new IllegalArgumentException("Unknown Record Field Type: " + fieldType); + } + + @Override + public TableType getJdbcTableType() { + return TableType.TEMPORARY_TABLE; + } +} diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/queryflowfile/FlowFileTableScan.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/queryflowfile/FlowFileTableScan.java new file mode 100644 index 000000000..ad3a1c36e --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/queryflowfile/FlowFileTableScan.java @@ -0,0 +1,91 @@ +/* + * 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.queryflowfile; + +import java.util.List; + +import org.apache.calcite.adapter.enumerable.EnumerableConvention; +import org.apache.calcite.adapter.enumerable.EnumerableRel; +import org.apache.calcite.adapter.enumerable.EnumerableRelImplementor; +import org.apache.calcite.adapter.enumerable.PhysType; +import org.apache.calcite.adapter.enumerable.PhysTypeImpl; +import org.apache.calcite.linq4j.tree.Blocks; +import org.apache.calcite.linq4j.tree.Expressions; +import org.apache.calcite.linq4j.tree.Primitive; +import org.apache.calcite.plan.RelOptCluster; +import org.apache.calcite.plan.RelOptPlanner; +import org.apache.calcite.plan.RelOptTable; +import org.apache.calcite.plan.RelTraitSet; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.RelWriter; +import org.apache.calcite.rel.core.TableScan; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeFactory; +import org.apache.calcite.rel.type.RelDataTypeField; + +/** + * Relational expression representing a scan of a FlowFile. + * + *

+ * Like any table scan, it serves as a leaf node of a query tree. + *

+ */ +public class FlowFileTableScan extends TableScan implements EnumerableRel { + final FlowFileTable flowFileTable; + final int[] fields; + + protected FlowFileTableScan(final RelOptCluster cluster, final RelOptTable table, final FlowFileTable flowFileTable, final int[] fields) { + super(cluster, cluster.traitSetOf(EnumerableConvention.INSTANCE), table); + + this.flowFileTable = flowFileTable; + this.fields = fields; + } + + @Override + public RelNode copy(final RelTraitSet traitSet, final List inputs) { + return new FlowFileTableScan(getCluster(), table, flowFileTable, fields); + } + + @Override + public RelWriter explainTerms(final RelWriter pw) { + return super.explainTerms(pw).item("fields", Primitive.asList(fields)); + } + + @Override + public RelDataType deriveRowType() { + final List fieldList = table.getRowType().getFieldList(); + final RelDataTypeFactory.FieldInfoBuilder builder = getCluster().getTypeFactory().builder(); + for (int field : fields) { + builder.add(fieldList.get(field)); + } + return builder.build(); + } + + @Override + public void register(RelOptPlanner planner) { + planner.addRule(FlowFileProjectTableScanRule.INSTANCE); + } + + @Override + public Result implement(EnumerableRelImplementor implementor, Prefer pref) { + PhysType physType = PhysTypeImpl.of(implementor.getTypeFactory(), getRowType(), pref.preferArray()); + + return implementor.result(physType, Blocks.toBlock( + Expressions.call(table.getExpression(FlowFileTable.class), "project", Expressions.constant(fields)))); + } + +} diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor index 9de5ab65e..2f2b0cbc0 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor @@ -75,6 +75,7 @@ org.apache.nifi.processors.standard.PutSyslog org.apache.nifi.processors.standard.PutTCP org.apache.nifi.processors.standard.PutUDP org.apache.nifi.processors.standard.QueryDatabaseTable +org.apache.nifi.processors.standard.QueryFlowFile org.apache.nifi.processors.standard.ReplaceText org.apache.nifi.processors.standard.RouteText org.apache.nifi.processors.standard.ReplaceTextWithMapping diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/resources/docs/org.apache.nifi.processors.standard.QueryFlowFile/additionalDetails.html b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/resources/docs/org.apache.nifi.processors.standard.QueryFlowFile/additionalDetails.html new file mode 100644 index 000000000..1cc79236c --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/resources/docs/org.apache.nifi.processors.standard.QueryFlowFile/additionalDetails.html @@ -0,0 +1,47 @@ + + + + + + QueryFlowFile + + + + + +

+ QueryFlowFile provides users a tremendous amount of power by leveraging an extremely well-known + syntax (SQL) to route, filter, transform, and query data as it traverses the system. In order to + provide the Processor with the maximum amount of flexibility, it is configured with a Controller + Service that is responsible for reading and parsing the incoming FlowFiles and a Controller Service + that is responsible for writing the results out. By using this paradigm, users are not forced to + convert their data from one format to another just to query it, and then transform the data back + into the form that they want. Rather, the appropriate Controller Service can easily be configured + and put to use for the appropriate data format. +

+ +

+ Rather than providing a single "SQL SELECT Statement" type of Property, this Processor makes use + of user-defined properties. Each user-defined property that is added to the Processor has a name + that becomes a new Relationship for the Processor and a corresponding SQL query that will be evaluated + against each FlowFile. This allows multiple SQL queries to be run against each FlowFile. +

+ +

+ The SQL syntax that is supported by this Processor is ANSI SQL and is powered by Apache Calcite. +

+ + \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestFilterCSVColumns.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestFilterCSVColumns.java deleted file mode 100644 index 421da98f0..000000000 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestFilterCSVColumns.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * 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.processors.standard; - -import static org.junit.Assert.assertEquals; - -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.List; - -import org.apache.nifi.reporting.InitializationException; -import org.apache.nifi.util.MockFlowFile; -import org.apache.nifi.util.TestRunner; -import org.apache.nifi.util.TestRunners; -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class TestFilterCSVColumns { - - private static final Logger LOGGER; - - static { - System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "info"); - System.setProperty("org.slf4j.simpleLogger.showDateTime", "true"); - System.setProperty("org.slf4j.simpleLogger.log.nifi.io.nio", "debug"); - System.setProperty("org.slf4j.simpleLogger.log.nifi.processors.standard.FilterCSVColumns", "debug"); - System.setProperty("org.slf4j.simpleLogger.log.nifi.processors.standard.TestFilterCSVColumns", "debug"); - LOGGER = LoggerFactory.getLogger(TestFilterCSVColumns.class); - } - - @Test - public void testTransformSimple() throws InitializationException, IOException, SQLException { - String sql = "select first_name, last_name, company_name, address, city from CSV.A where city='New York'"; - - Path inpath = Paths.get("src/test/resources/TestFilterCSVColumns/US500.csv"); - InputStream in = new FileInputStream(inpath.toFile()); - - ResultSet resultSet = FilterCSVColumns.transform(in, sql); - - int nrofColumns = resultSet.getMetaData().getColumnCount(); - - for (int i = 1; i <= nrofColumns; i++) { - System.out.print(resultSet.getMetaData().getColumnLabel(i) + " "); - } - System.out.println(); - - while (resultSet.next()) { - for (int i = 1; i <= nrofColumns; i++) { - System.out.print(resultSet.getString(i)+ " "); - } - System.out.println(); - } - } - - @Test - public void testTransformCalc() throws InitializationException, IOException, SQLException { - String sql = "select ID, AMOUNT1+AMOUNT2+AMOUNT3 as TOTAL from CSV.A where ID=100"; - - Path inpath = Paths.get("src/test/resources/TestFilterCSVColumns/Numeric.csv"); - InputStream in = new FileInputStream(inpath.toFile()); - - ResultSet resultSet = FilterCSVColumns.transform(in, sql); - - int nrofColumns = resultSet.getMetaData().getColumnCount(); - - for (int i = 1; i <= nrofColumns; i++) { - System.out.print(resultSet.getMetaData().getColumnLabel(i) + " "); - } - System.out.println(); - - while (resultSet.next()) { - for (int i = 1; i <= nrofColumns; i++) { - System.out.print(resultSet.getString(i)+ " "); - } - double total = resultSet.getDouble("TOTAL"); - System.out.println(); - assertEquals(90.75, total, 0.0001); - } - } - - @Test - public void testSimpleTypeless() throws InitializationException, IOException { - final TestRunner runner = TestRunners.newTestRunner(FilterCSVColumns.class); - String sql = "select first_name, last_name, company_name, address, city from CSV.A where city='New York'"; - runner.setProperty(FilterCSVColumns.SQL_SELECT, sql); - - runner.enqueue(Paths.get("src/test/resources/TestFilterCSVColumns/US500_typeless.csv")); - runner.run(); - - final List flowFiles = runner.getFlowFilesForRelationship(ExecuteProcess.REL_SUCCESS); - for (final MockFlowFile flowFile : flowFiles) { - System.out.println(flowFile); - System.out.println(new String(flowFile.toByteArray())); - } - } - -} diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestQueryFlowFile.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestQueryFlowFile.java new file mode 100644 index 000000000..41469ba54 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestQueryFlowFile.java @@ -0,0 +1,379 @@ +/* + * 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.processors.standard; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.nifi.controller.AbstractControllerService; +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.reporting.InitializationException; +import org.apache.nifi.serialization.MalformedRecordException; +import org.apache.nifi.serialization.RecordReader; +import org.apache.nifi.serialization.RecordSetWriter; +import org.apache.nifi.serialization.RecordSetWriterFactory; +import org.apache.nifi.serialization.RowRecordReaderFactory; +import org.apache.nifi.serialization.SimpleRecordSchema; +import org.apache.nifi.serialization.WriteResult; +import org.apache.nifi.serialization.record.MapRecord; +import org.apache.nifi.serialization.record.Record; +import org.apache.nifi.serialization.record.RecordField; +import org.apache.nifi.serialization.record.RecordFieldType; +import org.apache.nifi.serialization.record.RecordSchema; +import org.apache.nifi.serialization.record.RecordSet; +import org.apache.nifi.util.MockFlowFile; +import org.apache.nifi.util.TestRunner; +import org.apache.nifi.util.TestRunners; +import org.junit.Assert; +import org.junit.Test; + +public class TestQueryFlowFile { + + static { + System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "info"); + System.setProperty("org.slf4j.simpleLogger.showDateTime", "true"); + System.setProperty("org.slf4j.simpleLogger.log.nifi.io.nio", "debug"); + System.setProperty("org.slf4j.simpleLogger.log.org.apache.nifi.processors.standard.SQLTransform", "debug"); + } + + private static final String REL_NAME = "success"; + + @Test + public void testSimple() throws InitializationException, IOException, SQLException { + final MockRecordParser parser = new MockRecordParser(); + parser.addSchemaField("name", RecordFieldType.STRING); + parser.addSchemaField("age", RecordFieldType.INT); + parser.addRecord("Tom", 49); + + final MockRecordWriter writer = new MockRecordWriter("\"name\",\"points\""); + + final TestRunner runner = TestRunners.newTestRunner(QueryFlowFile.class); + runner.addControllerService("parser", parser); + runner.enableControllerService(parser); + runner.addControllerService("writer", writer); + runner.enableControllerService(writer); + + runner.setProperty(REL_NAME, "select name, age from FLOWFILE WHERE name <> ''"); + runner.setProperty(QueryFlowFile.RECORD_READER_FACTORY, "parser"); + runner.setProperty(QueryFlowFile.RECORD_WRITER_FACTORY, "writer"); + + final int numIterations = 1; + for (int i = 0; i < numIterations; i++) { + runner.enqueue(new byte[0]); + } + + runner.setThreadCount(4); + runner.run(2 * numIterations); + + runner.assertTransferCount(REL_NAME, 1); + final MockFlowFile out = runner.getFlowFilesForRelationship(REL_NAME).get(0); + System.out.println(new String(out.toByteArray())); + out.assertContentEquals("\"name\",\"points\"\n\"Tom\",\"49\"\n"); + } + + @Test + public void testParseFailure() throws InitializationException, IOException, SQLException { + final MockRecordParser parser = new MockRecordParser(); + parser.addSchemaField("name", RecordFieldType.STRING); + parser.addSchemaField("age", RecordFieldType.INT); + parser.addRecord("Tom", 49); + + final MockRecordWriter writer = new MockRecordWriter("\"name\",\"points\""); + + final TestRunner runner = TestRunners.newTestRunner(QueryFlowFile.class); + runner.addControllerService("parser", parser); + runner.enableControllerService(parser); + runner.addControllerService("writer", writer); + runner.enableControllerService(writer); + + runner.setProperty(REL_NAME, "select name, age from FLOWFILE WHERE name <> ''"); + runner.setProperty(QueryFlowFile.RECORD_READER_FACTORY, "parser"); + runner.setProperty(QueryFlowFile.RECORD_WRITER_FACTORY, "writer"); + + final int numIterations = 1; + for (int i = 0; i < numIterations; i++) { + runner.enqueue(new byte[0]); + } + + runner.setThreadCount(4); + runner.run(2 * numIterations); + + runner.assertTransferCount(REL_NAME, 1); + final MockFlowFile out = runner.getFlowFilesForRelationship(REL_NAME).get(0); + System.out.println(new String(out.toByteArray())); + out.assertContentEquals("\"name\",\"points\"\n\"Tom\",\"49\"\n"); + } + + + @Test + public void testTransformCalc() throws InitializationException, IOException, SQLException { + final MockRecordParser parser = new MockRecordParser(); + parser.addSchemaField("ID", RecordFieldType.INT); + parser.addSchemaField("AMOUNT1", RecordFieldType.FLOAT); + parser.addSchemaField("AMOUNT2", RecordFieldType.FLOAT); + parser.addSchemaField("AMOUNT3", RecordFieldType.FLOAT); + + parser.addRecord("008", 10.05F, 15.45F, 89.99F); + parser.addRecord("100", 20.25F, 25.25F, 45.25F); + parser.addRecord("105", 20.05F, 25.05F, 45.05F); + parser.addRecord("200", 34.05F, 25.05F, 75.05F); + + final MockRecordWriter writer = new MockRecordWriter("\"NAME\",\"POINTS\""); + + final TestRunner runner = TestRunners.newTestRunner(QueryFlowFile.class); + runner.addControllerService("parser", parser); + runner.enableControllerService(parser); + runner.addControllerService("writer", writer); + runner.enableControllerService(writer); + + runner.setProperty(REL_NAME, "select ID, AMOUNT1+AMOUNT2+AMOUNT3 as TOTAL from FLOWFILE where ID=100"); + runner.setProperty(QueryFlowFile.RECORD_READER_FACTORY, "parser"); + runner.setProperty(QueryFlowFile.RECORD_WRITER_FACTORY, "writer"); + + runner.enqueue(new byte[0]); + runner.run(); + + runner.assertTransferCount(REL_NAME, 1); + final MockFlowFile out = runner.getFlowFilesForRelationship(REL_NAME).get(0); + + out.assertContentEquals("\"NAME\",\"POINTS\"\n\"100\",\"90.75\"\n"); + } + + + @Test + public void testAggregateFunction() throws InitializationException, IOException { + final MockRecordParser parser = new MockRecordParser(); + parser.addSchemaField("name", RecordFieldType.STRING); + parser.addSchemaField("points", RecordFieldType.INT); + parser.addRecord("Tom", 1); + parser.addRecord("Jerry", 2); + parser.addRecord("Tom", 99); + + final MockRecordWriter writer = new MockRecordWriter("\"name\",\"points\""); + + final TestRunner runner = TestRunners.newTestRunner(QueryFlowFile.class); + runner.addControllerService("parser", parser); + runner.enableControllerService(parser); + runner.addControllerService("writer", writer); + runner.enableControllerService(writer); + + runner.setProperty(REL_NAME, "select name, sum(points) as points from FLOWFILE GROUP BY name"); + runner.setProperty(QueryFlowFile.RECORD_READER_FACTORY, "parser"); + runner.setProperty(QueryFlowFile.RECORD_WRITER_FACTORY, "writer"); + + runner.enqueue(""); + runner.run(); + + runner.assertTransferCount(REL_NAME, 1); + final MockFlowFile flowFileOut = runner.getFlowFilesForRelationship(ExecuteProcess.REL_SUCCESS).get(0); + flowFileOut.assertContentEquals("\"name\",\"points\"\n\"Tom\",\"100\"\n\"Jerry\",\"2\"\n"); + } + + @Test + public void testColumnNames() throws InitializationException, IOException { + final MockRecordParser parser = new MockRecordParser(); + parser.addSchemaField("name", RecordFieldType.STRING); + parser.addSchemaField("points", RecordFieldType.INT); + parser.addSchemaField("greeting", RecordFieldType.STRING); + parser.addRecord("Tom", 1, "Hello"); + parser.addRecord("Jerry", 2, "Hi"); + parser.addRecord("Tom", 99, "Howdy"); + + final List colNames = new ArrayList<>(); + colNames.add("name"); + colNames.add("points"); + colNames.add("greeting"); + colNames.add("FAV_GREETING"); + final ResultSetValidatingRecordWriter writer = new ResultSetValidatingRecordWriter(colNames); + + final TestRunner runner = TestRunners.newTestRunner(QueryFlowFile.class); + runner.addControllerService("parser", parser); + runner.enableControllerService(parser); + runner.addControllerService("writer", writer); + runner.enableControllerService(writer); + + runner.setProperty(REL_NAME, "select *, greeting AS FAV_GREETING from FLOWFILE"); + runner.setProperty(QueryFlowFile.RECORD_READER_FACTORY, "parser"); + runner.setProperty(QueryFlowFile.RECORD_WRITER_FACTORY, "writer"); + + runner.enqueue(""); + runner.run(); + + runner.assertTransferCount(REL_NAME, 1); + } + + + private static class ResultSetValidatingRecordWriter extends AbstractControllerService implements RecordSetWriterFactory { + private final List columnNames; + + public ResultSetValidatingRecordWriter(final List colNames) { + this.columnNames = new ArrayList<>(colNames); + } + + @Override + public RecordSetWriter createWriter(ComponentLog logger) { + return new RecordSetWriter() { + @Override + public WriteResult write(final RecordSet rs, final OutputStream out) throws IOException { + final int colCount = rs.getSchema().getFieldCount(); + Assert.assertEquals(columnNames.size(), colCount); + + final List colNames = new ArrayList<>(colCount); + for (int i = 0; i < colCount; i++) { + colNames.add(rs.getSchema().getField(i).getFieldName()); + } + + Assert.assertEquals(columnNames, colNames); + + return WriteResult.of(0, Collections.emptyMap()); + } + + @Override + public String getMimeType() { + return "text/plain"; + } + + @Override + public WriteResult write(Record record, OutputStream out) throws IOException { + return null; + } + }; + } + + } + + private static class MockRecordWriter extends AbstractControllerService implements RecordSetWriterFactory { + private final String header; + + public MockRecordWriter(final String header) { + this.header = header; + } + + @Override + public RecordSetWriter createWriter(final ComponentLog logger) { + return new RecordSetWriter() { + @Override + public WriteResult write(final RecordSet rs, final OutputStream out) throws IOException { + out.write(header.getBytes()); + out.write("\n".getBytes()); + + int recordCount = 0; + final int numCols = rs.getSchema().getFieldCount(); + Record record = null; + while ((record = rs.next()) != null) { + recordCount++; + int i = 0; + for (final String fieldName : record.getSchema().getFieldNames()) { + final String val = record.getAsString(fieldName); + out.write("\"".getBytes()); + out.write(val.getBytes()); + out.write("\"".getBytes()); + + if (i++ < numCols - 1) { + out.write(",".getBytes()); + } + } + out.write("\n".getBytes()); + } + + return WriteResult.of(recordCount, Collections.emptyMap()); + } + + @Override + public String getMimeType() { + return "text/plain"; + } + + @Override + public WriteResult write(Record record, OutputStream out) throws IOException { + return null; + } + }; + } + } + + private static class MockRecordParser extends AbstractControllerService implements RowRecordReaderFactory { + private final List records = new ArrayList<>(); + private final List fields = new ArrayList<>(); + private final int failAfterN; + + public MockRecordParser() { + this(-1); + } + + public MockRecordParser(final int failAfterN) { + this.failAfterN = failAfterN; + } + + + public void addSchemaField(final String fieldName, final RecordFieldType type) { + fields.add(new RecordField(fieldName, type.getDataType())); + } + + public void addRecord(Object... values) { + records.add(values); + } + + @Override + public RecordReader createRecordReader(InputStream in, ComponentLog logger) throws IOException { + final Iterator itr = records.iterator(); + + return new RecordReader() { + private int recordCount = 0; + + @Override + public void close() throws IOException { + } + + @Override + public Record nextRecord() throws IOException, MalformedRecordException { + if (failAfterN >= recordCount) { + throw new MalformedRecordException("Intentional Unit Test Exception because " + recordCount + " records have been read"); + } + recordCount++; + + if (!itr.hasNext()) { + return null; + } + + final Object[] values = itr.next(); + final Map valueMap = new HashMap<>(); + int i = 0; + for (final RecordField field : fields) { + final String fieldName = field.getFieldName(); + valueMap.put(fieldName, values[i++]); + } + + return new MapRecord(new SimpleRecordSchema(fields), valueMap); + } + + @Override + public RecordSchema getSchema() { + return new SimpleRecordSchema(fields); + } + }; + } + } +} diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestFilterCSVColumns/Numeric.csv b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestFilterCSVColumns/Numeric.csv deleted file mode 100644 index 2d56bb7b9..000000000 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestFilterCSVColumns/Numeric.csv +++ /dev/null @@ -1,5 +0,0 @@ -ID:int,AMOUNT1: float,AMOUNT2:float,AMOUNT3:float -008, 10.05, 15.45, 89.99 -100, 20.25, 25.25, 45.25 -105, 20.05, 25.05, 45.05 -200, 34.05, 25.05, 75.05 \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestFilterCSVColumns/US500.csv b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestFilterCSVColumns/US500.csv deleted file mode 100644 index 61ce4bd63..000000000 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestFilterCSVColumns/US500.csv +++ /dev/null @@ -1 +0,0 @@ -FIRST_NAME:string,LAST_NAME,COMPANY_NAME,ADDRESS,CITY,COUNTY,STATE,zip,phone1,phone2,email,web "James","Butt","Benton, John B Jr","6649 N Blue Gum St","New Orleans","Orleans","LA",70116,"504-621-8927","504-845-1427","jbutt@gmail.com","http://www.bentonjohnbjr.com" "Josephine","Darakjy","Chanay, Jeffrey A Esq","4 B Blue Ridge Blvd","Brighton","Livingston","MI",48116,"810-292-9388","810-374-9840","josephine_darakjy@darakjy.org","http://www.chanayjeffreyaesq.com" "Art","Venere","Chemel, James L Cpa","8 W Cerritos Ave #54","Bridgeport","Gloucester","NJ","08014","856-636-8749","856-264-4130","art@venere.org","http://www.chemeljameslcpa.com" "Lenna","Paprocki","Feltz Printing Service","639 Main St","Anchorage","Anchorage","AK",99501,"907-385-4412","907-921-2010","lpaprocki@hotmail.com","http://www.feltzprintingservice.com" "Donette","Foller","Printing Dimensions","34 Center St","Hamilton","Butler","OH",45011,"513-570-1893","513-549-4561","donette.foller@cox.net","http://www.printingdimensions.com" "Simona","Morasca","Chapman, Ross E Esq","3 Mcauley Dr","Ashland","Ashland","OH",44805,"419-503-2484","419-800-6759","simona@morasca.com","http://www.chapmanrosseesq.com" "Mitsue","Tollner","Morlong Associates","7 Eads St","Chicago","Cook","IL",60632,"773-573-6914","773-924-8565","mitsue_tollner@yahoo.com","http://www.morlongassociates.com" "Leota","Dilliard","Commercial Press","7 W Jackson Blvd","San Jose","Santa Clara","CA",95111,"408-752-3500","408-813-1105","leota@hotmail.com","http://www.commercialpress.com" "Sage","Wieser","Truhlar And Truhlar Attys","5 Boston Ave #88","Sioux Falls","Minnehaha","SD",57105,"605-414-2147","605-794-4895","sage_wieser@cox.net","http://www.truhlarandtruhlarattys.com" "Kris","Marrier","King, Christopher A Esq","228 Runamuck Pl #2808","Baltimore","Baltimore City","MD",21224,"410-655-8723","410-804-4694","kris@gmail.com","http://www.kingchristopheraesq.com" "Minna","Amigon","Dorl, James J Esq","2371 Jerrold Ave","Kulpsville","Montgomery","PA",19443,"215-874-1229","215-422-8694","minna_amigon@yahoo.com","http://www.dorljamesjesq.com" "Abel","Maclead","Rangoni Of Florence","37275 St Rt 17m M","Middle Island","Suffolk","NY",11953,"631-335-3414","631-677-3675","amaclead@gmail.com","http://www.rangoniofflorence.com" "Kiley","Caldarera","Feiner Bros","25 E 75th St #69","Los Angeles","Los Angeles","CA",90034,"310-498-5651","310-254-3084","kiley.caldarera@aol.com","http://www.feinerbros.com" "Graciela","Ruta","Buckley Miller & Wright","98 Connecticut Ave Nw","Chagrin Falls","Geauga","OH",44023,"440-780-8425","440-579-7763","gruta@cox.net","http://www.buckleymillerwright.com" "Cammy","Albares","Rousseaux, Michael Esq","56 E Morehead St","Laredo","Webb","TX",78045,"956-537-6195","956-841-7216","calbares@gmail.com","http://www.rousseauxmichaelesq.com" "Mattie","Poquette","Century Communications","73 State Road 434 E","Phoenix","Maricopa","AZ",85013,"602-277-4385","602-953-6360","mattie@aol.com","http://www.centurycommunications.com" "Meaghan","Garufi","Bolton, Wilbur Esq","69734 E Carrillo St","Mc Minnville","Warren","TN",37110,"931-313-9635","931-235-7959","meaghan@hotmail.com","http://www.boltonwilburesq.com" "Gladys","Rim","T M Byxbee Company Pc","322 New Horizon Blvd","Milwaukee","Milwaukee","WI",53207,"414-661-9598","414-377-2880","gladys.rim@rim.org","http://www.tmbyxbeecompanypc.com" "Yuki","Whobrey","Farmers Insurance Group","1 State Route 27","Taylor","Wayne","MI",48180,"313-288-7937","313-341-4470","yuki_whobrey@aol.com","http://www.farmersinsurancegroup.com" "Fletcher","Flosi","Post Box Services Plus","394 Manchester Blvd","Rockford","Winnebago","IL",61109,"815-828-2147","815-426-5657","fletcher.flosi@yahoo.com","http://www.postboxservicesplus.com" "Bette","Nicka","Sport En Art","6 S 33rd St","Aston","Delaware","PA",19014,"610-545-3615","610-492-4643","bette_nicka@cox.net","http://www.sportenart.com" "Veronika","Inouye","C 4 Network Inc","6 Greenleaf Ave","San Jose","Santa Clara","CA",95111,"408-540-1785","408-813-4592","vinouye@aol.com","http://www.cnetworkinc.com" "Willard","Kolmetz","Ingalls, Donald R Esq","618 W Yakima Ave","Irving","Dallas","TX",75062,"972-303-9197","972-896-4882","willard@hotmail.com","http://www.ingallsdonaldresq.com" "Maryann","Royster","Franklin, Peter L Esq","74 S Westgate St","Albany","Albany","NY",12204,"518-966-7987","518-448-8982","mroyster@royster.com","http://www.franklinpeterlesq.com" "Alisha","Slusarski","Wtlz Power 107 Fm","3273 State St","Middlesex","Middlesex","NJ","08846","732-658-3154","732-635-3453","alisha@slusarski.com","http://www.wtlzpowerfm.com" "Allene","Iturbide","Ledecky, David Esq","1 Central Ave","Stevens Point","Portage","WI",54481,"715-662-6764","715-530-9863","allene_iturbide@cox.net","http://www.ledeckydavidesq.com" "Chanel","Caudy","Professional Image Inc","86 Nw 66th St #8673","Shawnee","Johnson","KS",66218,"913-388-2079","913-899-1103","chanel.caudy@caudy.org","http://www.professionalimageinc.com" "Ezekiel","Chui","Sider, Donald C Esq","2 Cedar Ave #84","Easton","Talbot","MD",21601,"410-669-1642","410-235-8738","ezekiel@chui.com","http://www.siderdonaldcesq.com" "Willow","Kusko","U Pull It","90991 Thorburn Ave","New York","New York","NY",10011,"212-582-4976","212-934-5167","wkusko@yahoo.com","http://www.upullit.com" "Bernardo","Figeroa","Clark, Richard Cpa","386 9th Ave N","Conroe","Montgomery","TX",77301,"936-336-3951","936-597-3614","bfigeroa@aol.com","http://www.clarkrichardcpa.com" "Ammie","Corrio","Moskowitz, Barry S","74874 Atlantic Ave","Columbus","Franklin","OH",43215,"614-801-9788","614-648-3265","ammie@corrio.com","http://www.moskowitzbarrys.com" "Francine","Vocelka","Cascade Realty Advisors Inc","366 South Dr","Las Cruces","Dona Ana","NM",88011,"505-977-3911","505-335-5293","francine_vocelka@vocelka.com","http://www.cascaderealtyadvisorsinc.com" "Ernie","Stenseth","Knwz Newsradio","45 E Liberty St","Ridgefield Park","Bergen","NJ","07660","201-709-6245","201-387-9093","ernie_stenseth@aol.com","http://www.knwznewsradio.com" "Albina","Glick","Giampetro, Anthony D","4 Ralph Ct","Dunellen","Middlesex","NJ","08812","732-924-7882","732-782-6701","albina@glick.com","http://www.giampetroanthonyd.com" "Alishia","Sergi","Milford Enterprises Inc","2742 Distribution Way","New York","New York","NY",10025,"212-860-1579","212-753-2740","asergi@gmail.com","http://www.milfordenterprisesinc.com" "Solange","Shinko","Mosocco, Ronald A","426 Wolf St","Metairie","Jefferson","LA",70002,"504-979-9175","504-265-8174","solange@shinko.com","http://www.mosoccoronalda.com" "Jose","Stockham","Tri State Refueler Co","128 Bransten Rd","New York","New York","NY",10011,"212-675-8570","212-569-4233","jose@yahoo.com","http://www.tristaterefuelerco.com" "Rozella","Ostrosky","Parkway Company","17 Morena Blvd","Camarillo","Ventura","CA",93012,"805-832-6163","805-609-1531","rozella.ostrosky@ostrosky.com","http://www.parkwaycompany.com" "Valentine","Gillian","Fbs Business Finance","775 W 17th St","San Antonio","Bexar","TX",78204,"210-812-9597","210-300-6244","valentine_gillian@gmail.com","http://www.fbsbusinessfinance.com" "Kati","Rulapaugh","Eder Assocs Consltng Engrs Pc","6980 Dorsett Rd","Abilene","Dickinson","KS",67410,"785-463-7829","785-219-7724","kati.rulapaugh@hotmail.com","http://www.ederassocsconsltngengrspc.com" "Youlanda","Schemmer","Tri M Tool Inc","2881 Lewis Rd","Prineville","Crook","OR",97754,"541-548-8197","541-993-2611","youlanda@aol.com","http://www.trimtoolinc.com" "Dyan","Oldroyd","International Eyelets Inc","7219 Woodfield Rd","Overland Park","Johnson","KS",66204,"913-413-4604","913-645-8918","doldroyd@aol.com","http://www.internationaleyeletsinc.com" "Roxane","Campain","Rapid Trading Intl","1048 Main St","Fairbanks","Fairbanks North Star","AK",99708,"907-231-4722","907-335-6568","roxane@hotmail.com","http://www.rapidtradingintl.com" "Lavera","Perin","Abc Enterprises Inc","678 3rd Ave","Miami","Miami-Dade","FL",33196,"305-606-7291","305-995-2078","lperin@perin.org","http://www.abcenterprisesinc.com" "Erick","Ferencz","Cindy Turner Associates","20 S Babcock St","Fairbanks","Fairbanks North Star","AK",99712,"907-741-1044","907-227-6777","erick.ferencz@aol.com","http://www.cindyturnerassociates.com" "Fatima","Saylors","Stanton, James D Esq","2 Lighthouse Ave","Hopkins","Hennepin","MN",55343,"952-768-2416","952-479-2375","fsaylors@saylors.org","http://www.stantonjamesdesq.com" "Jina","Briddick","Grace Pastries Inc","38938 Park Blvd","Boston","Suffolk","MA","02128","617-399-5124","617-997-5771","jina_briddick@briddick.com","http://www.gracepastriesinc.com" "Kanisha","Waycott","Schroer, Gene E Esq","5 Tomahawk Dr","Los Angeles","Los Angeles","CA",90006,"323-453-2780","323-315-7314","kanisha_waycott@yahoo.com","http://www.schroergeneeesq.com" "Emerson","Bowley","Knights Inn","762 S Main St","Madison","Dane","WI",53711,"608-336-7444","608-658-7940","emerson.bowley@bowley.org","http://www.knightsinn.com" "Blair","Malet","Bollinger Mach Shp & Shipyard","209 Decker Dr","Philadelphia","Philadelphia","PA",19132,"215-907-9111","215-794-4519","bmalet@yahoo.com","http://www.bollingermachshpshipyard.com" "Brock","Bolognia","Orinda News","4486 W O St #1","New York","New York","NY",10003,"212-402-9216","212-617-5063","bbolognia@yahoo.com","http://www.orindanews.com" "Lorrie","Nestle","Ballard Spahr Andrews","39 S 7th St","Tullahoma","Coffee","TN",37388,"931-875-6644","931-303-6041","lnestle@hotmail.com","http://www.ballardspahrandrews.com" "Sabra","Uyetake","Lowy Limousine Service","98839 Hawthorne Blvd #6101","Columbia","Richland","SC",29201,"803-925-5213","803-681-3678","sabra@uyetake.org","http://www.lowylimousineservice.com" "Marjory","Mastella","Vicon Corporation","71 San Mateo Ave","Wayne","Delaware","PA",19087,"610-814-5533","610-379-7125","mmastella@mastella.com","http://www.viconcorporation.com" "Karl","Klonowski","Rossi, Michael M","76 Brooks St #9","Flemington","Hunterdon","NJ","08822","908-877-6135","908-470-4661","karl_klonowski@yahoo.com","http://www.rossimichaelm.com" "Tonette","Wenner","Northwest Publishing","4545 Courthouse Rd","Westbury","Nassau","NY",11590,"516-968-6051","516-333-4861","twenner@aol.com","http://www.northwestpublishing.com" "Amber","Monarrez","Branford Wire & Mfg Co","14288 Foster Ave #4121","Jenkintown","Montgomery","PA",19046,"215-934-8655","215-329-6386","amber_monarrez@monarrez.org","http://www.branfordwiremfgco.com" "Shenika","Seewald","East Coast Marketing","4 Otis St","Van Nuys","Los Angeles","CA",91405,"818-423-4007","818-749-8650","shenika@gmail.com","http://www.eastcoastmarketing.com" "Delmy","Ahle","Wye Technologies Inc","65895 S 16th St","Providence","Providence","RI","02909","401-458-2547","401-559-8961","delmy.ahle@hotmail.com","http://www.wyetechnologiesinc.com" "Deeanna","Juhas","Healy, George W Iv","14302 Pennsylvania Ave","Huntingdon Valley","Montgomery","PA",19006,"215-211-9589","215-417-9563","deeanna_juhas@gmail.com","http://www.healygeorgewiv.com" "Blondell","Pugh","Alpenlite Inc","201 Hawk Ct","Providence","Providence","RI","02904","401-960-8259","401-300-8122","bpugh@aol.com","http://www.alpenliteinc.com" "Jamal","Vanausdal","Hubbard, Bruce Esq","53075 Sw 152nd Ter #615","Monroe Township","Middlesex","NJ","08831","732-234-1546","732-904-2931","jamal@vanausdal.org","http://www.hubbardbruceesq.com" "Cecily","Hollack","Arthur A Oliver & Son Inc","59 N Groesbeck Hwy","Austin","Travis","TX",78731,"512-486-3817","512-861-3814","cecily@hollack.org","http://www.arthuraoliversoninc.com" "Carmelina","Lindall","George Jessop Carter Jewelers","2664 Lewis Rd","Littleton","Douglas","CO",80126,"303-724-7371","303-874-5160","carmelina_lindall@lindall.com","http://www.georgejessopcarterjewelers.com" "Maurine","Yglesias","Schultz, Thomas C Md","59 Shady Ln #53","Milwaukee","Milwaukee","WI",53214,"414-748-1374","414-573-7719","maurine_yglesias@yglesias.com","http://www.schultzthomascmd.com" "Tawna","Buvens","H H H Enterprises Inc","3305 Nabell Ave #679","New York","New York","NY",10009,"212-674-9610","212-462-9157","tawna@gmail.com","http://www.hhhenterprisesinc.com" "Penney","Weight","Hawaiian King Hotel","18 Fountain St","Anchorage","Anchorage","AK",99515,"907-797-9628","907-873-2882","penney_weight@aol.com","http://www.hawaiiankinghotel.com" "Elly","Morocco","Killion Industries","7 W 32nd St","Erie","Erie","PA",16502,"814-393-5571","814-420-3553","elly_morocco@gmail.com","http://www.killionindustries.com" "Ilene","Eroman","Robinson, William J Esq","2853 S Central Expy","Glen Burnie","Anne Arundel","MD",21061,"410-914-9018","410-937-4543","ilene.eroman@hotmail.com","http://www.robinsonwilliamjesq.com" "Vallie","Mondella","Private Properties","74 W College St","Boise","Ada","ID",83707,"208-862-5339","208-737-8439","vmondella@mondella.com","http://www.privateproperties.com" "Kallie","Blackwood","Rowley Schlimgen Inc","701 S Harrison Rd","San Francisco","San Francisco","CA",94104,"415-315-2761","415-604-7609","kallie.blackwood@gmail.com","http://www.rowleyschlimgeninc.com" "Johnetta","Abdallah","Forging Specialties","1088 Pinehurst St","Chapel Hill","Orange","NC",27514,"919-225-9345","919-715-3791","johnetta_abdallah@aol.com","http://www.forgingspecialties.com" "Bobbye","Rhym","Smits, Patricia Garity","30 W 80th St #1995","San Carlos","San Mateo","CA",94070,"650-528-5783","650-811-9032","brhym@rhym.com","http://www.smitspatriciagarity.com" "Micaela","Rhymes","H Lee Leonard Attorney At Law","20932 Hedley St","Concord","Contra Costa","CA",94520,"925-647-3298","925-522-7798","micaela_rhymes@gmail.com","http://www.hleeleonardattorneyatlaw.com" "Tamar","Hoogland","A K Construction Co","2737 Pistorio Rd #9230","London","Madison","OH",43140,"740-343-8575","740-526-5410","tamar@hotmail.com","http://www.akconstructionco.com" "Moon","Parlato","Ambelang, Jessica M Md","74989 Brandon St","Wellsville","Allegany","NY",14895,"585-866-8313","585-498-4278","moon@yahoo.com","http://www.ambelangjessicammd.com" "Laurel","Reitler","Q A Service","6 Kains Ave","Baltimore","Baltimore City","MD",21215,"410-520-4832","410-957-6903","laurel_reitler@reitler.com","http://www.qaservice.com" "Delisa","Crupi","Wood & Whitacre Contractors","47565 W Grand Ave","Newark","Essex","NJ","07105","973-354-2040","973-847-9611","delisa.crupi@crupi.com","http://www.woodwhitacrecontractors.com" "Viva","Toelkes","Mark Iv Press Ltd","4284 Dorigo Ln","Chicago","Cook","IL",60647,"773-446-5569","773-352-3437","viva.toelkes@gmail.com","http://www.markivpressltd.com" "Elza","Lipke","Museum Of Science & Industry","6794 Lake Dr E","Newark","Essex","NJ","07104","973-927-3447","973-796-3667","elza@yahoo.com","http://www.museumofscienceindustry.com" "Devorah","Chickering","Garrison Ind","31 Douglas Blvd #950","Clovis","Curry","NM",88101,"505-975-8559","505-950-1763","devorah@hotmail.com","http://www.garrisonind.com" "Timothy","Mulqueen","Saronix Nymph Products","44 W 4th St","Staten Island","Richmond","NY",10309,"718-332-6527","718-654-7063","timothy_mulqueen@mulqueen.org","http://www.saronixnymphproducts.com" "Arlette","Honeywell","Smc Inc","11279 Loytan St","Jacksonville","Duval","FL",32254,"904-775-4480","904-514-9918","ahoneywell@honeywell.com","http://www.smcinc.com" "Dominque","Dickerson","E A I Electronic Assocs Inc","69 Marquette Ave","Hayward","Alameda","CA",94545,"510-993-3758","510-901-7640","dominque.dickerson@dickerson.org","http://www.eaielectronicassocsinc.com" "Lettie","Isenhower","Conte, Christopher A Esq","70 W Main St","Beachwood","Cuyahoga","OH",44122,"216-657-7668","216-733-8494","lettie_isenhower@yahoo.com","http://www.contechristopheraesq.com" "Myra","Munns","Anker Law Office","461 Prospect Pl #316","Euless","Tarrant","TX",76040,"817-914-7518","817-451-3518","mmunns@cox.net","http://www.ankerlawoffice.com" "Stephaine","Barfield","Beutelschies & Company","47154 Whipple Ave Nw","Gardena","Los Angeles","CA",90247,"310-774-7643","310-968-1219","stephaine@barfield.com","http://www.beutelschiescompany.com" "Lai","Gato","Fligg, Kenneth I Jr","37 Alabama Ave","Evanston","Cook","IL",60201,"847-728-7286","847-957-4614","lai.gato@gato.org","http://www.fliggkennethijr.com" "Stephen","Emigh","Sharp, J Daniel Esq","3777 E Richmond St #900","Akron","Summit","OH",44302,"330-537-5358","330-700-2312","stephen_emigh@hotmail.com","http://www.sharpjdanielesq.com" "Tyra","Shields","Assink, Anne H Esq","3 Fort Worth Ave","Philadelphia","Philadelphia","PA",19106,"215-255-1641","215-228-8264","tshields@gmail.com","http://www.assinkannehesq.com" "Tammara","Wardrip","Jewel My Shop Inc","4800 Black Horse Pike","Burlingame","San Mateo","CA",94010,"650-803-1936","650-216-5075","twardrip@cox.net","http://www.jewelmyshopinc.com" "Cory","Gibes","Chinese Translation Resources","83649 W Belmont Ave","San Gabriel","Los Angeles","CA",91776,"626-572-1096","626-696-2777","cory.gibes@gmail.com","http://www.chinesetranslationresources.com" "Danica","Bruschke","Stevens, Charles T","840 15th Ave","Waco","McLennan","TX",76708,"254-782-8569","254-205-1422","danica_bruschke@gmail.com","http://www.stevenscharlest.com" "Wilda","Giguere","Mclaughlin, Luther W Cpa","1747 Calle Amanecer #2","Anchorage","Anchorage","AK",99501,"907-870-5536","907-914-9482","wilda@cox.net","http://www.mclaughlinlutherwcpa.com" "Elvera","Benimadho","Tree Musketeers","99385 Charity St #840","San Jose","Santa Clara","CA",95110,"408-703-8505","408-440-8447","elvera.benimadho@cox.net","http://www.treemusketeers.com" "Carma","Vanheusen","Springfield Div Oh Edison Co","68556 Central Hwy","San Leandro","Alameda","CA",94577,"510-503-7169","510-452-4835","carma@cox.net","http://www.springfielddivohedisonco.com" "Malinda","Hochard","Logan Memorial Hospital","55 Riverside Ave","Indianapolis","Marion","IN",46202,"317-722-5066","317-472-2412","malinda.hochard@yahoo.com","http://www.loganmemorialhospital.com" "Natalie","Fern","Kelly, Charles G Esq","7140 University Ave","Rock Springs","Sweetwater","WY",82901,"307-704-8713","307-279-3793","natalie.fern@hotmail.com","http://www.kellycharlesgesq.com" "Lisha","Centini","Industrial Paper Shredders Inc","64 5th Ave #1153","Mc Lean","Fairfax","VA",22102,"703-235-3937","703-475-7568","lisha@centini.org","http://www.industrialpapershreddersinc.com" "Arlene","Klusman","Beck Horizon Builders","3 Secor Rd","New Orleans","Orleans","LA",70112,"504-710-5840","504-946-1807","arlene_klusman@gmail.com","http://www.beckhorizonbuilders.com" "Alease","Buemi","Porto Cayo At Hawks Cay","4 Webbs Chapel Rd","Boulder","Boulder","CO",80303,"303-301-4946","303-521-9860","alease@buemi.com","http://www.portocayoathawkscay.com" "Louisa","Cronauer","Pacific Grove Museum Ntrl Hist","524 Louisiana Ave Nw","San Leandro","Alameda","CA",94577,"510-828-7047","510-472-7758","louisa@cronauer.com","http://www.pacificgrovemuseumntrlhist.com" "Angella","Cetta","Bender & Hatley Pc","185 Blackstone Bldge","Honolulu","Honolulu","HI",96817,"808-892-7943","808-475-2310","angella.cetta@hotmail.com","http://www.benderhatleypc.com" "Cyndy","Goldammer","Di Cristina J & Son","170 Wyoming Ave","Burnsville","Dakota","MN",55337,"952-334-9408","952-938-9457","cgoldammer@cox.net","http://www.dicristinajson.com" "Rosio","Cork","Green Goddess","4 10th St W","High Point","Guilford","NC",27263,"336-243-5659","336-497-4407","rosio.cork@gmail.com","http://www.greengoddess.com" "Celeste","Korando","American Arts & Graphics","7 W Pinhook Rd","Lynbrook","Nassau","NY",11563,"516-509-2347","516-365-7266","ckorando@hotmail.com","http://www.americanartsgraphics.com" "Twana","Felger","Opryland Hotel","1 Commerce Way","Portland","Washington","OR",97224,"503-939-3153","503-909-7167","twana.felger@felger.org","http://www.oprylandhotel.com" "Estrella","Samu","Marking Devices Pubg Co","64 Lakeview Ave","Beloit","Rock","WI",53511,"608-976-7199","608-942-8836","estrella@aol.com","http://www.markingdevicespubgco.com" "Donte","Kines","W Tc Industries Inc","3 Aspen St","Worcester","Worcester","MA","01602","508-429-8576","508-843-1426","dkines@hotmail.com","http://www.wtcindustriesinc.com" "Tiffiny","Steffensmeier","Whitehall Robbins Labs Divsn","32860 Sierra Rd","Miami","Miami-Dade","FL",33133,"305-385-9695","305-304-6573","tiffiny_steffensmeier@cox.net","http://www.whitehallrobbinslabsdivsn.com" "Edna","Miceli","Sampler","555 Main St","Erie","Erie","PA",16502,"814-460-2655","814-299-2877","emiceli@miceli.org","http://www.sampler.com" "Sue","Kownacki","Juno Chefs Incorporated","2 Se 3rd Ave","Mesquite","Dallas","TX",75149,"972-666-3413","972-742-4000","sue@aol.com","http://www.junochefsincorporated.com" "Jesusa","Shin","Carroccio, A Thomas Esq","2239 Shawnee Mission Pky","Tullahoma","Coffee","TN",37388,"931-273-8709","931-739-1551","jshin@shin.com","http://www.carroccioathomasesq.com" "Rolland","Francescon","Stanley, Richard L Esq","2726 Charcot Ave","Paterson","Passaic","NJ","07501","973-649-2922","973-284-4048","rolland@cox.net","http://www.stanleyrichardlesq.com" "Pamella","Schmierer","K Cs Cstm Mouldings Windows","5161 Dorsett Rd","Homestead","Miami-Dade","FL",33030,"305-420-8970","305-575-8481","pamella.schmierer@schmierer.org","http://www.kcscstmmouldingswindows.com" "Glory","Kulzer","Comfort Inn","55892 Jacksonville Rd","Owings Mills","Baltimore","MD",21117,"410-224-9462","410-916-8015","gkulzer@kulzer.org","http://www.comfortinn.com" "Shawna","Palaspas","Windsor, James L Esq","5 N Cleveland Massillon Rd","Thousand Oaks","Ventura","CA",91362,"805-275-3566","805-638-6617","shawna_palaspas@palaspas.org","http://www.windsorjameslesq.com" "Brandon","Callaro","Jackson Shields Yeiser","7 Benton Dr","Honolulu","Honolulu","HI",96819,"808-215-6832","808-240-5168","brandon_callaro@hotmail.com","http://www.jacksonshieldsyeiser.com" "Scarlet","Cartan","Box, J Calvin Esq","9390 S Howell Ave","Albany","Dougherty","GA",31701,"229-735-3378","229-365-9658","scarlet.cartan@yahoo.com","http://www.boxjcalvinesq.com" "Oretha","Menter","Custom Engineering Inc","8 County Center Dr #647","Boston","Suffolk","MA","02210","617-418-5043","617-697-6024","oretha_menter@yahoo.com","http://www.customengineeringinc.com" "Ty","Smith","Bresler Eitel Framg Gllry Ltd","4646 Kaahumanu St","Hackensack","Bergen","NJ","07601","201-672-1553","201-995-3149","tsmith@aol.com","http://www.breslereitelframggllryltd.com" "Xuan","Rochin","Carol, Drake Sparks Esq","2 Monroe St","San Mateo","San Mateo","CA",94403,"650-933-5072","650-247-2625","xuan@gmail.com","http://www.caroldrakesparksesq.com" "Lindsey","Dilello","Biltmore Investors Bank","52777 Leaders Heights Rd","Ontario","San Bernardino","CA",91761,"909-639-9887","909-589-1693","lindsey.dilello@hotmail.com","http://www.biltmoreinvestorsbank.com" "Devora","Perez","Desco Equipment Corp","72868 Blackington Ave","Oakland","Alameda","CA",94606,"510-955-3016","510-755-9274","devora_perez@perez.org","http://www.descoequipmentcorp.com" "Herman","Demesa","Merlin Electric Co","9 Norristown Rd","Troy","Rensselaer","NY",12180,"518-497-2940","518-931-7852","hdemesa@cox.net","http://www.merlinelectricco.com" "Rory","Papasergi","Bailey Cntl Co Div Babcock","83 County Road 437 #8581","Clarks Summit","Lackawanna","PA",18411,"570-867-7489","570-469-8401","rpapasergi@cox.net","http://www.baileycntlcodivbabcock.com" "Talia","Riopelle","Ford Brothers Wholesale Inc","1 N Harlem Ave #9","Orange","Essex","NJ","07050","973-245-2133","973-818-9788","talia_riopelle@aol.com","http://www.fordbrotherswholesaleinc.com" "Van","Shire","Cambridge Inn","90131 J St","Pittstown","Hunterdon","NJ","08867","908-409-2890","908-448-1209","van.shire@shire.com","http://www.cambridgeinn.com" "Lucina","Lary","Matricciani, Albert J Jr","8597 W National Ave","Cocoa","Brevard","FL",32922,"321-749-4981","321-632-4668","lucina_lary@cox.net","http://www.matriccianialbertjjr.com" "Bok","Isaacs","Nelson Hawaiian Ltd","6 Gilson St","Bronx","Bronx","NY",10468,"718-809-3762","718-478-8568","bok.isaacs@aol.com","http://www.nelsonhawaiianltd.com" "Rolande","Spickerman","Neland Travel Agency","65 W Maple Ave","Pearl City","Honolulu","HI",96782,"808-315-3077","808-526-5863","rolande.spickerman@spickerman.com","http://www.nelandtravelagency.com" "Howard","Paulas","Asendorf, J Alan Esq","866 34th Ave","Denver","Denver","CO",80231,"303-623-4241","303-692-3118","hpaulas@gmail.com","http://www.asendorfjalanesq.com" "Kimbery","Madarang","Silberman, Arthur L Esq","798 Lund Farm Way","Rockaway","Morris","NJ","07866","973-310-1634","973-225-6259","kimbery_madarang@cox.net","http://www.silbermanarthurlesq.com" "Thurman","Manno","Honey Bee Breeding Genetics &","9387 Charcot Ave","Absecon","Atlantic","NJ","08201","609-524-3586","609-234-8376","thurman.manno@yahoo.com","http://www.honeybeebreedinggenetics.com" "Becky","Mirafuentes","Wells Kravitz Schnitzer","30553 Washington Rd","Plainfield","Union","NJ","07062","908-877-8409","908-426-8272","becky.mirafuentes@mirafuentes.com","http://www.wellskravitzschnitzer.com" "Beatriz","Corrington","Prohab Rehabilitation Servs","481 W Lemon St","Middleboro","Plymouth","MA","02346","508-584-4279","508-315-3867","beatriz@yahoo.com","http://www.prohabrehabilitationservs.com" "Marti","Maybury","Eldridge, Kristin K Esq","4 Warehouse Point Rd #7","Chicago","Cook","IL",60638,"773-775-4522","773-539-1058","marti.maybury@yahoo.com","http://www.eldridgekristinkesq.com" "Nieves","Gotter","Vlahos, John J Esq","4940 Pulaski Park Dr","Portland","Multnomah","OR",97202,"503-527-5274","503-455-3094","nieves_gotter@gmail.com","http://www.vlahosjohnjesq.com" "Leatha","Hagele","Ninas Indian Grs & Videos","627 Walford Ave","Dallas","Dallas","TX",75227,"214-339-1809","214-225-5850","lhagele@cox.net","http://www.ninasindiangrsvideos.com" "Valentin","Klimek","Schmid, Gayanne K Esq","137 Pioneer Way","Chicago","Cook","IL",60604,"312-303-5453","312-512-2338","vklimek@klimek.org","http://www.schmidgayannekesq.com" "Melissa","Wiklund","Moapa Valley Federal Credit Un","61 13 Stoneridge #835","Findlay","Hancock","OH",45840,"419-939-3613","419-254-4591","melissa@cox.net","http://www.moapavalleyfederalcreditun.com" "Sheridan","Zane","Kentucky Tennessee Clay Co","2409 Alabama Rd","Riverside","Riverside","CA",92501,"951-645-3605","951-248-6822","sheridan.zane@zane.com","http://www.kentuckytennesseeclayco.com" "Bulah","Padilla","Admiral Party Rentals & Sales","8927 Vandever Ave","Waco","McLennan","TX",76707,"254-463-4368","254-816-8417","bulah_padilla@hotmail.com","http://www.admiralpartyrentalssales.com" "Audra","Kohnert","Nelson, Karolyn King Esq","134 Lewis Rd","Nashville","Davidson","TN",37211,"615-406-7854","615-448-9249","audra@kohnert.com","http://www.nelsonkarolynkingesq.com" "Daren","Weirather","Panasystems","9 N College Ave #3","Milwaukee","Milwaukee","WI",53216,"414-959-2540","414-838-3151","dweirather@aol.com","http://www.panasystems.com" "Fernanda","Jillson","Shank, Edward L Esq","60480 Old Us Highway 51","Preston","Caroline","MD",21655,"410-387-5260","410-724-6472","fjillson@aol.com","http://www.shankedwardlesq.com" "Gearldine","Gellinger","Megibow & Edwards","4 Bloomfield Ave","Irving","Dallas","TX",75061,"972-934-6914","972-821-7118","gearldine_gellinger@gellinger.com","http://www.megibowedwards.com" "Chau","Kitzman","Benoff, Edward Esq","429 Tiger Ln","Beverly Hills","Los Angeles","CA",90212,"310-560-8022","310-969-7230","chau@gmail.com","http://www.benoffedwardesq.com" "Theola","Frey","Woodbridge Free Public Library","54169 N Main St","Massapequa","Nassau","NY",11758,"516-948-5768","516-357-3362","theola_frey@frey.com","http://www.woodbridgefreepubliclibrary.com" "Cheryl","Haroldson","New York Life John Thune","92 Main St","Atlantic City","Atlantic","NJ","08401","609-518-7697","609-263-9243","cheryl@haroldson.org","http://www.newyorklifejohnthune.com" "Laticia","Merced","Alinabal Inc","72 Mannix Dr","Cincinnati","Hamilton","OH",45203,"513-508-7371","513-418-1566","lmerced@gmail.com","http://www.alinabalinc.com" "Carissa","Batman","Poletto, Kim David Esq","12270 Caton Center Dr","Eugene","Lane","OR",97401,"541-326-4074","541-801-5717","carissa.batman@yahoo.com","http://www.polettokimdavidesq.com" "Lezlie","Craghead","Chang, Carolyn Esq","749 W 18th St #45","Smithfield","Johnston","NC",27577,"919-533-3762","919-885-2453","lezlie.craghead@craghead.org","http://www.changcarolynesq.com" "Ozell","Shealy","Silver Bros Inc","8 Industry Ln","New York","New York","NY",10002,"212-332-8435","212-880-8865","oshealy@hotmail.com","http://www.silverbrosinc.com" "Arminda","Parvis","Newtec Inc","1 Huntwood Ave","Phoenix","Maricopa","AZ",85017,"602-906-9419","602-277-3025","arminda@parvis.com","http://www.newtecinc.com" "Reita","Leto","Creative Business Systems","55262 N French Rd","Indianapolis","Marion","IN",46240,"317-234-1135","317-787-5514","reita.leto@gmail.com","http://www.creativebusinesssystems.com" "Yolando","Luczki","Dal Tile Corporation","422 E 21st St","Syracuse","Onondaga","NY",13214,"315-304-4759","315-640-6357","yolando@cox.net","http://www.daltilecorporation.com" "Lizette","Stem","Edward S Katz","501 N 19th Ave","Cherry Hill","Camden","NJ","08002","856-487-5412","856-702-3676","lizette.stem@aol.com","http://www.edwardskatz.com" "Gregoria","Pawlowicz","Oh My Goodknits Inc","455 N Main Ave","Garden City","Nassau","NY",11530,"516-212-1915","516-376-4230","gpawlowicz@yahoo.com","http://www.ohmygoodknitsinc.com" "Carin","Deleo","Redeker, Debbie","1844 Southern Blvd","Little Rock","Pulaski","AR",72202,"501-308-1040","501-409-6072","cdeleo@deleo.com","http://www.redekerdebbie.com" "Chantell","Maynerich","Desert Sands Motel","2023 Greg St","Saint Paul","Ramsey","MN",55101,"651-591-2583","651-776-9688","chantell@yahoo.com","http://www.desertsandsmotel.com" "Dierdre","Yum","Cummins Southern Plains Inc","63381 Jenks Ave","Philadelphia","Philadelphia","PA",19134,"215-325-3042","215-346-4666","dyum@yahoo.com","http://www.cumminssouthernplainsinc.com" "Larae","Gudroe","Lehigh Furn Divsn Lehigh","6651 Municipal Rd","Houma","Terrebonne","LA",70360,"985-890-7262","985-261-5783","larae_gudroe@gmail.com","http://www.lehighfurndivsnlehigh.com" "Latrice","Tolfree","United Van Lines Agent","81 Norris Ave #525","Ronkonkoma","Suffolk","NY",11779,"631-957-7624","631-998-2102","latrice.tolfree@hotmail.com","http://www.unitedvanlinesagent.com" "Kerry","Theodorov","Capitol Reporters","6916 W Main St","Sacramento","Sacramento","CA",95827,"916-591-3277","916-770-7448","kerry.theodorov@gmail.com","http://www.capitolreporters.com" "Dorthy","Hidvegi","Kwik Kopy Printing","9635 S Main St","Boise","Ada","ID",83704,"208-649-2373","208-690-3315","dhidvegi@yahoo.com","http://www.kwikkopyprinting.com" "Fannie","Lungren","Centro Inc","17 Us Highway 111","Round Rock","Williamson","TX",78664,"512-587-5746","512-528-9933","fannie.lungren@yahoo.com","http://www.centroinc.com" "Evangelina","Radde","Campbell, Jan Esq","992 Civic Center Dr","Philadelphia","Philadelphia","PA",19123,"215-964-3284","215-417-5612","evangelina@aol.com","http://www.campbelljanesq.com" "Novella","Degroot","Evans, C Kelly Esq","303 N Radcliffe St","Hilo","Hawaii","HI",96720,"808-477-4775","808-746-1865","novella_degroot@degroot.org","http://www.evansckellyesq.com" "Clay","Hoa","Scat Enterprises","73 Saint Ann St #86","Reno","Washoe","NV",89502,"775-501-8109","775-848-9135","choa@hoa.org","http://www.scatenterprises.com" "Jennifer","Fallick","Nagle, Daniel J Esq","44 58th St","Wheeling","Cook","IL",60090,"847-979-9545","847-800-3054","jfallick@yahoo.com","http://www.nagledanieljesq.com" "Irma","Wolfgramm","Serendiquity Bed & Breakfast","9745 W Main St","Randolph","Morris","NJ","07869","973-545-7355","973-868-8660","irma.wolfgramm@hotmail.com","http://www.serendiquitybedbreakfast.com" "Eun","Coody","Ray Carolyne Realty","84 Bloomfield Ave","Spartanburg","Spartanburg","SC",29301,"864-256-3620","864-594-4578","eun@yahoo.com","http://www.raycarolynerealty.com" "Sylvia","Cousey","Berg, Charles E","287 Youngstown Warren Rd","Hampstead","Carroll","MD",21074,"410-209-9545","410-863-8263","sylvia_cousey@cousey.org","http://www.bergcharlese.com" "Nana","Wrinkles","Ray, Milbern D","6 Van Buren St","Mount Vernon","Westchester","NY",10553,"914-855-2115","914-796-3775","nana@aol.com","http://www.raymilbernd.com" "Layla","Springe","Chadds Ford Winery","229 N Forty Driv","New York","New York","NY",10011,"212-260-3151","212-253-7448","layla.springe@cox.net","http://www.chaddsfordwinery.com" "Joesph","Degonia","A R Packaging","2887 Knowlton St #5435","Berkeley","Alameda","CA",94710,"510-677-9785","510-942-5916","joesph_degonia@degonia.org","http://www.arpackaging.com" "Annabelle","Boord","Corn Popper","523 Marquette Ave","Concord","Middlesex","MA","01742","978-697-6263","978-289-7717","annabelle.boord@cox.net","http://www.cornpopper.com" "Stephaine","Vinning","Birite Foodservice Distr","3717 Hamann Industrial Pky","San Francisco","San Francisco","CA",94104,"415-767-6596","415-712-9530","stephaine@cox.net","http://www.biritefoodservicedistr.com" "Nelida","Sawchuk","Anchorage Museum Of Hist & Art","3 State Route 35 S","Paramus","Bergen","NJ","07652","201-971-1638","201-247-8925","nelida@gmail.com","http://www.anchoragemuseumofhistart.com" "Marguerita","Hiatt","Haber, George D Md","82 N Highway 67","Oakley","Contra Costa","CA",94561,"925-634-7158","925-541-8521","marguerita.hiatt@gmail.com","http://www.habergeorgedmd.com" "Carmela","Cookey","Royal Pontiac Olds Inc","9 Murfreesboro Rd","Chicago","Cook","IL",60623,"773-494-4195","773-297-9391","ccookey@cookey.org","http://www.royalpontiacoldsinc.com" "Junita","Brideau","Leonards Antiques Inc","6 S Broadway St","Cedar Grove","Essex","NJ","07009","973-943-3423","973-582-5469","jbrideau@aol.com","http://www.leonardsantiquesinc.com" "Claribel","Varriano","Meca","6 Harry L Dr #6327","Perrysburg","Wood","OH",43551,"419-544-4900","419-573-2033","claribel_varriano@cox.net","http://www.meca.com" "Benton","Skursky","Nercon Engineering & Mfg Inc","47939 Porter Ave","Gardena","Los Angeles","CA",90248,"310-579-2907","310-694-8466","benton.skursky@aol.com","http://www.nerconengineeringmfginc.com" "Hillary","Skulski","Replica I","9 Wales Rd Ne #914","Homosassa","Citrus","FL",34448,"352-242-2570","352-990-5946","hillary.skulski@aol.com","http://www.replicai.com" "Merilyn","Bayless","20 20 Printing Inc","195 13n N","Santa Clara","Santa Clara","CA",95054,"408-758-5015","408-346-2180","merilyn_bayless@cox.net","http://www.printinginc.com" "Teri","Ennaco","Publishers Group West","99 Tank Farm Rd","Hazleton","Luzerne","PA",18201,"570-889-5187","570-355-1665","tennaco@gmail.com","http://www.publishersgroupwest.com" "Merlyn","Lawler","Nischwitz, Jeffrey L Esq","4671 Alemany Blvd","Jersey City","Hudson","NJ","07304","201-588-7810","201-858-9960","merlyn_lawler@hotmail.com","http://www.nischwitzjeffreylesq.com" "Georgene","Montezuma","Payne Blades & Wellborn Pa","98 University Dr","San Ramon","Contra Costa","CA",94583,"925-615-5185","925-943-3449","gmontezuma@cox.net","http://www.paynebladeswellbornpa.com" "Jettie","Mconnell","Coldwell Bnkr Wright Real Est","50 E Wacker Dr","Bridgewater","Somerset","NJ","08807","908-802-3564","908-602-5258","jmconnell@hotmail.com","http://www.coldwellbnkrwrightrealest.com" "Lemuel","Latzke","Computer Repair Service","70 Euclid Ave #722","Bohemia","Suffolk","NY",11716,"631-748-6479","631-291-4976","lemuel.latzke@gmail.com","http://www.computerrepairservice.com" "Melodie","Knipp","Fleetwood Building Block Inc","326 E Main St #6496","Thousand Oaks","Ventura","CA",91362,"805-690-1682","805-810-8964","mknipp@gmail.com","http://www.fleetwoodbuildingblockinc.com" "Candida","Corbley","Colts Neck Medical Assocs Inc","406 Main St","Somerville","Somerset","NJ","08876","908-275-8357","908-943-6103","candida_corbley@hotmail.com","http://www.coltsneckmedicalassocsinc.com" "Karan","Karpin","New England Taxidermy","3 Elmwood Dr","Beaverton","Washington","OR",97005,"503-940-8327","503-707-5812","karan_karpin@gmail.com","http://www.newenglandtaxidermy.com" "Andra","Scheyer","Ludcke, George O Esq","9 Church St","Salem","Marion","OR",97302,"503-516-2189","503-950-3068","andra@gmail.com","http://www.ludckegeorgeoesq.com" "Felicidad","Poullion","Mccorkle, Tom S Esq","9939 N 14th St","Riverton","Burlington","NJ","08077","856-305-9731","856-828-6021","fpoullion@poullion.com","http://www.mccorkletomsesq.com" "Belen","Strassner","Eagle Software Inc","5384 Southwyck Blvd","Douglasville","Douglas","GA",30135,"770-507-8791","770-802-4003","belen_strassner@aol.com","http://www.eaglesoftwareinc.com" "Gracia","Melnyk","Juvenile & Adult Super","97 Airport Loop Dr","Jacksonville","Duval","FL",32216,"904-235-3633","904-627-4341","gracia@melnyk.com","http://www.juvenileadultsuper.com" "Jolanda","Hanafan","Perez, Joseph J Esq","37855 Nolan Rd","Bangor","Penobscot","ME","04401","207-458-9196","207-233-6185","jhanafan@gmail.com","http://www.perezjosephjesq.com" "Barrett","Toyama","Case Foundation Co","4252 N Washington Ave #9","Kennedale","Tarrant","TX",76060,"817-765-5781","817-577-6151","barrett.toyama@toyama.org","http://www.casefoundationco.com" "Helga","Fredicks","Eis Environmental Engrs Inc","42754 S Ash Ave","Buffalo","Erie","NY",14228,"716-752-4114","716-854-9845","helga_fredicks@yahoo.com","http://www.eisenvironmentalengrsinc.com" "Ashlyn","Pinilla","Art Crafters","703 Beville Rd","Opa Locka","Miami-Dade","FL",33054,"305-670-9628","305-857-5489","apinilla@cox.net","http://www.artcrafters.com" "Fausto","Agramonte","Marriott Hotels Resorts Suites","5 Harrison Rd","New York","New York","NY",10038,"212-313-1783","212-778-3063","fausto_agramonte@yahoo.com","http://www.marriotthotelsresortssuites.com" "Ronny","Caiafa","Remaco Inc","73 Southern Blvd","Philadelphia","Philadelphia","PA",19103,"215-605-7570","215-511-3531","ronny.caiafa@caiafa.org","http://www.remacoinc.com" "Marge","Limmel","Bjork, Robert D Jr","189 Village Park Rd","Crestview","Okaloosa","FL",32536,"850-430-1663","850-330-8079","marge@gmail.com","http://www.bjorkrobertdjr.com" "Norah","Waymire","Carmichael, Jeffery L Esq","6 Middlegate Rd #106","San Francisco","San Francisco","CA",94107,"415-306-7897","415-874-2984","norah.waymire@gmail.com","http://www.carmichaeljefferylesq.com" "Aliza","Baltimore","Andrews, J Robert Esq","1128 Delaware St","San Jose","Santa Clara","CA",95132,"408-504-3552","408-425-1994","aliza@aol.com","http://www.andrewsjrobertesq.com" "Mozell","Pelkowski","Winship & Byrne","577 Parade St","South San Francisco","San Mateo","CA",94080,"650-947-1215","650-960-1069","mpelkowski@pelkowski.org","http://www.winshipbyrne.com" "Viola","Bitsuie","Burton & Davis","70 Mechanic St","Northridge","Los Angeles","CA",91325,"818-864-4875","818-481-5787","viola@gmail.com","http://www.burtondavis.com" "Franklyn","Emard","Olympic Graphic Arts","4379 Highway 116","Philadelphia","Philadelphia","PA",19103,"215-558-8189","215-483-3003","femard@emard.com","http://www.olympicgraphicarts.com" "Willodean","Konopacki","Magnuson","55 Hawthorne Blvd","Lafayette","Lafayette","LA",70506,"337-253-8384","337-774-7564","willodean_konopacki@konopacki.org","http://www.magnuson.com" "Beckie","Silvestrini","A All American Travel Inc","7116 Western Ave","Dearborn","Wayne","MI",48126,"313-533-4884","313-390-7855","beckie.silvestrini@silvestrini.com","http://www.aallamericantravelinc.com" "Rebecka","Gesick","Polykote Inc","2026 N Plankinton Ave #3","Austin","Travis","TX",78754,"512-213-8574","512-693-8345","rgesick@gesick.org","http://www.polykoteinc.com" "Frederica","Blunk","Jets Cybernetics","99586 Main St","Dallas","Dallas","TX",75207,"214-428-2285","214-529-1949","frederica_blunk@gmail.com","http://www.jetscybernetics.com" "Glen","Bartolet","Metlab Testing Services","8739 Hudson St","Vashon","King","WA",98070,"206-697-5796","206-389-1482","glen_bartolet@hotmail.com","http://www.metlabtestingservices.com" "Freeman","Gochal","Kellermann, William T Esq","383 Gunderman Rd #197","Coatesville","Chester","PA",19320,"610-476-3501","610-752-2683","freeman_gochal@aol.com","http://www.kellermannwilliamtesq.com" "Vincent","Meinerding","Arturi, Peter D Esq","4441 Point Term Mkt","Philadelphia","Philadelphia","PA",19143,"215-372-1718","215-829-4221","vincent.meinerding@hotmail.com","http://www.arturipeterdesq.com" "Rima","Bevelacqua","Mcauley Mfg Co","2972 Lafayette Ave","Gardena","Los Angeles","CA",90248,"310-858-5079","310-499-4200","rima@cox.net","http://www.mcauleymfgco.com" "Glendora","Sarbacher","Defur Voran Hanley Radcliff","2140 Diamond Blvd","Rohnert Park","Sonoma","CA",94928,"707-653-8214","707-881-3154","gsarbacher@gmail.com","http://www.defurvoranhanleyradcliff.com" "Avery","Steier","Dill Dill Carr & Stonbraker Pc","93 Redmond Rd #492","Orlando","Orange","FL",32803,"407-808-9439","407-945-8566","avery@cox.net","http://www.dilldillcarrstonbrakerpc.com" "Cristy","Lother","Kleensteel","3989 Portage Tr","Escondido","San Diego","CA",92025,"760-971-4322","760-465-4762","cristy@lother.com","http://www.kleensteel.com" "Nicolette","Brossart","Goulds Pumps Inc Slurry Pump","1 Midway Rd","Westborough","Worcester","MA","01581","508-837-9230","508-504-6388","nicolette_brossart@brossart.com","http://www.gouldspumpsincslurrypump.com" "Tracey","Modzelewski","Kansas City Insurance Report","77132 Coon Rapids Blvd Nw","Conroe","Montgomery","TX",77301,"936-264-9294","936-988-8171","tracey@hotmail.com","http://www.kansascityinsurancereport.com" "Virgina","Tegarden","Berhanu International Foods","755 Harbor Way","Milwaukee","Milwaukee","WI",53226,"414-214-8697","414-411-5744","virgina_tegarden@tegarden.com","http://www.berhanuinternationalfoods.com" "Tiera","Frankel","Roland Ashcroft","87 Sierra Rd","El Monte","Los Angeles","CA",91731,"626-636-4117","626-638-4241","tfrankel@aol.com","http://www.rolandashcroft.com" "Alaine","Bergesen","Hispanic Magazine","7667 S Hulen St #42","Yonkers","Westchester","NY",10701,"914-300-9193","914-654-1426","alaine_bergesen@cox.net","http://www.hispanicmagazine.com" "Earleen","Mai","Little Sheet Metal Co","75684 S Withlapopka Dr #32","Dallas","Dallas","TX",75227,"214-289-1973","214-785-6750","earleen_mai@cox.net","http://www.littlesheetmetalco.com" "Leonida","Gobern","Holmes, Armstead J Esq","5 Elmwood Park Blvd","Biloxi","Harrison","MS",39530,"228-235-5615","228-432-4635","leonida@gobern.org","http://www.holmesarmsteadjesq.com" "Ressie","Auffrey","Faw, James C Cpa","23 Palo Alto Sq","Miami","Miami-Dade","FL",33134,"305-604-8981","305-287-4743","ressie.auffrey@yahoo.com","http://www.fawjamesccpa.com" "Justine","Mugnolo","Evans Rule Company","38062 E Main St","New York","New York","NY",10048,"212-304-9225","212-311-6377","jmugnolo@yahoo.com","http://www.evansrulecompany.com" "Eladia","Saulter","Tyee Productions Inc","3958 S Dupont Hwy #7","Ramsey","Bergen","NJ","07446","201-474-4924","201-365-8698","eladia@saulter.com","http://www.tyeeproductionsinc.com" "Chaya","Malvin","Dunnells & Duvall","560 Civic Center Dr","Ann Arbor","Washtenaw","MI",48103,"734-928-5182","734-408-8174","chaya@malvin.com","http://www.dunnellsduvall.com" "Gwenn","Suffield","Deltam Systems Inc","3270 Dequindre Rd","Deer Park","Suffolk","NY",11729,"631-258-6558","631-295-9879","gwenn_suffield@suffield.org","http://www.deltamsystemsinc.com" "Salena","Karpel","Hammill Mfg Co","1 Garfield Ave #7","Canton","Stark","OH",44707,"330-791-8557","330-618-2579","skarpel@cox.net","http://www.hammillmfgco.com" "Yoko","Fishburne","Sams Corner Store","9122 Carpenter Ave","New Haven","New Haven","CT","06511","203-506-4706","203-840-8634","yoko@fishburne.com","http://www.samscornerstore.com" "Taryn","Moyd","Siskin, Mark J Esq","48 Lenox St","Fairfax","Fairfax City","VA",22030,"703-322-4041","703-938-7939","taryn.moyd@hotmail.com","http://www.siskinmarkjesq.com" "Katina","Polidori","Cape & Associates Real Estate","5 Little River Tpke","Wilmington","Middlesex","MA","01887","978-626-2978","978-679-7429","katina_polidori@aol.com","http://www.capeassociatesrealestate.com" "Rickie","Plumer","Merrill Lynch","3 N Groesbeck Hwy","Toledo","Lucas","OH",43613,"419-693-1334","419-313-5571","rickie.plumer@aol.com","http://www.merrilllynch.com" "Alex","Loader","Sublett, Scott Esq","37 N Elm St #916","Tacoma","Pierce","WA",98409,"253-660-7821","253-875-9222","alex@loader.com","http://www.sublettscottesq.com" "Lashon","Vizarro","Sentry Signs","433 Westminster Blvd #590","Roseville","Placer","CA",95661,"916-741-7884","916-289-4526","lashon@aol.com","http://www.sentrysigns.com" "Lauran","Burnard","Professionals Unlimited","66697 Park Pl #3224","Riverton","Fremont","WY",82501,"307-342-7795","307-453-7589","lburnard@burnard.com","http://www.professionalsunlimited.com" "Ceola","Setter","Southern Steel Shelving Co","96263 Greenwood Pl","Warren","Knox","ME","04864","207-627-7565","207-297-5029","ceola.setter@setter.org","http://www.southernsteelshelvingco.com" "My","Rantanen","Bosco, Paul J","8 Mcarthur Ln","Richboro","Bucks","PA",18954,"215-491-5633","215-647-2158","my@hotmail.com","http://www.boscopaulj.com" "Lorrine","Worlds","Longo, Nicholas J Esq","8 Fair Lawn Ave","Tampa","Hillsborough","FL",33614,"813-769-2939","813-863-6467","lorrine.worlds@worlds.com","http://www.longonicholasjesq.com" "Peggie","Sturiale","Henry County Middle School","9 N 14th St","El Cajon","San Diego","CA",92020,"619-608-1763","619-695-8086","peggie@cox.net","http://www.henrycountymiddleschool.com" "Marvel","Raymo","Edison Supply & Equipment Co","9 Vanowen St","College Station","Brazos","TX",77840,"979-718-8968","979-809-5770","mraymo@yahoo.com","http://www.edisonsupplyequipmentco.com" "Daron","Dinos","Wolf, Warren R Esq","18 Waterloo Geneva Rd","Highland Park","Lake","IL",60035,"847-233-3075","847-265-6609","daron_dinos@cox.net","http://www.wolfwarrenresq.com" "An","Fritz","Linguistic Systems Inc","506 S Hacienda Dr","Atlantic City","Atlantic","NJ","08401","609-228-5265","609-854-7156","an_fritz@hotmail.com","http://www.linguisticsystemsinc.com" "Portia","Stimmel","Peace Christian Center","3732 Sherman Ave","Bridgewater","Somerset","NJ","08807","908-722-7128","908-670-4712","portia.stimmel@aol.com","http://www.peacechristiancenter.com" "Rhea","Aredondo","Double B Foods Inc","25657 Live Oak St","Brooklyn","Kings","NY",11226,"718-560-9537","718-280-4183","rhea_aredondo@cox.net","http://www.doublebfoodsinc.com" "Benedict","Sama","Alexander & Alexander Inc","4923 Carey Ave","Saint Louis","Saint Louis City","MO",63104,"314-787-1588","314-858-4832","bsama@cox.net","http://www.alexanderalexanderinc.com" "Alyce","Arias","Fairbanks Scales","3196 S Rider Trl","Stockton","San Joaquin","CA",95207,"209-317-1801","209-242-7022","alyce@arias.org","http://www.fairbanksscales.com" "Heike","Berganza","Cali Sportswear Cutting Dept","3 Railway Ave #75","Little Falls","Passaic","NJ","07424","973-936-5095","973-822-8827","heike@gmail.com","http://www.calisportswearcuttingdept.com" "Carey","Dopico","Garofani, John Esq","87393 E Highland Rd","Indianapolis","Marion","IN",46220,"317-578-2453","317-441-5848","carey_dopico@dopico.org","http://www.garofanijohnesq.com" "Dottie","Hellickson","Thompson Fabricating Co","67 E Chestnut Hill Rd","Seattle","King","WA",98133,"206-540-6076","206-295-5631","dottie@hellickson.org","http://www.thompsonfabricatingco.com" "Deandrea","Hughey","Century 21 Krall Real Estate","33 Lewis Rd #46","Burlington","Alamance","NC",27215,"336-822-7652","336-467-3095","deandrea@yahoo.com","http://www.centurykrallrealestate.com" "Kimberlie","Duenas","Mid Contntl Rlty & Prop Mgmt","8100 Jacksonville Rd #7","Hays","Ellis","KS",67601,"785-629-8542","785-616-1685","kimberlie_duenas@yahoo.com","http://www.midcontntlrltypropmgmt.com" "Martina","Staback","Ace Signs Inc","7 W Wabansia Ave #227","Orlando","Orange","FL",32822,"407-471-6908","407-429-2145","martina_staback@staback.com","http://www.acesignsinc.com" "Skye","Fillingim","Rodeway Inn","25 Minters Chapel Rd #9","Minneapolis","Hennepin","MN",55401,"612-508-2655","612-664-6304","skye_fillingim@yahoo.com","http://www.rodewayinn.com" "Jade","Farrar","Bonnet & Daughter","6882 Torresdale Ave","Columbia","Richland","SC",29201,"803-352-5387","803-975-3405","jade.farrar@yahoo.com","http://www.bonnetdaughter.com" "Charlene","Hamilton","Oshins & Gibbons","985 E 6th Ave","Santa Rosa","Sonoma","CA",95407,"707-300-1771","707-821-8037","charlene.hamilton@hotmail.com","http://www.oshinsgibbons.com" "Geoffrey","Acey","Price Business Services","7 West Ave #1","Palatine","Cook","IL",60067,"847-222-1734","847-556-2909","geoffrey@gmail.com","http://www.pricebusinessservices.com" "Stevie","Westerbeck","Wise, Dennis W Md","26659 N 13th St","Costa Mesa","Orange","CA",92626,"949-867-4077","949-903-3898","stevie.westerbeck@yahoo.com","http://www.wisedenniswmd.com" "Pamella","Fortino","Super 8 Motel","669 Packerland Dr #1438","Denver","Denver","CO",80212,"303-404-2210","303-794-1341","pamella@fortino.com","http://www.supermotel.com" "Harrison","Haufler","John Wagner Associates","759 Eldora St","New Haven","New Haven","CT","06515","203-801-6193","203-801-8497","hhaufler@hotmail.com","http://www.johnwagnerassociates.com" "Johnna","Engelberg","Thrifty Oil Co","5 S Colorado Blvd #449","Bothell","Snohomish","WA",98021,"425-986-7573","425-700-3751","jengelberg@engelberg.org","http://www.thriftyoilco.com" "Buddy","Cloney","Larkfield Photo","944 Gaither Dr","Strongsville","Cuyahoga","OH",44136,"440-989-5826","440-327-2093","buddy.cloney@yahoo.com","http://www.larkfieldphoto.com" "Dalene","Riden","Silverman Planetarium","66552 Malone Rd","Plaistow","Rockingham","NH","03865","603-315-6839","603-745-7497","dalene.riden@aol.com","http://www.silvermanplanetarium.com" "Jerry","Zurcher","J & F Lumber","77 Massillon Rd #822","Satellite Beach","Brevard","FL",32937,"321-518-5938","321-597-2159","jzurcher@zurcher.org","http://www.jflumber.com" "Haydee","Denooyer","Cleaning Station Inc","25346 New Rd","New York","New York","NY",10016,"212-792-8658","212-782-3493","hdenooyer@denooyer.org","http://www.cleaningstationinc.com" "Joseph","Cryer","Ames Stationers","60 Fillmore Ave","Huntington Beach","Orange","CA",92647,"714-584-2237","714-698-2170","joseph_cryer@cox.net","http://www.amesstationers.com" "Deonna","Kippley","Midas Muffler Shops","57 Haven Ave #90","Southfield","Oakland","MI",48075,"248-913-4677","248-793-4966","deonna_kippley@hotmail.com","http://www.midasmufflershops.com" "Raymon","Calvaresi","Seaboard Securities Inc","6538 E Pomona St #60","Indianapolis","Marion","IN",46222,"317-825-4724","317-342-1532","raymon.calvaresi@gmail.com","http://www.seaboardsecuritiesinc.com" "Alecia","Bubash","Petersen, James E Esq","6535 Joyce St","Wichita Falls","Wichita","TX",76301,"940-276-7922","940-302-3036","alecia@aol.com","http://www.petersenjameseesq.com" "Ma","Layous","Development Authority","78112 Morris Ave","North Haven","New Haven","CT","06473","203-721-3388","203-564-1543","mlayous@hotmail.com","http://www.developmentauthority.com" "Detra","Coyier","Schott Fiber Optics Inc","96950 Hidden Ln","Aberdeen","Harford","MD",21001,"410-739-9277","410-259-2118","detra@aol.com","http://www.schottfiberopticsinc.com" "Terrilyn","Rodeigues","Stuart J Agins","3718 S Main St","New Orleans","Orleans","LA",70130,"504-463-4384","504-635-8518","terrilyn.rodeigues@cox.net","http://www.stuartjagins.com" "Salome","Lacovara","Mitsumi Electronics Corp","9677 Commerce Dr","Richmond","Richmond City","VA",23219,"804-550-5097","804-858-1011","slacovara@gmail.com","http://www.mitsumielectronicscorp.com" "Garry","Keetch","Italian Express Franchise Corp","5 Green Pond Rd #4","Southampton","Bucks","PA",18966,"215-979-8776","215-846-9046","garry_keetch@hotmail.com","http://www.italianexpressfranchisecorp.com" "Matthew","Neither","American Council On Sci & Hlth","636 Commerce Dr #42","Shakopee","Scott","MN",55379,"952-651-7597","952-906-4597","mneither@yahoo.com","http://www.americancouncilonscihlth.com" "Theodora","Restrepo","Kleri, Patricia S Esq","42744 Hamann Industrial Pky #82","Miami","Miami-Dade","FL",33136,"305-936-8226","305-573-1085","theodora.restrepo@restrepo.com","http://www.kleripatriciasesq.com" "Noah","Kalafatis","Twiggs Abrams Blanchard","1950 5th Ave","Milwaukee","Milwaukee","WI",53209,"414-263-5287","414-660-9766","noah.kalafatis@aol.com","http://www.twiggsabramsblanchard.com" "Carmen","Sweigard","Maui Research & Technology Pk","61304 N French Rd","Somerset","Somerset","NJ","08873","732-941-2621","732-445-6940","csweigard@sweigard.com","http://www.mauiresearchtechnologypk.com" "Lavonda","Hengel","Bradley Nameplate Corp","87 Imperial Ct #79","Fargo","Cass","ND",58102,"701-898-2154","701-421-7080","lavonda@cox.net","http://www.bradleynameplatecorp.com" "Junita","Stoltzman","Geonex Martel Inc","94 W Dodge Rd","Carson City","Carson City","NV",89701,"775-638-9963","775-578-1214","junita@aol.com","http://www.geonexmartelinc.com" "Herminia","Nicolozakes","Sea Island Div Of Fstr Ind Inc","4 58th St #3519","Scottsdale","Maricopa","AZ",85254,"602-954-5141","602-304-6433","herminia@nicolozakes.org","http://www.seaislanddivoffstrindinc.com" "Casie","Good","Papay, Debbie J Esq","5221 Bear Valley Rd","Nashville","Davidson","TN",37211,"615-390-2251","615-825-4297","casie.good@aol.com","http://www.papaydebbiejesq.com" "Reena","Maisto","Lane Promotions","9648 S Main","Salisbury","Wicomico","MD",21801,"410-351-1863","410-951-2667","reena@hotmail.com","http://www.lanepromotions.com" "Mirta","Mallett","Stephen Kennerly Archts Inc Pc","7 S San Marcos Rd","New York","New York","NY",10004,"212-870-1286","212-745-6948","mirta_mallett@gmail.com","http://www.stephenkennerlyarchtsincpc.com" "Cathrine","Pontoriero","Business Systems Of Wis Inc","812 S Haven St","Amarillo","Randall","TX",79109,"806-703-1435","806-558-5848","cathrine.pontoriero@pontoriero.com","http://www.businesssystemsofwisinc.com" "Filiberto","Tawil","Flash, Elena Salerno Esq","3882 W Congress St #799","Los Angeles","Los Angeles","CA",90016,"323-765-2528","323-842-8226","ftawil@hotmail.com","http://www.flashelenasalernoesq.com" "Raul","Upthegrove","Neeley, Gregory W Esq","4 E Colonial Dr","La Mesa","San Diego","CA",91942,"619-509-5282","619-666-4765","rupthegrove@yahoo.com","http://www.neeleygregorywesq.com" "Sarah","Candlish","Alabama Educational Tv Comm","45 2nd Ave #9759","Atlanta","Fulton","GA",30328,"770-732-1194","770-531-2842","sarah.candlish@gmail.com","http://www.alabamaeducationaltvcomm.com" "Lucy","Treston","Franz Inc","57254 Brickell Ave #372","Worcester","Worcester","MA","01602","508-769-5250","508-502-5634","lucy@cox.net","http://www.franzinc.com" "Judy","Aquas","Plantation Restaurant","8977 Connecticut Ave Nw #3","Niles","Berrien","MI",49120,"269-756-7222","269-431-9464","jaquas@aquas.com","http://www.plantationrestaurant.com" "Yvonne","Tjepkema","Radio Communications Co","9 Waydell St","Fairfield","Essex","NJ","07004","973-714-1721","973-976-8627","yvonne.tjepkema@hotmail.com","http://www.radiocommunicationsco.com" "Kayleigh","Lace","Dentalaw Divsn Hlth Care","43 Huey P Long Ave","Lafayette","Lafayette","LA",70508,"337-740-9323","337-751-2326","kayleigh.lace@yahoo.com","http://www.dentalawdivsnhlthcare.com" "Felix","Hirpara","American Speedy Printing Ctrs","7563 Cornwall Rd #4462","Denver","Lancaster","PA",17517,"717-491-5643","717-583-1497","felix_hirpara@cox.net","http://www.americanspeedyprintingctrs.com" "Tresa","Sweely","Grayson, Grant S Esq","22 Bridle Ln","Valley Park","Saint Louis","MO",63088,"314-359-9566","314-231-3514","tresa_sweely@hotmail.com","http://www.graysongrantsesq.com" "Kristeen","Turinetti","Jeanerette Middle School","70099 E North Ave","Arlington","Tarrant","TX",76013,"817-213-8851","817-947-9480","kristeen@gmail.com","http://www.jeanerettemiddleschool.com" "Jenelle","Regusters","Haavisto, Brian F Esq","3211 E Northeast Loop","Tampa","Hillsborough","FL",33619,"813-932-8715","813-357-7296","jregusters@regusters.com","http://www.haavistobrianfesq.com" "Renea","Monterrubio","Wmmt Radio Station","26 Montgomery St","Atlanta","Fulton","GA",30328,"770-679-4752","770-930-9967","renea@hotmail.com","http://www.wmmtradiostation.com" "Olive","Matuszak","Colony Paints Sales Ofc & Plnt","13252 Lighthouse Ave","Cathedral City","Riverside","CA",92234,"760-938-6069","760-745-2649","olive@aol.com","http://www.colonypaintssalesofcplnt.com" "Ligia","Reiber","Floral Expressions","206 Main St #2804","Lansing","Ingham","MI",48933,"517-906-1108","517-747-7664","lreiber@cox.net","http://www.floralexpressions.com" "Christiane","Eschberger","Casco Services Inc","96541 W Central Blvd","Phoenix","Maricopa","AZ",85034,"602-390-4944","602-330-6894","christiane.eschberger@yahoo.com","http://www.cascoservicesinc.com" "Goldie","Schirpke","Reuter, Arthur C Jr","34 Saint George Ave #2","Bangor","Penobscot","ME","04401","207-295-7569","207-748-3722","goldie.schirpke@yahoo.com","http://www.reuterarthurcjr.com" "Loreta","Timenez","Kaminski, Katherine Andritsaki","47857 Coney Island Ave","Clinton","Prince Georges","MD",20735,"301-696-6420","301-392-6698","loreta.timenez@hotmail.com","http://www.kaminskikatherineandritsaki.com" "Fabiola","Hauenstein","Sidewinder Products Corp","8573 Lincoln Blvd","York","York","PA",17404,"717-809-3119","717-344-2804","fabiola.hauenstein@hauenstein.org","http://www.sidewinderproductscorp.com" "Amie","Perigo","General Foam Corporation","596 Santa Maria Ave #7913","Mesquite","Dallas","TX",75150,"972-419-7946","972-898-1033","amie.perigo@yahoo.com","http://www.generalfoamcorporation.com" "Raina","Brachle","Ikg Borden Divsn Harsco Corp","3829 Ventura Blvd","Butte","Silver Bow","MT",59701,"406-318-1515","406-374-7752","raina.brachle@brachle.org","http://www.ikgbordendivsnharscocorp.com" "Erinn","Canlas","Anchor Computer Inc","13 S Hacienda Dr","Livingston","Essex","NJ","07039","973-767-3008","973-563-9502","erinn.canlas@canlas.com","http://www.anchorcomputerinc.com" "Cherry","Lietz","Sebring & Co","40 9th Ave Sw #91","Waterford","Oakland","MI",48329,"248-980-6904","248-697-7722","cherry@lietz.com","http://www.sebringco.com" "Kattie","Vonasek","H A C Farm Lines Co Optv Assoc","2845 Boulder Crescent St","Cleveland","Cuyahoga","OH",44103,"216-923-3715","216-270-9653","kattie@vonasek.org","http://www.hacfarmlinescooptvassoc.com" "Lilli","Scriven","Hunter, John J Esq","33 State St","Abilene","Taylor","TX",79601,"325-631-1560","325-667-7868","lilli@aol.com","http://www.hunterjohnjesq.com" "Whitley","Tomasulo","Freehold Fence Co","2 S 15th St","Fort Worth","Tarrant","TX",76107,"817-526-4408","817-819-7799","whitley.tomasulo@aol.com","http://www.freeholdfenceco.com" "Barbra","Adkin","Binswanger","4 Kohler Memorial Dr","Brooklyn","Kings","NY",11230,"718-201-3751","718-732-9475","badkin@hotmail.com","http://www.binswanger.com" "Hermila","Thyberg","Chilton Malting Co","1 Rancho Del Mar Shopping C","Providence","Providence","RI","02903","401-893-4882","401-885-7681","hermila_thyberg@hotmail.com","http://www.chiltonmaltingco.com" "Jesusita","Flister","Schoen, Edward J Jr","3943 N Highland Ave","Lancaster","Lancaster","PA",17601,"717-885-9118","717-686-7564","jesusita.flister@hotmail.com","http://www.schoenedwardjjr.com" "Caitlin","Julia","Helderman, Seymour Cpa","5 Williams St","Johnston","Providence","RI","02919","401-948-4982","401-552-9059","caitlin.julia@julia.org","http://www.heldermanseymourcpa.com" "Roosevelt","Hoffis","Denbrook, Myron","60 Old Dover Rd","Hialeah","Miami-Dade","FL",33014,"305-622-4739","305-302-1135","roosevelt.hoffis@aol.com","http://www.denbrookmyron.com" "Helaine","Halter","Lippitt, Mike","8 Sheridan Rd","Jersey City","Hudson","NJ","07304","201-832-4168","201-412-3040","hhalter@yahoo.com","http://www.lippittmike.com" "Lorean","Martabano","Hiram, Hogg P Esq","85092 Southern Blvd","San Antonio","Bexar","TX",78204,"210-856-4979","210-634-2447","lorean.martabano@hotmail.com","http://www.hiramhoggpesq.com" "France","Buzick","In Travel Agency","64 Newman Springs Rd E","Brooklyn","Kings","NY",11219,"718-478-8504","718-853-3740","france.buzick@yahoo.com","http://www.intravelagency.com" "Justine","Ferrario","Newhart Foods Inc","48 Stratford Ave","Pomona","Los Angeles","CA",91768,"909-993-3242","909-631-5703","jferrario@hotmail.com","http://www.newhartfoodsinc.com" "Adelina","Nabours","Courtyard By Marriott","80 Pittsford Victor Rd #9","Cleveland","Cuyahoga","OH",44103,"216-230-4892","216-937-5320","adelina_nabours@gmail.com","http://www.courtyardbymarriott.com" "Derick","Dhamer","Studer, Eugene A Esq","87163 N Main Ave","New York","New York","NY",10013,"212-304-4515","212-225-9676","ddhamer@cox.net","http://www.studereugeneaesq.com" "Jerry","Dallen","Seashore Supply Co Waretown","393 Lafayette Ave","Richmond","Richmond City","VA",23219,"804-762-9576","804-808-9574","jerry.dallen@yahoo.com","http://www.seashoresupplycowaretown.com" "Leota","Ragel","Mayar Silk Inc","99 5th Ave #33","Trion","Chattooga","GA",30753,"706-221-4243","706-616-5131","leota.ragel@gmail.com","http://www.mayarsilkinc.com" "Jutta","Amyot","National Medical Excess Corp","49 N Mays St","Broussard","Lafayette","LA",70518,"337-515-1438","337-991-8070","jamyot@hotmail.com","http://www.nationalmedicalexcesscorp.com" "Aja","Gehrett","Stero Company","993 Washington Ave","Nutley","Essex","NJ","07110","973-544-2677","973-986-4456","aja_gehrett@hotmail.com","http://www.sterocompany.com" "Kirk","Herritt","Hasting, H Duane Esq","88 15th Ave Ne","Vestal","Broome","NY",13850,"607-407-3716","607-350-7690","kirk.herritt@aol.com","http://www.hastinghduaneesq.com" "Leonora","Mauson","Insty Prints","3381 E 40th Ave","Passaic","Passaic","NJ","07055","973-412-2995","973-355-2120","leonora@yahoo.com","http://www.instyprints.com" "Winfred","Brucato","Glenridge Manor Mobile Home Pk","201 Ridgewood Rd","Moscow","Latah","ID",83843,"208-252-4552","208-793-4108","winfred_brucato@hotmail.com","http://www.glenridgemanormobilehomepk.com" "Tarra","Nachor","Circuit Solution Inc","39 Moccasin Dr","San Francisco","San Francisco","CA",94104,"415-411-1775","415-284-2730","tarra.nachor@cox.net","http://www.circuitsolutioninc.com" "Corinne","Loder","Local Office","4 Carroll St","North Attleboro","Bristol","MA","02760","508-942-4186","508-618-7826","corinne@loder.org","http://www.localoffice.com" "Dulce","Labreche","Lee Kilkelly Paulson & Kabaker","9581 E Arapahoe Rd","Rochester","Oakland","MI",48307,"248-357-8718","248-811-5696","dulce_labreche@yahoo.com","http://www.leekilkellypaulsonkabaker.com" "Kate","Keneipp","Davis, Maxon R Esq","33 N Michigan Ave","Green Bay","Brown","WI",54301,"920-353-6377","920-355-1610","kate_keneipp@yahoo.com","http://www.davismaxonresq.com" "Kaitlyn","Ogg","Garrison, Paul E Esq","2 S Biscayne Blvd","Baltimore","Baltimore City","MD",21230,"410-665-4903","410-773-3862","kaitlyn.ogg@gmail.com","http://www.garrisonpauleesq.com" "Sherita","Saras","Black History Resource Center","8 Us Highway 22","Colorado Springs","El Paso","CO",80937,"719-669-1664","719-547-9543","sherita.saras@cox.net","http://www.blackhistoryresourcecenter.com" "Lashawnda","Stuer","Rodriguez, J Christopher Esq","7422 Martin Ave #8","Toledo","Lucas","OH",43607,"419-588-8719","419-399-1744","lstuer@cox.net","http://www.rodriguezjchristopheresq.com" "Ernest","Syrop","Grant Family Health Center","94 Chase Rd","Hyattsville","Prince Georges","MD",20785,"301-998-9644","301-257-4883","ernest@cox.net","http://www.grantfamilyhealthcenter.com" "Nobuko","Halsey","Goeman Wood Products Inc","8139 I Hwy 10 #92","New Bedford","Bristol","MA","02745","508-855-9887","508-897-7916","nobuko.halsey@yahoo.com","http://www.goemanwoodproductsinc.com" "Lavonna","Wolny","Linhares, Kenneth A Esq","5 Cabot Rd","Mc Lean","Fairfax","VA",22102,"703-483-1970","703-892-2914","lavonna.wolny@hotmail.com","http://www.linhareskennethaesq.com" "Lashaunda","Lizama","Earnhardt Printing","3387 Ryan Dr","Hanover","Anne Arundel","MD",21076,"410-678-2473","410-912-6032","llizama@cox.net","http://www.earnhardtprinting.com" "Mariann","Bilden","H P G Industrys Inc","3125 Packer Ave #9851","Austin","Travis","TX",78753,"512-223-4791","512-742-1149","mariann.bilden@aol.com","http://www.hpgindustrysinc.com" "Helene","Rodenberger","Bailey Transportation Prod Inc","347 Chestnut St","Peoria","Maricopa","AZ",85381,"623-461-8551","623-426-4907","helene@aol.com","http://www.baileytransportationprodinc.com" "Roselle","Estell","Mcglynn Bliss Pc","8116 Mount Vernon Ave","Bucyrus","Crawford","OH",44820,"419-571-5920","419-488-6648","roselle.estell@hotmail.com","http://www.mcglynnblisspc.com" "Samira","Heintzman","Mutual Fish Co","8772 Old County Rd #5410","Kent","King","WA",98032,"206-311-4137","206-923-6042","sheintzman@hotmail.com","http://www.mutualfishco.com" "Margart","Meisel","Yeates, Arthur L Aia","868 State St #38","Cincinnati","Hamilton","OH",45251,"513-617-2362","513-747-9603","margart_meisel@yahoo.com","http://www.yeatesarthurlaia.com" "Kristofer","Bennick","Logan, Ronald J Esq","772 W River Dr","Bloomington","Monroe","IN",47404,"812-368-1511","812-442-8544","kristofer.bennick@yahoo.com","http://www.loganronaldjesq.com" "Weldon","Acuff","Advantage Martgage Company","73 W Barstow Ave","Arlington Heights","Cook","IL",60004,"847-353-2156","847-613-5866","wacuff@gmail.com","http://www.advantagemartgagecompany.com" "Shalon","Shadrick","Germer And Gertz Llp","61047 Mayfield Ave","Brooklyn","Kings","NY",11223,"718-232-2337","718-394-4974","shalon@cox.net","http://www.germerandgertzllp.com" "Denise","Patak","Spence Law Offices","2139 Santa Rosa Ave","Orlando","Orange","FL",32801,"407-446-4358","407-808-3254","denise@patak.org","http://www.spencelawoffices.com" "Louvenia","Beech","John Ortiz Nts Therapy Center","598 43rd St","Beverly Hills","Los Angeles","CA",90210,"310-820-2117","310-652-2379","louvenia.beech@beech.com","http://www.johnortizntstherapycenter.com" "Audry","Yaw","Mike Uchrin Htg & Air Cond Inc","70295 Pioneer Ct","Brandon","Hillsborough","FL",33511,"813-797-4816","813-744-7100","audry.yaw@yaw.org","http://www.mikeuchrinhtgaircondinc.com" "Kristel","Ehmann","Mccoy, Joy Reynolds Esq","92899 Kalakaua Ave","El Paso","El Paso","TX",79925,"915-452-1290","915-300-6100","kristel.ehmann@aol.com","http://www.mccoyjoyreynoldsesq.com" "Vincenza","Zepp","Kbor 1600 Am","395 S 6th St #2","El Cajon","San Diego","CA",92020,"619-603-5125","619-935-6661","vzepp@gmail.com","http://www.kboram.com" "Elouise","Gwalthney","Quality Inn Northwest","9506 Edgemore Ave","Bladensburg","Prince Georges","MD",20710,"301-841-5012","301-591-3034","egwalthney@yahoo.com","http://www.qualityinnnorthwest.com" "Venita","Maillard","Wallace Church Assoc Inc","72119 S Walker Ave #63","Anaheim","Orange","CA",92801,"714-523-6653","714-663-9740","venita_maillard@gmail.com","http://www.wallacechurchassocinc.com" "Kasandra","Semidey","Can Tron","369 Latham St #500","Saint Louis","Saint Louis City","MO",63102,"314-732-9131","314-697-3652","kasandra_semidey@semidey.com","http://www.cantron.com" "Xochitl","Discipio","Ravaal Enterprises Inc","3158 Runamuck Pl","Round Rock","Williamson","TX",78664,"512-233-1831","512-942-3411","xdiscipio@gmail.com","http://www.ravaalenterprisesinc.com" "Maile","Linahan","Thompson Steel Company Inc","9 Plainsboro Rd #598","Greensboro","Guilford","NC",27409,"336-670-2640","336-364-6037","mlinahan@yahoo.com","http://www.thompsonsteelcompanyinc.com" "Krissy","Rauser","Anderson, Mark A Esq","8728 S Broad St","Coram","Suffolk","NY",11727,"631-443-4710","631-288-2866","krauser@cox.net","http://www.andersonmarkaesq.com" "Pete","Dubaldi","Womack & Galich","2215 Prosperity Dr","Lyndhurst","Bergen","NJ","07071","201-825-2514","201-749-8866","pdubaldi@hotmail.com","http://www.womackgalich.com" "Linn","Paa","Valerie & Company","1 S Pine St","Memphis","Shelby","TN",38112,"901-412-4381","901-573-9024","linn_paa@paa.com","http://www.valeriecompany.com" "Paris","Wide","Gehring Pumps Inc","187 Market St","Atlanta","Fulton","GA",30342,"404-505-4445","404-607-8435","paris@hotmail.com","http://www.gehringpumpsinc.com" "Wynell","Dorshorst","Haehnel, Craig W Esq","94290 S Buchanan St","Pacifica","San Mateo","CA",94044,"650-473-1262","650-749-9879","wynell_dorshorst@dorshorst.org","http://www.haehnelcraigwesq.com" "Quentin","Birkner","Spoor Behrins Campbell & Young","7061 N 2nd St","Burnsville","Dakota","MN",55337,"952-702-7993","952-314-5871","qbirkner@aol.com","http://www.spoorbehrinscampbellyoung.com" "Regenia","Kannady","Ken Jeter Store Equipment Inc","10759 Main St","Scottsdale","Maricopa","AZ",85260,"480-726-1280","480-205-5121","regenia.kannady@cox.net","http://www.kenjeterstoreequipmentinc.com" "Sheron","Louissant","Potter, Brenda J Cpa","97 E 3rd St #9","Long Island City","Queens","NY",11101,"718-976-8610","718-613-9994","sheron@aol.com","http://www.potterbrendajcpa.com" "Izetta","Funnell","Baird Kurtz & Dobson","82 Winsor St #54","Atlanta","Dekalb","GA",30340,"770-844-3447","770-584-4119","izetta.funnell@hotmail.com","http://www.bairdkurtzdobson.com" "Rodolfo","Butzen","Minor, Cynthia A Esq","41 Steel Ct","Northfield","Rice","MN",55057,"507-210-3510","507-590-5237","rodolfo@hotmail.com","http://www.minorcynthiaaesq.com" "Zona","Colla","Solove, Robert A Esq","49440 Dearborn St","Norwalk","Fairfield","CT","06854","203-461-1949","203-938-2557","zona@hotmail.com","http://www.soloverobertaesq.com" "Serina","Zagen","Mark Ii Imports Inc","7 S Beverly Dr","Fort Wayne","Allen","IN",46802,"260-273-3725","260-382-4869","szagen@aol.com","http://www.markiiimportsinc.com" "Paz","Sahagun","White Sign Div Ctrl Equip Co","919 Wall Blvd","Meridian","Lauderdale","MS",39307,"601-927-8287","601-249-4511","paz_sahagun@cox.net","http://www.whitesigndivctrlequipco.com" "Markus","Lukasik","M & M Store Fixtures Co Inc","89 20th St E #779","Sterling Heights","Macomb","MI",48310,"586-970-7380","586-247-1614","markus@yahoo.com","http://www.mmstorefixturescoinc.com" "Jaclyn","Bachman","Judah Caster & Wheel Co","721 Interstate 45 S","Colorado Springs","El Paso","CO",80919,"719-853-3600","719-223-2074","jaclyn@aol.com","http://www.judahcasterwheelco.com" "Cyril","Daufeldt","Galaxy International Inc","3 Lawton St","New York","New York","NY",10013,"212-745-8484","212-422-5427","cyril_daufeldt@daufeldt.com","http://www.galaxyinternationalinc.com" "Gayla","Schnitzler","Sigma Corp Of America","38 Pleasant Hill Rd","Hayward","Alameda","CA",94545,"510-686-3407","510-441-4055","gschnitzler@gmail.com","http://www.sigmacorpofamerica.com" "Erick","Nievas","Soward, Anne Esq","45 E Acacia Ct","Chicago","Cook","IL",60624,"773-704-9903","773-359-6109","erick_nievas@aol.com","http://www.sowardanneesq.com" "Jennie","Drymon","Osborne, Michelle M Esq","63728 Poway Rd #1","Scranton","Lackawanna","PA",18509,"570-218-4831","570-868-8688","jennie@cox.net","http://www.osbornemichellemesq.com" "Mitsue","Scipione","Students In Free Entrprs Natl","77 222 Dr","Oroville","Butte","CA",95965,"530-986-9272","530-399-3254","mscipione@scipione.com","http://www.studentsinfreeentrprsnatl.com" "Ciara","Ventura","Johnson, Robert M Esq","53 W Carey St","Port Jervis","Orange","NY",12771,"845-823-8877","845-694-7919","cventura@yahoo.com","http://www.johnsonrobertmesq.com" "Galen","Cantres","Del Charro Apartments","617 Nw 36th Ave","Brook Park","Cuyahoga","OH",44142,"216-600-6111","216-871-6876","galen@yahoo.com","http://www.delcharroapartments.com" "Truman","Feichtner","Legal Search Inc","539 Coldwater Canyon Ave","Bloomfield","Essex","NJ","07003","973-852-2736","973-473-5108","tfeichtner@yahoo.com","http://www.legalsearchinc.com" "Gail","Kitty","Service Supply Co Inc","735 Crawford Dr","Anchorage","Anchorage","AK",99501,"907-435-9166","907-770-3542","gail@kitty.com","http://www.servicesupplycoinc.com" "Dalene","Schoeneck","Sameshima, Douglas J Esq","910 Rahway Ave","Philadelphia","Philadelphia","PA",19102,"215-268-1275","215-380-8820","dalene@schoeneck.org","http://www.sameshimadouglasjesq.com" "Gertude","Witten","Thompson, John Randolph Jr","7 Tarrytown Rd","Cincinnati","Hamilton","OH",45217,"513-977-7043","513-863-9471","gertude.witten@gmail.com","http://www.thompsonjohnrandolphjr.com" "Lizbeth","Kohl","E T Balancing Co Inc","35433 Blake St #588","Gardena","Los Angeles","CA",90248,"310-699-1222","310-955-5788","lizbeth@yahoo.com","http://www.etbalancingcoinc.com" "Glenn","Berray","Griswold, John E Esq","29 Cherry St #7073","Des Moines","Polk","IA",50315,"515-370-7348","515-372-1738","gberray@gmail.com","http://www.griswoldjohneesq.com" "Lashandra","Klang","Acqua Group","810 N La Brea Ave","King of Prussia","Montgomery","PA",19406,"610-809-1818","610-378-7332","lashandra@yahoo.com","http://www.acquagroup.com" "Lenna","Newville","Brooks, Morris J Jr","987 Main St","Raleigh","Wake","NC",27601,"919-623-2524","919-254-5987","lnewville@newville.com","http://www.brooksmorrisjjr.com" "Laurel","Pagliuca","Printing Images Corp","36 Enterprise St Se","Richland","Benton","WA",99352,"509-695-5199","509-595-6485","laurel@yahoo.com","http://www.printingimagescorp.com" "Mireya","Frerking","Roberts Supply Co Inc","8429 Miller Rd","Pelham","Westchester","NY",10803,"914-868-5965","914-883-3061","mireya.frerking@hotmail.com","http://www.robertssupplycoinc.com" "Annelle","Tagala","Vico Products Mfg Co","5 W 7th St","Parkville","Baltimore","MD",21234,"410-757-1035","410-234-2267","annelle@yahoo.com","http://www.vicoproductsmfgco.com" "Dean","Ketelsen","J M Custom Design Millwork","2 Flynn Rd","Hicksville","Nassau","NY",11801,"516-847-4418","516-732-6649","dean_ketelsen@gmail.com","http://www.jmcustomdesignmillwork.com" "Levi","Munis","Farrell & Johnson Office Equip","2094 Ne 36th Ave","Worcester","Worcester","MA","01603","508-456-4907","508-658-7802","levi.munis@gmail.com","http://www.farrelljohnsonofficeequip.com" "Sylvie","Ryser","Millers Market & Deli","649 Tulane Ave","Tulsa","Tulsa","OK",74105,"918-644-9555","918-565-1706","sylvie@aol.com","http://www.millersmarketdeli.com" "Sharee","Maile","Holiday Inn Naperville","2094 Montour Blvd","Muskegon","Muskegon","MI",49442,"231-467-9978","231-265-6940","sharee_maile@aol.com","http://www.holidayinnnaperville.com" "Cordelia","Storment","Burrows, Jon H Esq","393 Hammond Dr","Lafayette","Lafayette","LA",70506,"337-566-6001","337-255-3427","cordelia_storment@aol.com","http://www.burrowsjonhesq.com" "Mollie","Mcdoniel","Dock Seal Specialty","8590 Lake Lizzie Dr","Bowling Green","Wood","OH",43402,"419-975-3182","419-417-4674","mollie_mcdoniel@yahoo.com","http://www.docksealspecialty.com" "Brett","Mccullan","Five Star Limousines Of Tx Inc","87895 Concord Rd","La Mesa","San Diego","CA",91942,"619-461-9984","619-727-3892","brett.mccullan@mccullan.com","http://www.fivestarlimousinesoftxinc.com" "Teddy","Pedrozo","Barkan, Neal J Esq","46314 Route 130","Bridgeport","Fairfield","CT","06610","203-892-3863","203-918-3939","teddy_pedrozo@aol.com","http://www.barkannealjesq.com" "Tasia","Andreason","Campbell, Robert A","4 Cowesett Ave","Kearny","Hudson","NJ","07032","201-920-9002","201-969-7063","tasia_andreason@yahoo.com","http://www.campbellroberta.com" "Hubert","Walthall","Dee, Deanna","95 Main Ave #2","Barberton","Summit","OH",44203,"330-903-1345","330-566-8898","hubert@walthall.org","http://www.deedeanna.com" "Arthur","Farrow","Young, Timothy L Esq","28 S 7th St #2824","Englewood","Bergen","NJ","07631","201-238-5688","201-772-4377","arthur.farrow@yahoo.com","http://www.youngtimothylesq.com" "Vilma","Berlanga","Wells, D Fred Esq","79 S Howell Ave","Grand Rapids","Kent","MI",49546,"616-737-3085","616-568-4113","vberlanga@berlanga.com","http://www.wellsdfredesq.com" "Billye","Miro","Gray, Francine H Esq","36 Lancaster Dr Se","Pearl","Rankin","MS",39208,"601-567-5386","601-637-5479","billye_miro@cox.net","http://www.grayfrancinehesq.com" "Glenna","Slayton","Toledo Iv Care","2759 Livingston Ave","Memphis","Shelby","TN",38118,"901-640-9178","901-869-4314","glenna_slayton@cox.net","http://www.toledoivcare.com" "Mitzie","Hudnall","Cangro Transmission Co","17 Jersey Ave","Englewood","Arapahoe","CO",80110,"303-402-1940","303-997-7760","mitzie_hudnall@yahoo.com","http://www.cangrotransmissionco.com" "Bernardine","Rodefer","Sat Poly Inc","2 W Grand Ave","Memphis","Shelby","TN",38112,"901-901-4726","901-739-5892","bernardine_rodefer@yahoo.com","http://www.satpolyinc.com" "Staci","Schmaltz","Midwest Contracting & Mfg Inc","18 Coronado Ave #563","Pasadena","Los Angeles","CA",91106,"626-866-2339","626-293-7678","staci_schmaltz@aol.com","http://www.midwestcontractingmfginc.com" "Nichelle","Meteer","Print Doctor","72 Beechwood Ter","Chicago","Cook","IL",60657,"773-225-9985","773-857-2231","nichelle_meteer@meteer.com","http://www.printdoctor.com" "Janine","Rhoden","Nordic Group Inc","92 Broadway","Astoria","Queens","NY",11103,"718-228-5894","718-728-5051","jrhoden@yahoo.com","http://www.nordicgroupinc.com" "Ettie","Hoopengardner","Jackson Millwork Co","39 Franklin Ave","Richland","Benton","WA",99352,"509-755-5393","509-847-3352","ettie.hoopengardner@hotmail.com","http://www.jacksonmillworkco.com" "Eden","Jayson","Harris Corporation","4 Iwaena St","Baltimore","Baltimore City","MD",21202,"410-890-7866","410-429-4888","eden_jayson@yahoo.com","http://www.harriscorporation.com" "Lynelle","Auber","United Cerebral Palsy Of Ne Pa","32820 Corkwood Rd","Newark","Essex","NJ","07104","973-860-8610","973-605-6492","lynelle_auber@gmail.com","http://www.unitedcerebralpalsyofnepa.com" "Merissa","Tomblin","One Day Surgery Center Inc","34 Raritan Center Pky","Bellflower","Los Angeles","CA",90706,"562-579-6900","562-719-7922","merissa.tomblin@gmail.com","http://www.onedaysurgerycenterinc.com" "Golda","Kaniecki","Calaveras Prospect","6201 S Nevada Ave","Toms River","Ocean","NJ","08755","732-628-9909","732-617-5310","golda_kaniecki@yahoo.com","http://www.calaverasprospect.com" "Catarina","Gleich","Terk, Robert E Esq","78 Maryland Dr #146","Denville","Morris","NJ","07834","973-210-3994","973-491-8723","catarina_gleich@hotmail.com","http://www.terkroberteesq.com" "Virgie","Kiel","Cullen, Terrence P Esq","76598 Rd I 95 #1","Denver","Denver","CO",80216,"303-776-7548","303-845-5408","vkiel@hotmail.com","http://www.cullenterrencepesq.com" "Jolene","Ostolaza","Central Die Casting Mfg Co Inc","1610 14th St Nw","Newport News","Newport News City","VA",23608,"757-682-7116","757-940-1741","jolene@yahoo.com","http://www.centraldiecastingmfgcoinc.com" "Keneth","Borgman","Centerline Engineering","86350 Roszel Rd","Phoenix","Maricopa","AZ",85012,"602-919-4211","602-442-3092","keneth@yahoo.com","http://www.centerlineengineering.com" "Rikki","Nayar","Targan & Kievit Pa","1644 Clove Rd","Miami","Miami-Dade","FL",33155,"305-968-9487","305-978-2069","rikki@nayar.com","http://www.targankievitpa.com" "Elke","Sengbusch","Riley Riper Hollin & Colagreco","9 W Central Ave","Phoenix","Maricopa","AZ",85013,"602-896-2993","602-575-3457","elke_sengbusch@yahoo.com","http://www.rileyriperhollincolagreco.com" "Hoa","Sarao","Kaplan, Joel S Esq","27846 Lafayette Ave","Oak Hill","Volusia","FL",32759,"386-526-7800","386-599-7296","hoa@sarao.org","http://www.kaplanjoelsesq.com" "Trinidad","Mcrae","Water Office","10276 Brooks St","San Francisco","San Francisco","CA",94105,"415-331-9634","415-419-1597","trinidad_mcrae@yahoo.com","http://www.wateroffice.com" "Mari","Lueckenbach","Westbrooks, Nelson E Jr","1 Century Park E","San Diego","San Diego","CA",92110,"858-793-9684","858-228-5683","mari_lueckenbach@yahoo.com","http://www.westbrooksnelsonejr.com" "Selma","Husser","Armon Communications","9 State Highway 57 #22","Jersey City","Hudson","NJ","07306","201-991-8369","201-772-7699","selma.husser@cox.net","http://www.armoncommunications.com" "Antione","Onofrio","Jacobs & Gerber Inc","4 S Washington Ave","San Bernardino","San Bernardino","CA",92410,"909-430-7765","909-665-3223","aonofrio@onofrio.com","http://www.jacobsgerberinc.com" "Luisa","Jurney","Forest Fire Laboratory","25 Se 176th Pl","Cambridge","Middlesex","MA","02138","617-365-2134","617-544-2541","ljurney@hotmail.com","http://www.forestfirelaboratory.com" "Clorinda","Heimann","Haughey, Charles Jr","105 Richmond Valley Rd","Escondido","San Diego","CA",92025,"760-291-5497","760-261-4786","clorinda.heimann@hotmail.com","http://www.haugheycharlesjr.com" "Dick","Wenzinger","Wheaton Plastic Products","22 Spruce St #595","Gardena","Los Angeles","CA",90248,"310-510-9713","310-936-2258","dick@yahoo.com","http://www.wheatonplasticproducts.com" "Ahmed","Angalich","Reese Plastics","2 W Beverly Blvd","Harrisburg","Dauphin","PA",17110,"717-528-8996","717-632-5831","ahmed.angalich@angalich.com","http://www.reeseplastics.com" "Iluminada","Ohms","Nazette Marner Good Wendt","72 Southern Blvd","Mesa","Maricopa","AZ",85204,"480-293-2882","480-866-6544","iluminada.ohms@yahoo.com","http://www.nazettemarnergoodwendt.com" "Joanna","Leinenbach","Levinson Axelrod Wheaton","1 Washington St","Lake Worth","Palm Beach","FL",33461,"561-470-4574","561-951-9734","joanna_leinenbach@hotmail.com","http://www.levinsonaxelrodwheaton.com" "Caprice","Suell","Egnor, W Dan Esq","90177 N 55th Ave","Nashville","Davidson","TN",37211,"615-246-1824","615-726-4537","caprice@aol.com","http://www.egnorwdanesq.com" "Stephane","Myricks","Portland Central Thriftlodge","9 Tower Ave","Burlington","Boone","KY",41005,"859-717-7638","859-308-4286","stephane_myricks@cox.net","http://www.portlandcentralthriftlodge.com" "Quentin","Swayze","Ulbrich Trucking","278 Bayview Ave","Milan","Monroe","MI",48160,"734-561-6170","734-851-8571","quentin_swayze@yahoo.com","http://www.ulbrichtrucking.com" "Annmarie","Castros","Tipiak Inc","80312 W 32nd St","Conroe","Montgomery","TX",77301,"936-751-7961","936-937-2334","annmarie_castros@gmail.com","http://www.tipiakinc.com" "Shonda","Greenbush","Saint George Well Drilling","82 Us Highway 46","Clifton","Passaic","NJ","07011","973-482-2430","973-644-2974","shonda_greenbush@cox.net","http://www.saintgeorgewelldrilling.com" "Cecil","Lapage","Hawkes, Douglas D","4 Stovall St #72","Union City","Hudson","NJ","07087","201-693-3967","201-856-2720","clapage@lapage.com","http://www.hawkesdouglasd.com" "Jeanice","Claucherty","Accurel Systems Intrntl Corp","19 Amboy Ave","Miami","Miami-Dade","FL",33142,"305-988-4162","305-306-7834","jeanice.claucherty@yahoo.com","http://www.accurelsystemsintrntlcorp.com" "Josphine","Villanueva","Santa Cruz Community Internet","63 Smith Ln #8343","Moss","Clay","TN",38575,"931-553-9774","931-486-6946","josphine_villanueva@villanueva.com","http://www.santacruzcommunityinternet.com" "Daniel","Perruzza","Gersh & Danielson","11360 S Halsted St","Santa Ana","Orange","CA",92705,"714-771-3880","714-531-1391","dperruzza@perruzza.com","http://www.gershdanielson.com" "Cassi","Wildfong","Cobb, James O Esq","26849 Jefferson Hwy","Rolling Meadows","Cook","IL",60008,"847-633-3216","847-755-9041","cassi.wildfong@aol.com","http://www.cobbjamesoesq.com" "Britt","Galam","Wheatley Trucking Company","2500 Pringle Rd Se #508","Hatfield","Montgomery","PA",19440,"215-888-3304","215-351-8523","britt@galam.org","http://www.wheatleytruckingcompany.com" "Adell","Lipkin","Systems Graph Inc Ab Dick Dlr","65 Mountain View Dr","Whippany","Morris","NJ","07981","973-654-1561","973-662-8988","adell.lipkin@lipkin.com","http://www.systemsgraphincabdickdlr.com" "Jacqueline","Rowling","John Hancock Mutl Life Ins Co","1 N San Saba","Erie","Erie","PA",16501,"814-865-8113","814-481-1700","jacqueline.rowling@yahoo.com","http://www.johnhancockmutllifeinsco.com" "Lonny","Weglarz","History Division Of State","51120 State Route 18","Salt Lake City","Salt Lake","UT",84115,"801-293-9853","801-892-8781","lonny_weglarz@gmail.com","http://www.historydivisionofstate.com" "Lonna","Diestel","Dimmock, Thomas J Esq","1482 College Ave","Fayetteville","Cumberland","NC",28301,"910-922-3672","910-200-7912","lonna_diestel@gmail.com","http://www.dimmockthomasjesq.com" "Cristal","Samara","Intermed Inc","4119 Metropolitan Dr","Los Angeles","Los Angeles","CA",90021,"213-975-8026","213-696-8004","cristal@cox.net","http://www.intermedinc.com" "Kenneth","Grenet","Bank Of New York","2167 Sierra Rd","East Lansing","Ingham","MI",48823,"517-499-2322","517-867-8077","kenneth.grenet@grenet.org","http://www.bankofnewyork.com" "Elli","Mclaird","Sportmaster Intrnatl","6 Sunrise Ave","Utica","Oneida","NY",13501,"315-818-2638","315-474-5570","emclaird@mclaird.com","http://www.sportmasterintrnatl.com" "Alline","Jeanty","W W John Holden Inc","55713 Lake City Hwy","South Bend","St Joseph","IN",46601,"574-656-2800","574-405-1983","ajeanty@gmail.com","http://www.wwjohnholdeninc.com" "Sharika","Eanes","Maccani & Delp","75698 N Fiesta Blvd","Orlando","Orange","FL",32806,"407-312-1691","407-472-1332","sharika.eanes@aol.com","http://www.maccanidelp.com" "Nu","Mcnease","Amazonia Film Project","88 Sw 28th Ter","Harrison","Hudson","NJ","07029","973-751-9003","973-903-4175","nu@gmail.com","http://www.amazoniafilmproject.com" "Daniela","Comnick","Water & Sewer Department","7 Flowers Rd #403","Trenton","Mercer","NJ","08611","609-200-8577","609-398-2805","dcomnick@cox.net","http://www.watersewerdepartment.com" "Cecilia","Colaizzo","Switchcraft Inc","4 Nw 12th St #3849","Madison","Dane","WI",53717,"608-382-4541","608-302-3387","cecilia_colaizzo@colaizzo.com","http://www.switchcraftinc.com" "Leslie","Threets","C W D C Metal Fabricators","2 A Kelley Dr","Katonah","Westchester","NY",10536,"914-861-9748","914-396-2615","leslie@cox.net","http://www.cwdcmetalfabricators.com" "Nan","Koppinger","Shimotani, Grace T","88827 Frankford Ave","Greensboro","Guilford","NC",27401,"336-370-5333","336-564-1492","nan@koppinger.com","http://www.shimotanigracet.com" "Izetta","Dewar","Lisatoni, Jean Esq","2 W Scyene Rd #3","Baltimore","Baltimore City","MD",21217,"410-473-1708","410-522-7621","idewar@dewar.com","http://www.lisatonijeanesq.com" "Tegan","Arceo","Ceramic Tile Sales Inc","62260 Park Stre","Monroe Township","Middlesex","NJ","08831","732-730-2692","732-705-6719","tegan.arceo@arceo.org","http://www.ceramictilesalesinc.com" "Ruthann","Keener","Maiden Craft Inc","3424 29th St Se","Kerrville","Kerr","TX",78028,"830-258-2769","830-919-5991","ruthann@hotmail.com","http://www.maidencraftinc.com" "Joni","Breland","Carriage House Cllsn Rpr Inc","35 E Main St #43","Elk Grove Village","Cook","IL",60007,"847-519-5906","847-740-5304","joni_breland@cox.net","http://www.carriagehousecllsnrprinc.com" "Vi","Rentfro","Video Workshop","7163 W Clark Rd","Freehold","Monmouth","NJ","07728","732-605-4781","732-724-7251","vrentfro@cox.net","http://www.videoworkshop.com" "Colette","Kardas","Fresno Tile Center Inc","21575 S Apple Creek Rd","Omaha","Douglas","NE",68124,"402-896-5943","402-707-1602","colette.kardas@yahoo.com","http://www.fresnotilecenterinc.com" "Malcolm","Tromblay","Versatile Sash & Woodwork","747 Leonis Blvd","Annandale","Fairfax","VA",22003,"703-221-5602","703-874-4248","malcolm_tromblay@cox.net","http://www.versatilesashwoodwork.com" "Ryan","Harnos","Warner Electric Brk & Cltch Co","13 Gunnison St","Plano","Collin","TX",75075,"972-558-1665","972-961-4968","ryan@cox.net","http://www.warnerelectricbrkcltchco.com" "Jess","Chaffins","New York Public Library","18 3rd Ave","New York","New York","NY",10016,"212-510-4633","212-428-9538","jess.chaffins@chaffins.org","http://www.newyorkpubliclibrary.com" "Sharen","Bourbon","Mccaleb, John A Esq","62 W Austin St","Syosset","Nassau","NY",11791,"516-816-1541","516-749-3188","sbourbon@yahoo.com","http://www.mccalebjohnaesq.com" "Nickolas","Juvera","United Oil Co Inc","177 S Rider Trl #52","Crystal River","Citrus","FL",34429,"352-598-8301","352-947-6152","nickolas_juvera@cox.net","http://www.unitedoilcoinc.com" "Gary","Nunlee","Irving Foot Center","2 W Mount Royal Ave","Fortville","Hancock","IN",46040,"317-542-6023","317-887-8486","gary_nunlee@nunlee.org","http://www.irvingfootcenter.com" "Diane","Devreese","Acme Supply Co","1953 Telegraph Rd","Saint Joseph","Buchanan","MO",64504,"816-557-9673","816-329-5565","diane@cox.net","http://www.acmesupplyco.com" "Roslyn","Chavous","Mcrae, James L","63517 Dupont St","Jackson","Hinds","MS",39211,"601-234-9632","601-973-5754","roslyn.chavous@chavous.org","http://www.mcraejamesl.com" "Glory","Schieler","Mcgraths Seafood","5 E Truman Rd","Abilene","Taylor","TX",79602,"325-869-2649","325-740-3778","glory@yahoo.com","http://www.mcgrathsseafood.com" "Rasheeda","Sayaphon","Kummerer, J Michael Esq","251 Park Ave #979","Saratoga","Santa Clara","CA",95070,"408-805-4309","408-997-7490","rasheeda@aol.com","http://www.kummererjmichaelesq.com" "Alpha","Palaia","Stoffer, James M Jr","43496 Commercial Dr #29","Cherry Hill","Camden","NJ","08003","856-312-2629","856-513-7024","alpha@yahoo.com","http://www.stofferjamesmjr.com" "Refugia","Jacobos","North Central Fl Sfty Cncl","2184 Worth St","Hayward","Alameda","CA",94545,"510-974-8671","510-509-3496","refugia.jacobos@jacobos.com","http://www.northcentralflsftycncl.com" "Shawnda","Yori","Fiorucci Foods Usa Inc","50126 N Plankinton Ave","Longwood","Seminole","FL",32750,"407-538-5106","407-564-8113","shawnda.yori@yahoo.com","http://www.fioruccifoodsusainc.com" "Mona","Delasancha","Sign All","38773 Gravois Ave","Cheyenne","Laramie","WY",82001,"307-403-1488","307-816-7115","mdelasancha@hotmail.com","http://www.signall.com" "Gilma","Liukko","Sammys Steak Den","16452 Greenwich St","Garden City","Nassau","NY",11530,"516-393-9967","516-407-9573","gilma_liukko@gmail.com","http://www.sammyssteakden.com" "Janey","Gabisi","Dobscha, Stephen F Esq","40 Cambridge Ave","Madison","Dane","WI",53715,"608-967-7194","608-586-6912","jgabisi@hotmail.com","http://www.dobschastephenfesq.com" "Lili","Paskin","Morgan Custom Homes","20113 4th Ave E","Kearny","Hudson","NJ","07032","201-431-2989","201-478-8540","lili.paskin@cox.net","http://www.morgancustomhomes.com" "Loren","Asar","Olsen Payne & Company","6 Ridgewood Center Dr","Old Forge","Lackawanna","PA",18518,"570-648-3035","570-569-2356","loren.asar@aol.com","http://www.olsenpaynecompany.com" "Dorothy","Chesterfield","Cowan & Kelly","469 Outwater Ln","San Diego","San Diego","CA",92126,"858-617-7834","858-732-1884","dorothy@cox.net","http://www.cowankelly.com" "Gail","Similton","Johnson, Wes Esq","62 Monroe St","Thousand Palms","Riverside","CA",92276,"760-616-5388","760-493-9208","gail_similton@similton.com","http://www.johnsonwesesq.com" "Catalina","Tillotson","Icn Pharmaceuticals Inc","3338 A Lockport Pl #6","Margate City","Atlantic","NJ","08402","609-373-3332","609-826-4990","catalina@hotmail.com","http://www.icnpharmaceuticalsinc.com" "Lawrence","Lorens","New England Sec Equip Co Inc","9 Hwy","Providence","Providence","RI","02906","401-465-6432","401-893-1820","lawrence.lorens@hotmail.com","http://www.newenglandsecequipcoinc.com" "Carlee","Boulter","Tippett, Troy M Ii","8284 Hart St","Abilene","Dickinson","KS",67410,"785-347-1805","785-253-7049","carlee.boulter@hotmail.com","http://www.tippetttroymii.com" "Thaddeus","Ankeny","Atc Contracting","5 Washington St #1","Roseville","Placer","CA",95678,"916-920-3571","916-459-2433","tankeny@ankeny.org","http://www.atccontracting.com" "Jovita","Oles","Pagano, Philip G Esq","8 S Haven St","Daytona Beach","Volusia","FL",32114,"386-248-4118","386-208-6976","joles@gmail.com","http://www.paganophilipgesq.com" "Alesia","Hixenbaugh","Kwikprint","9 Front St","Washington","District of Columbia","DC",20001,"202-646-7516","202-276-6826","alesia_hixenbaugh@hixenbaugh.org","http://www.kwikprint.com" "Lai","Harabedian","Buergi & Madden Scale","1933 Packer Ave #2","Novato","Marin","CA",94945,"415-423-3294","415-926-6089","lai@gmail.com","http://www.buergimaddenscale.com" "Brittni","Gillaspie","Inner Label","67 Rv Cent","Boise","Ada","ID",83709,"208-709-1235","208-206-9848","bgillaspie@gillaspie.com","http://www.innerlabel.com" "Raylene","Kampa","Hermar Inc","2 Sw Nyberg Rd","Elkhart","Elkhart","IN",46514,"574-499-1454","574-330-1884","rkampa@kampa.org","http://www.hermarinc.com" "Flo","Bookamer","Simonton Howe & Schneider Pc","89992 E 15th St","Alliance","Box Butte","NE",69301,"308-726-2182","308-250-6987","flo.bookamer@cox.net","http://www.simontonhoweschneiderpc.com" "Jani","Biddy","Warehouse Office & Paper Prod","61556 W 20th Ave","Seattle","King","WA",98104,"206-711-6498","206-395-6284","jbiddy@yahoo.com","http://www.warehouseofficepaperprod.com" "Chauncey","Motley","Affiliated With Travelodge","63 E Aurora Dr","Orlando","Orange","FL",32804,"407-413-4842","407-557-8857","chauncey_motley@aol.com","http://www.affiliatedwithtravelodge.com" \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestFilterCSVColumns/US500_typeless.csv b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestFilterCSVColumns/US500_typeless.csv deleted file mode 100644 index 61ce4bd63..000000000 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestFilterCSVColumns/US500_typeless.csv +++ /dev/null @@ -1 +0,0 @@ -FIRST_NAME:string,LAST_NAME,COMPANY_NAME,ADDRESS,CITY,COUNTY,STATE,zip,phone1,phone2,email,web "James","Butt","Benton, John B Jr","6649 N Blue Gum St","New Orleans","Orleans","LA",70116,"504-621-8927","504-845-1427","jbutt@gmail.com","http://www.bentonjohnbjr.com" "Josephine","Darakjy","Chanay, Jeffrey A Esq","4 B Blue Ridge Blvd","Brighton","Livingston","MI",48116,"810-292-9388","810-374-9840","josephine_darakjy@darakjy.org","http://www.chanayjeffreyaesq.com" "Art","Venere","Chemel, James L Cpa","8 W Cerritos Ave #54","Bridgeport","Gloucester","NJ","08014","856-636-8749","856-264-4130","art@venere.org","http://www.chemeljameslcpa.com" "Lenna","Paprocki","Feltz Printing Service","639 Main St","Anchorage","Anchorage","AK",99501,"907-385-4412","907-921-2010","lpaprocki@hotmail.com","http://www.feltzprintingservice.com" "Donette","Foller","Printing Dimensions","34 Center St","Hamilton","Butler","OH",45011,"513-570-1893","513-549-4561","donette.foller@cox.net","http://www.printingdimensions.com" "Simona","Morasca","Chapman, Ross E Esq","3 Mcauley Dr","Ashland","Ashland","OH",44805,"419-503-2484","419-800-6759","simona@morasca.com","http://www.chapmanrosseesq.com" "Mitsue","Tollner","Morlong Associates","7 Eads St","Chicago","Cook","IL",60632,"773-573-6914","773-924-8565","mitsue_tollner@yahoo.com","http://www.morlongassociates.com" "Leota","Dilliard","Commercial Press","7 W Jackson Blvd","San Jose","Santa Clara","CA",95111,"408-752-3500","408-813-1105","leota@hotmail.com","http://www.commercialpress.com" "Sage","Wieser","Truhlar And Truhlar Attys","5 Boston Ave #88","Sioux Falls","Minnehaha","SD",57105,"605-414-2147","605-794-4895","sage_wieser@cox.net","http://www.truhlarandtruhlarattys.com" "Kris","Marrier","King, Christopher A Esq","228 Runamuck Pl #2808","Baltimore","Baltimore City","MD",21224,"410-655-8723","410-804-4694","kris@gmail.com","http://www.kingchristopheraesq.com" "Minna","Amigon","Dorl, James J Esq","2371 Jerrold Ave","Kulpsville","Montgomery","PA",19443,"215-874-1229","215-422-8694","minna_amigon@yahoo.com","http://www.dorljamesjesq.com" "Abel","Maclead","Rangoni Of Florence","37275 St Rt 17m M","Middle Island","Suffolk","NY",11953,"631-335-3414","631-677-3675","amaclead@gmail.com","http://www.rangoniofflorence.com" "Kiley","Caldarera","Feiner Bros","25 E 75th St #69","Los Angeles","Los Angeles","CA",90034,"310-498-5651","310-254-3084","kiley.caldarera@aol.com","http://www.feinerbros.com" "Graciela","Ruta","Buckley Miller & Wright","98 Connecticut Ave Nw","Chagrin Falls","Geauga","OH",44023,"440-780-8425","440-579-7763","gruta@cox.net","http://www.buckleymillerwright.com" "Cammy","Albares","Rousseaux, Michael Esq","56 E Morehead St","Laredo","Webb","TX",78045,"956-537-6195","956-841-7216","calbares@gmail.com","http://www.rousseauxmichaelesq.com" "Mattie","Poquette","Century Communications","73 State Road 434 E","Phoenix","Maricopa","AZ",85013,"602-277-4385","602-953-6360","mattie@aol.com","http://www.centurycommunications.com" "Meaghan","Garufi","Bolton, Wilbur Esq","69734 E Carrillo St","Mc Minnville","Warren","TN",37110,"931-313-9635","931-235-7959","meaghan@hotmail.com","http://www.boltonwilburesq.com" "Gladys","Rim","T M Byxbee Company Pc","322 New Horizon Blvd","Milwaukee","Milwaukee","WI",53207,"414-661-9598","414-377-2880","gladys.rim@rim.org","http://www.tmbyxbeecompanypc.com" "Yuki","Whobrey","Farmers Insurance Group","1 State Route 27","Taylor","Wayne","MI",48180,"313-288-7937","313-341-4470","yuki_whobrey@aol.com","http://www.farmersinsurancegroup.com" "Fletcher","Flosi","Post Box Services Plus","394 Manchester Blvd","Rockford","Winnebago","IL",61109,"815-828-2147","815-426-5657","fletcher.flosi@yahoo.com","http://www.postboxservicesplus.com" "Bette","Nicka","Sport En Art","6 S 33rd St","Aston","Delaware","PA",19014,"610-545-3615","610-492-4643","bette_nicka@cox.net","http://www.sportenart.com" "Veronika","Inouye","C 4 Network Inc","6 Greenleaf Ave","San Jose","Santa Clara","CA",95111,"408-540-1785","408-813-4592","vinouye@aol.com","http://www.cnetworkinc.com" "Willard","Kolmetz","Ingalls, Donald R Esq","618 W Yakima Ave","Irving","Dallas","TX",75062,"972-303-9197","972-896-4882","willard@hotmail.com","http://www.ingallsdonaldresq.com" "Maryann","Royster","Franklin, Peter L Esq","74 S Westgate St","Albany","Albany","NY",12204,"518-966-7987","518-448-8982","mroyster@royster.com","http://www.franklinpeterlesq.com" "Alisha","Slusarski","Wtlz Power 107 Fm","3273 State St","Middlesex","Middlesex","NJ","08846","732-658-3154","732-635-3453","alisha@slusarski.com","http://www.wtlzpowerfm.com" "Allene","Iturbide","Ledecky, David Esq","1 Central Ave","Stevens Point","Portage","WI",54481,"715-662-6764","715-530-9863","allene_iturbide@cox.net","http://www.ledeckydavidesq.com" "Chanel","Caudy","Professional Image Inc","86 Nw 66th St #8673","Shawnee","Johnson","KS",66218,"913-388-2079","913-899-1103","chanel.caudy@caudy.org","http://www.professionalimageinc.com" "Ezekiel","Chui","Sider, Donald C Esq","2 Cedar Ave #84","Easton","Talbot","MD",21601,"410-669-1642","410-235-8738","ezekiel@chui.com","http://www.siderdonaldcesq.com" "Willow","Kusko","U Pull It","90991 Thorburn Ave","New York","New York","NY",10011,"212-582-4976","212-934-5167","wkusko@yahoo.com","http://www.upullit.com" "Bernardo","Figeroa","Clark, Richard Cpa","386 9th Ave N","Conroe","Montgomery","TX",77301,"936-336-3951","936-597-3614","bfigeroa@aol.com","http://www.clarkrichardcpa.com" "Ammie","Corrio","Moskowitz, Barry S","74874 Atlantic Ave","Columbus","Franklin","OH",43215,"614-801-9788","614-648-3265","ammie@corrio.com","http://www.moskowitzbarrys.com" "Francine","Vocelka","Cascade Realty Advisors Inc","366 South Dr","Las Cruces","Dona Ana","NM",88011,"505-977-3911","505-335-5293","francine_vocelka@vocelka.com","http://www.cascaderealtyadvisorsinc.com" "Ernie","Stenseth","Knwz Newsradio","45 E Liberty St","Ridgefield Park","Bergen","NJ","07660","201-709-6245","201-387-9093","ernie_stenseth@aol.com","http://www.knwznewsradio.com" "Albina","Glick","Giampetro, Anthony D","4 Ralph Ct","Dunellen","Middlesex","NJ","08812","732-924-7882","732-782-6701","albina@glick.com","http://www.giampetroanthonyd.com" "Alishia","Sergi","Milford Enterprises Inc","2742 Distribution Way","New York","New York","NY",10025,"212-860-1579","212-753-2740","asergi@gmail.com","http://www.milfordenterprisesinc.com" "Solange","Shinko","Mosocco, Ronald A","426 Wolf St","Metairie","Jefferson","LA",70002,"504-979-9175","504-265-8174","solange@shinko.com","http://www.mosoccoronalda.com" "Jose","Stockham","Tri State Refueler Co","128 Bransten Rd","New York","New York","NY",10011,"212-675-8570","212-569-4233","jose@yahoo.com","http://www.tristaterefuelerco.com" "Rozella","Ostrosky","Parkway Company","17 Morena Blvd","Camarillo","Ventura","CA",93012,"805-832-6163","805-609-1531","rozella.ostrosky@ostrosky.com","http://www.parkwaycompany.com" "Valentine","Gillian","Fbs Business Finance","775 W 17th St","San Antonio","Bexar","TX",78204,"210-812-9597","210-300-6244","valentine_gillian@gmail.com","http://www.fbsbusinessfinance.com" "Kati","Rulapaugh","Eder Assocs Consltng Engrs Pc","6980 Dorsett Rd","Abilene","Dickinson","KS",67410,"785-463-7829","785-219-7724","kati.rulapaugh@hotmail.com","http://www.ederassocsconsltngengrspc.com" "Youlanda","Schemmer","Tri M Tool Inc","2881 Lewis Rd","Prineville","Crook","OR",97754,"541-548-8197","541-993-2611","youlanda@aol.com","http://www.trimtoolinc.com" "Dyan","Oldroyd","International Eyelets Inc","7219 Woodfield Rd","Overland Park","Johnson","KS",66204,"913-413-4604","913-645-8918","doldroyd@aol.com","http://www.internationaleyeletsinc.com" "Roxane","Campain","Rapid Trading Intl","1048 Main St","Fairbanks","Fairbanks North Star","AK",99708,"907-231-4722","907-335-6568","roxane@hotmail.com","http://www.rapidtradingintl.com" "Lavera","Perin","Abc Enterprises Inc","678 3rd Ave","Miami","Miami-Dade","FL",33196,"305-606-7291","305-995-2078","lperin@perin.org","http://www.abcenterprisesinc.com" "Erick","Ferencz","Cindy Turner Associates","20 S Babcock St","Fairbanks","Fairbanks North Star","AK",99712,"907-741-1044","907-227-6777","erick.ferencz@aol.com","http://www.cindyturnerassociates.com" "Fatima","Saylors","Stanton, James D Esq","2 Lighthouse Ave","Hopkins","Hennepin","MN",55343,"952-768-2416","952-479-2375","fsaylors@saylors.org","http://www.stantonjamesdesq.com" "Jina","Briddick","Grace Pastries Inc","38938 Park Blvd","Boston","Suffolk","MA","02128","617-399-5124","617-997-5771","jina_briddick@briddick.com","http://www.gracepastriesinc.com" "Kanisha","Waycott","Schroer, Gene E Esq","5 Tomahawk Dr","Los Angeles","Los Angeles","CA",90006,"323-453-2780","323-315-7314","kanisha_waycott@yahoo.com","http://www.schroergeneeesq.com" "Emerson","Bowley","Knights Inn","762 S Main St","Madison","Dane","WI",53711,"608-336-7444","608-658-7940","emerson.bowley@bowley.org","http://www.knightsinn.com" "Blair","Malet","Bollinger Mach Shp & Shipyard","209 Decker Dr","Philadelphia","Philadelphia","PA",19132,"215-907-9111","215-794-4519","bmalet@yahoo.com","http://www.bollingermachshpshipyard.com" "Brock","Bolognia","Orinda News","4486 W O St #1","New York","New York","NY",10003,"212-402-9216","212-617-5063","bbolognia@yahoo.com","http://www.orindanews.com" "Lorrie","Nestle","Ballard Spahr Andrews","39 S 7th St","Tullahoma","Coffee","TN",37388,"931-875-6644","931-303-6041","lnestle@hotmail.com","http://www.ballardspahrandrews.com" "Sabra","Uyetake","Lowy Limousine Service","98839 Hawthorne Blvd #6101","Columbia","Richland","SC",29201,"803-925-5213","803-681-3678","sabra@uyetake.org","http://www.lowylimousineservice.com" "Marjory","Mastella","Vicon Corporation","71 San Mateo Ave","Wayne","Delaware","PA",19087,"610-814-5533","610-379-7125","mmastella@mastella.com","http://www.viconcorporation.com" "Karl","Klonowski","Rossi, Michael M","76 Brooks St #9","Flemington","Hunterdon","NJ","08822","908-877-6135","908-470-4661","karl_klonowski@yahoo.com","http://www.rossimichaelm.com" "Tonette","Wenner","Northwest Publishing","4545 Courthouse Rd","Westbury","Nassau","NY",11590,"516-968-6051","516-333-4861","twenner@aol.com","http://www.northwestpublishing.com" "Amber","Monarrez","Branford Wire & Mfg Co","14288 Foster Ave #4121","Jenkintown","Montgomery","PA",19046,"215-934-8655","215-329-6386","amber_monarrez@monarrez.org","http://www.branfordwiremfgco.com" "Shenika","Seewald","East Coast Marketing","4 Otis St","Van Nuys","Los Angeles","CA",91405,"818-423-4007","818-749-8650","shenika@gmail.com","http://www.eastcoastmarketing.com" "Delmy","Ahle","Wye Technologies Inc","65895 S 16th St","Providence","Providence","RI","02909","401-458-2547","401-559-8961","delmy.ahle@hotmail.com","http://www.wyetechnologiesinc.com" "Deeanna","Juhas","Healy, George W Iv","14302 Pennsylvania Ave","Huntingdon Valley","Montgomery","PA",19006,"215-211-9589","215-417-9563","deeanna_juhas@gmail.com","http://www.healygeorgewiv.com" "Blondell","Pugh","Alpenlite Inc","201 Hawk Ct","Providence","Providence","RI","02904","401-960-8259","401-300-8122","bpugh@aol.com","http://www.alpenliteinc.com" "Jamal","Vanausdal","Hubbard, Bruce Esq","53075 Sw 152nd Ter #615","Monroe Township","Middlesex","NJ","08831","732-234-1546","732-904-2931","jamal@vanausdal.org","http://www.hubbardbruceesq.com" "Cecily","Hollack","Arthur A Oliver & Son Inc","59 N Groesbeck Hwy","Austin","Travis","TX",78731,"512-486-3817","512-861-3814","cecily@hollack.org","http://www.arthuraoliversoninc.com" "Carmelina","Lindall","George Jessop Carter Jewelers","2664 Lewis Rd","Littleton","Douglas","CO",80126,"303-724-7371","303-874-5160","carmelina_lindall@lindall.com","http://www.georgejessopcarterjewelers.com" "Maurine","Yglesias","Schultz, Thomas C Md","59 Shady Ln #53","Milwaukee","Milwaukee","WI",53214,"414-748-1374","414-573-7719","maurine_yglesias@yglesias.com","http://www.schultzthomascmd.com" "Tawna","Buvens","H H H Enterprises Inc","3305 Nabell Ave #679","New York","New York","NY",10009,"212-674-9610","212-462-9157","tawna@gmail.com","http://www.hhhenterprisesinc.com" "Penney","Weight","Hawaiian King Hotel","18 Fountain St","Anchorage","Anchorage","AK",99515,"907-797-9628","907-873-2882","penney_weight@aol.com","http://www.hawaiiankinghotel.com" "Elly","Morocco","Killion Industries","7 W 32nd St","Erie","Erie","PA",16502,"814-393-5571","814-420-3553","elly_morocco@gmail.com","http://www.killionindustries.com" "Ilene","Eroman","Robinson, William J Esq","2853 S Central Expy","Glen Burnie","Anne Arundel","MD",21061,"410-914-9018","410-937-4543","ilene.eroman@hotmail.com","http://www.robinsonwilliamjesq.com" "Vallie","Mondella","Private Properties","74 W College St","Boise","Ada","ID",83707,"208-862-5339","208-737-8439","vmondella@mondella.com","http://www.privateproperties.com" "Kallie","Blackwood","Rowley Schlimgen Inc","701 S Harrison Rd","San Francisco","San Francisco","CA",94104,"415-315-2761","415-604-7609","kallie.blackwood@gmail.com","http://www.rowleyschlimgeninc.com" "Johnetta","Abdallah","Forging Specialties","1088 Pinehurst St","Chapel Hill","Orange","NC",27514,"919-225-9345","919-715-3791","johnetta_abdallah@aol.com","http://www.forgingspecialties.com" "Bobbye","Rhym","Smits, Patricia Garity","30 W 80th St #1995","San Carlos","San Mateo","CA",94070,"650-528-5783","650-811-9032","brhym@rhym.com","http://www.smitspatriciagarity.com" "Micaela","Rhymes","H Lee Leonard Attorney At Law","20932 Hedley St","Concord","Contra Costa","CA",94520,"925-647-3298","925-522-7798","micaela_rhymes@gmail.com","http://www.hleeleonardattorneyatlaw.com" "Tamar","Hoogland","A K Construction Co","2737 Pistorio Rd #9230","London","Madison","OH",43140,"740-343-8575","740-526-5410","tamar@hotmail.com","http://www.akconstructionco.com" "Moon","Parlato","Ambelang, Jessica M Md","74989 Brandon St","Wellsville","Allegany","NY",14895,"585-866-8313","585-498-4278","moon@yahoo.com","http://www.ambelangjessicammd.com" "Laurel","Reitler","Q A Service","6 Kains Ave","Baltimore","Baltimore City","MD",21215,"410-520-4832","410-957-6903","laurel_reitler@reitler.com","http://www.qaservice.com" "Delisa","Crupi","Wood & Whitacre Contractors","47565 W Grand Ave","Newark","Essex","NJ","07105","973-354-2040","973-847-9611","delisa.crupi@crupi.com","http://www.woodwhitacrecontractors.com" "Viva","Toelkes","Mark Iv Press Ltd","4284 Dorigo Ln","Chicago","Cook","IL",60647,"773-446-5569","773-352-3437","viva.toelkes@gmail.com","http://www.markivpressltd.com" "Elza","Lipke","Museum Of Science & Industry","6794 Lake Dr E","Newark","Essex","NJ","07104","973-927-3447","973-796-3667","elza@yahoo.com","http://www.museumofscienceindustry.com" "Devorah","Chickering","Garrison Ind","31 Douglas Blvd #950","Clovis","Curry","NM",88101,"505-975-8559","505-950-1763","devorah@hotmail.com","http://www.garrisonind.com" "Timothy","Mulqueen","Saronix Nymph Products","44 W 4th St","Staten Island","Richmond","NY",10309,"718-332-6527","718-654-7063","timothy_mulqueen@mulqueen.org","http://www.saronixnymphproducts.com" "Arlette","Honeywell","Smc Inc","11279 Loytan St","Jacksonville","Duval","FL",32254,"904-775-4480","904-514-9918","ahoneywell@honeywell.com","http://www.smcinc.com" "Dominque","Dickerson","E A I Electronic Assocs Inc","69 Marquette Ave","Hayward","Alameda","CA",94545,"510-993-3758","510-901-7640","dominque.dickerson@dickerson.org","http://www.eaielectronicassocsinc.com" "Lettie","Isenhower","Conte, Christopher A Esq","70 W Main St","Beachwood","Cuyahoga","OH",44122,"216-657-7668","216-733-8494","lettie_isenhower@yahoo.com","http://www.contechristopheraesq.com" "Myra","Munns","Anker Law Office","461 Prospect Pl #316","Euless","Tarrant","TX",76040,"817-914-7518","817-451-3518","mmunns@cox.net","http://www.ankerlawoffice.com" "Stephaine","Barfield","Beutelschies & Company","47154 Whipple Ave Nw","Gardena","Los Angeles","CA",90247,"310-774-7643","310-968-1219","stephaine@barfield.com","http://www.beutelschiescompany.com" "Lai","Gato","Fligg, Kenneth I Jr","37 Alabama Ave","Evanston","Cook","IL",60201,"847-728-7286","847-957-4614","lai.gato@gato.org","http://www.fliggkennethijr.com" "Stephen","Emigh","Sharp, J Daniel Esq","3777 E Richmond St #900","Akron","Summit","OH",44302,"330-537-5358","330-700-2312","stephen_emigh@hotmail.com","http://www.sharpjdanielesq.com" "Tyra","Shields","Assink, Anne H Esq","3 Fort Worth Ave","Philadelphia","Philadelphia","PA",19106,"215-255-1641","215-228-8264","tshields@gmail.com","http://www.assinkannehesq.com" "Tammara","Wardrip","Jewel My Shop Inc","4800 Black Horse Pike","Burlingame","San Mateo","CA",94010,"650-803-1936","650-216-5075","twardrip@cox.net","http://www.jewelmyshopinc.com" "Cory","Gibes","Chinese Translation Resources","83649 W Belmont Ave","San Gabriel","Los Angeles","CA",91776,"626-572-1096","626-696-2777","cory.gibes@gmail.com","http://www.chinesetranslationresources.com" "Danica","Bruschke","Stevens, Charles T","840 15th Ave","Waco","McLennan","TX",76708,"254-782-8569","254-205-1422","danica_bruschke@gmail.com","http://www.stevenscharlest.com" "Wilda","Giguere","Mclaughlin, Luther W Cpa","1747 Calle Amanecer #2","Anchorage","Anchorage","AK",99501,"907-870-5536","907-914-9482","wilda@cox.net","http://www.mclaughlinlutherwcpa.com" "Elvera","Benimadho","Tree Musketeers","99385 Charity St #840","San Jose","Santa Clara","CA",95110,"408-703-8505","408-440-8447","elvera.benimadho@cox.net","http://www.treemusketeers.com" "Carma","Vanheusen","Springfield Div Oh Edison Co","68556 Central Hwy","San Leandro","Alameda","CA",94577,"510-503-7169","510-452-4835","carma@cox.net","http://www.springfielddivohedisonco.com" "Malinda","Hochard","Logan Memorial Hospital","55 Riverside Ave","Indianapolis","Marion","IN",46202,"317-722-5066","317-472-2412","malinda.hochard@yahoo.com","http://www.loganmemorialhospital.com" "Natalie","Fern","Kelly, Charles G Esq","7140 University Ave","Rock Springs","Sweetwater","WY",82901,"307-704-8713","307-279-3793","natalie.fern@hotmail.com","http://www.kellycharlesgesq.com" "Lisha","Centini","Industrial Paper Shredders Inc","64 5th Ave #1153","Mc Lean","Fairfax","VA",22102,"703-235-3937","703-475-7568","lisha@centini.org","http://www.industrialpapershreddersinc.com" "Arlene","Klusman","Beck Horizon Builders","3 Secor Rd","New Orleans","Orleans","LA",70112,"504-710-5840","504-946-1807","arlene_klusman@gmail.com","http://www.beckhorizonbuilders.com" "Alease","Buemi","Porto Cayo At Hawks Cay","4 Webbs Chapel Rd","Boulder","Boulder","CO",80303,"303-301-4946","303-521-9860","alease@buemi.com","http://www.portocayoathawkscay.com" "Louisa","Cronauer","Pacific Grove Museum Ntrl Hist","524 Louisiana Ave Nw","San Leandro","Alameda","CA",94577,"510-828-7047","510-472-7758","louisa@cronauer.com","http://www.pacificgrovemuseumntrlhist.com" "Angella","Cetta","Bender & Hatley Pc","185 Blackstone Bldge","Honolulu","Honolulu","HI",96817,"808-892-7943","808-475-2310","angella.cetta@hotmail.com","http://www.benderhatleypc.com" "Cyndy","Goldammer","Di Cristina J & Son","170 Wyoming Ave","Burnsville","Dakota","MN",55337,"952-334-9408","952-938-9457","cgoldammer@cox.net","http://www.dicristinajson.com" "Rosio","Cork","Green Goddess","4 10th St W","High Point","Guilford","NC",27263,"336-243-5659","336-497-4407","rosio.cork@gmail.com","http://www.greengoddess.com" "Celeste","Korando","American Arts & Graphics","7 W Pinhook Rd","Lynbrook","Nassau","NY",11563,"516-509-2347","516-365-7266","ckorando@hotmail.com","http://www.americanartsgraphics.com" "Twana","Felger","Opryland Hotel","1 Commerce Way","Portland","Washington","OR",97224,"503-939-3153","503-909-7167","twana.felger@felger.org","http://www.oprylandhotel.com" "Estrella","Samu","Marking Devices Pubg Co","64 Lakeview Ave","Beloit","Rock","WI",53511,"608-976-7199","608-942-8836","estrella@aol.com","http://www.markingdevicespubgco.com" "Donte","Kines","W Tc Industries Inc","3 Aspen St","Worcester","Worcester","MA","01602","508-429-8576","508-843-1426","dkines@hotmail.com","http://www.wtcindustriesinc.com" "Tiffiny","Steffensmeier","Whitehall Robbins Labs Divsn","32860 Sierra Rd","Miami","Miami-Dade","FL",33133,"305-385-9695","305-304-6573","tiffiny_steffensmeier@cox.net","http://www.whitehallrobbinslabsdivsn.com" "Edna","Miceli","Sampler","555 Main St","Erie","Erie","PA",16502,"814-460-2655","814-299-2877","emiceli@miceli.org","http://www.sampler.com" "Sue","Kownacki","Juno Chefs Incorporated","2 Se 3rd Ave","Mesquite","Dallas","TX",75149,"972-666-3413","972-742-4000","sue@aol.com","http://www.junochefsincorporated.com" "Jesusa","Shin","Carroccio, A Thomas Esq","2239 Shawnee Mission Pky","Tullahoma","Coffee","TN",37388,"931-273-8709","931-739-1551","jshin@shin.com","http://www.carroccioathomasesq.com" "Rolland","Francescon","Stanley, Richard L Esq","2726 Charcot Ave","Paterson","Passaic","NJ","07501","973-649-2922","973-284-4048","rolland@cox.net","http://www.stanleyrichardlesq.com" "Pamella","Schmierer","K Cs Cstm Mouldings Windows","5161 Dorsett Rd","Homestead","Miami-Dade","FL",33030,"305-420-8970","305-575-8481","pamella.schmierer@schmierer.org","http://www.kcscstmmouldingswindows.com" "Glory","Kulzer","Comfort Inn","55892 Jacksonville Rd","Owings Mills","Baltimore","MD",21117,"410-224-9462","410-916-8015","gkulzer@kulzer.org","http://www.comfortinn.com" "Shawna","Palaspas","Windsor, James L Esq","5 N Cleveland Massillon Rd","Thousand Oaks","Ventura","CA",91362,"805-275-3566","805-638-6617","shawna_palaspas@palaspas.org","http://www.windsorjameslesq.com" "Brandon","Callaro","Jackson Shields Yeiser","7 Benton Dr","Honolulu","Honolulu","HI",96819,"808-215-6832","808-240-5168","brandon_callaro@hotmail.com","http://www.jacksonshieldsyeiser.com" "Scarlet","Cartan","Box, J Calvin Esq","9390 S Howell Ave","Albany","Dougherty","GA",31701,"229-735-3378","229-365-9658","scarlet.cartan@yahoo.com","http://www.boxjcalvinesq.com" "Oretha","Menter","Custom Engineering Inc","8 County Center Dr #647","Boston","Suffolk","MA","02210","617-418-5043","617-697-6024","oretha_menter@yahoo.com","http://www.customengineeringinc.com" "Ty","Smith","Bresler Eitel Framg Gllry Ltd","4646 Kaahumanu St","Hackensack","Bergen","NJ","07601","201-672-1553","201-995-3149","tsmith@aol.com","http://www.breslereitelframggllryltd.com" "Xuan","Rochin","Carol, Drake Sparks Esq","2 Monroe St","San Mateo","San Mateo","CA",94403,"650-933-5072","650-247-2625","xuan@gmail.com","http://www.caroldrakesparksesq.com" "Lindsey","Dilello","Biltmore Investors Bank","52777 Leaders Heights Rd","Ontario","San Bernardino","CA",91761,"909-639-9887","909-589-1693","lindsey.dilello@hotmail.com","http://www.biltmoreinvestorsbank.com" "Devora","Perez","Desco Equipment Corp","72868 Blackington Ave","Oakland","Alameda","CA",94606,"510-955-3016","510-755-9274","devora_perez@perez.org","http://www.descoequipmentcorp.com" "Herman","Demesa","Merlin Electric Co","9 Norristown Rd","Troy","Rensselaer","NY",12180,"518-497-2940","518-931-7852","hdemesa@cox.net","http://www.merlinelectricco.com" "Rory","Papasergi","Bailey Cntl Co Div Babcock","83 County Road 437 #8581","Clarks Summit","Lackawanna","PA",18411,"570-867-7489","570-469-8401","rpapasergi@cox.net","http://www.baileycntlcodivbabcock.com" "Talia","Riopelle","Ford Brothers Wholesale Inc","1 N Harlem Ave #9","Orange","Essex","NJ","07050","973-245-2133","973-818-9788","talia_riopelle@aol.com","http://www.fordbrotherswholesaleinc.com" "Van","Shire","Cambridge Inn","90131 J St","Pittstown","Hunterdon","NJ","08867","908-409-2890","908-448-1209","van.shire@shire.com","http://www.cambridgeinn.com" "Lucina","Lary","Matricciani, Albert J Jr","8597 W National Ave","Cocoa","Brevard","FL",32922,"321-749-4981","321-632-4668","lucina_lary@cox.net","http://www.matriccianialbertjjr.com" "Bok","Isaacs","Nelson Hawaiian Ltd","6 Gilson St","Bronx","Bronx","NY",10468,"718-809-3762","718-478-8568","bok.isaacs@aol.com","http://www.nelsonhawaiianltd.com" "Rolande","Spickerman","Neland Travel Agency","65 W Maple Ave","Pearl City","Honolulu","HI",96782,"808-315-3077","808-526-5863","rolande.spickerman@spickerman.com","http://www.nelandtravelagency.com" "Howard","Paulas","Asendorf, J Alan Esq","866 34th Ave","Denver","Denver","CO",80231,"303-623-4241","303-692-3118","hpaulas@gmail.com","http://www.asendorfjalanesq.com" "Kimbery","Madarang","Silberman, Arthur L Esq","798 Lund Farm Way","Rockaway","Morris","NJ","07866","973-310-1634","973-225-6259","kimbery_madarang@cox.net","http://www.silbermanarthurlesq.com" "Thurman","Manno","Honey Bee Breeding Genetics &","9387 Charcot Ave","Absecon","Atlantic","NJ","08201","609-524-3586","609-234-8376","thurman.manno@yahoo.com","http://www.honeybeebreedinggenetics.com" "Becky","Mirafuentes","Wells Kravitz Schnitzer","30553 Washington Rd","Plainfield","Union","NJ","07062","908-877-8409","908-426-8272","becky.mirafuentes@mirafuentes.com","http://www.wellskravitzschnitzer.com" "Beatriz","Corrington","Prohab Rehabilitation Servs","481 W Lemon St","Middleboro","Plymouth","MA","02346","508-584-4279","508-315-3867","beatriz@yahoo.com","http://www.prohabrehabilitationservs.com" "Marti","Maybury","Eldridge, Kristin K Esq","4 Warehouse Point Rd #7","Chicago","Cook","IL",60638,"773-775-4522","773-539-1058","marti.maybury@yahoo.com","http://www.eldridgekristinkesq.com" "Nieves","Gotter","Vlahos, John J Esq","4940 Pulaski Park Dr","Portland","Multnomah","OR",97202,"503-527-5274","503-455-3094","nieves_gotter@gmail.com","http://www.vlahosjohnjesq.com" "Leatha","Hagele","Ninas Indian Grs & Videos","627 Walford Ave","Dallas","Dallas","TX",75227,"214-339-1809","214-225-5850","lhagele@cox.net","http://www.ninasindiangrsvideos.com" "Valentin","Klimek","Schmid, Gayanne K Esq","137 Pioneer Way","Chicago","Cook","IL",60604,"312-303-5453","312-512-2338","vklimek@klimek.org","http://www.schmidgayannekesq.com" "Melissa","Wiklund","Moapa Valley Federal Credit Un","61 13 Stoneridge #835","Findlay","Hancock","OH",45840,"419-939-3613","419-254-4591","melissa@cox.net","http://www.moapavalleyfederalcreditun.com" "Sheridan","Zane","Kentucky Tennessee Clay Co","2409 Alabama Rd","Riverside","Riverside","CA",92501,"951-645-3605","951-248-6822","sheridan.zane@zane.com","http://www.kentuckytennesseeclayco.com" "Bulah","Padilla","Admiral Party Rentals & Sales","8927 Vandever Ave","Waco","McLennan","TX",76707,"254-463-4368","254-816-8417","bulah_padilla@hotmail.com","http://www.admiralpartyrentalssales.com" "Audra","Kohnert","Nelson, Karolyn King Esq","134 Lewis Rd","Nashville","Davidson","TN",37211,"615-406-7854","615-448-9249","audra@kohnert.com","http://www.nelsonkarolynkingesq.com" "Daren","Weirather","Panasystems","9 N College Ave #3","Milwaukee","Milwaukee","WI",53216,"414-959-2540","414-838-3151","dweirather@aol.com","http://www.panasystems.com" "Fernanda","Jillson","Shank, Edward L Esq","60480 Old Us Highway 51","Preston","Caroline","MD",21655,"410-387-5260","410-724-6472","fjillson@aol.com","http://www.shankedwardlesq.com" "Gearldine","Gellinger","Megibow & Edwards","4 Bloomfield Ave","Irving","Dallas","TX",75061,"972-934-6914","972-821-7118","gearldine_gellinger@gellinger.com","http://www.megibowedwards.com" "Chau","Kitzman","Benoff, Edward Esq","429 Tiger Ln","Beverly Hills","Los Angeles","CA",90212,"310-560-8022","310-969-7230","chau@gmail.com","http://www.benoffedwardesq.com" "Theola","Frey","Woodbridge Free Public Library","54169 N Main St","Massapequa","Nassau","NY",11758,"516-948-5768","516-357-3362","theola_frey@frey.com","http://www.woodbridgefreepubliclibrary.com" "Cheryl","Haroldson","New York Life John Thune","92 Main St","Atlantic City","Atlantic","NJ","08401","609-518-7697","609-263-9243","cheryl@haroldson.org","http://www.newyorklifejohnthune.com" "Laticia","Merced","Alinabal Inc","72 Mannix Dr","Cincinnati","Hamilton","OH",45203,"513-508-7371","513-418-1566","lmerced@gmail.com","http://www.alinabalinc.com" "Carissa","Batman","Poletto, Kim David Esq","12270 Caton Center Dr","Eugene","Lane","OR",97401,"541-326-4074","541-801-5717","carissa.batman@yahoo.com","http://www.polettokimdavidesq.com" "Lezlie","Craghead","Chang, Carolyn Esq","749 W 18th St #45","Smithfield","Johnston","NC",27577,"919-533-3762","919-885-2453","lezlie.craghead@craghead.org","http://www.changcarolynesq.com" "Ozell","Shealy","Silver Bros Inc","8 Industry Ln","New York","New York","NY",10002,"212-332-8435","212-880-8865","oshealy@hotmail.com","http://www.silverbrosinc.com" "Arminda","Parvis","Newtec Inc","1 Huntwood Ave","Phoenix","Maricopa","AZ",85017,"602-906-9419","602-277-3025","arminda@parvis.com","http://www.newtecinc.com" "Reita","Leto","Creative Business Systems","55262 N French Rd","Indianapolis","Marion","IN",46240,"317-234-1135","317-787-5514","reita.leto@gmail.com","http://www.creativebusinesssystems.com" "Yolando","Luczki","Dal Tile Corporation","422 E 21st St","Syracuse","Onondaga","NY",13214,"315-304-4759","315-640-6357","yolando@cox.net","http://www.daltilecorporation.com" "Lizette","Stem","Edward S Katz","501 N 19th Ave","Cherry Hill","Camden","NJ","08002","856-487-5412","856-702-3676","lizette.stem@aol.com","http://www.edwardskatz.com" "Gregoria","Pawlowicz","Oh My Goodknits Inc","455 N Main Ave","Garden City","Nassau","NY",11530,"516-212-1915","516-376-4230","gpawlowicz@yahoo.com","http://www.ohmygoodknitsinc.com" "Carin","Deleo","Redeker, Debbie","1844 Southern Blvd","Little Rock","Pulaski","AR",72202,"501-308-1040","501-409-6072","cdeleo@deleo.com","http://www.redekerdebbie.com" "Chantell","Maynerich","Desert Sands Motel","2023 Greg St","Saint Paul","Ramsey","MN",55101,"651-591-2583","651-776-9688","chantell@yahoo.com","http://www.desertsandsmotel.com" "Dierdre","Yum","Cummins Southern Plains Inc","63381 Jenks Ave","Philadelphia","Philadelphia","PA",19134,"215-325-3042","215-346-4666","dyum@yahoo.com","http://www.cumminssouthernplainsinc.com" "Larae","Gudroe","Lehigh Furn Divsn Lehigh","6651 Municipal Rd","Houma","Terrebonne","LA",70360,"985-890-7262","985-261-5783","larae_gudroe@gmail.com","http://www.lehighfurndivsnlehigh.com" "Latrice","Tolfree","United Van Lines Agent","81 Norris Ave #525","Ronkonkoma","Suffolk","NY",11779,"631-957-7624","631-998-2102","latrice.tolfree@hotmail.com","http://www.unitedvanlinesagent.com" "Kerry","Theodorov","Capitol Reporters","6916 W Main St","Sacramento","Sacramento","CA",95827,"916-591-3277","916-770-7448","kerry.theodorov@gmail.com","http://www.capitolreporters.com" "Dorthy","Hidvegi","Kwik Kopy Printing","9635 S Main St","Boise","Ada","ID",83704,"208-649-2373","208-690-3315","dhidvegi@yahoo.com","http://www.kwikkopyprinting.com" "Fannie","Lungren","Centro Inc","17 Us Highway 111","Round Rock","Williamson","TX",78664,"512-587-5746","512-528-9933","fannie.lungren@yahoo.com","http://www.centroinc.com" "Evangelina","Radde","Campbell, Jan Esq","992 Civic Center Dr","Philadelphia","Philadelphia","PA",19123,"215-964-3284","215-417-5612","evangelina@aol.com","http://www.campbelljanesq.com" "Novella","Degroot","Evans, C Kelly Esq","303 N Radcliffe St","Hilo","Hawaii","HI",96720,"808-477-4775","808-746-1865","novella_degroot@degroot.org","http://www.evansckellyesq.com" "Clay","Hoa","Scat Enterprises","73 Saint Ann St #86","Reno","Washoe","NV",89502,"775-501-8109","775-848-9135","choa@hoa.org","http://www.scatenterprises.com" "Jennifer","Fallick","Nagle, Daniel J Esq","44 58th St","Wheeling","Cook","IL",60090,"847-979-9545","847-800-3054","jfallick@yahoo.com","http://www.nagledanieljesq.com" "Irma","Wolfgramm","Serendiquity Bed & Breakfast","9745 W Main St","Randolph","Morris","NJ","07869","973-545-7355","973-868-8660","irma.wolfgramm@hotmail.com","http://www.serendiquitybedbreakfast.com" "Eun","Coody","Ray Carolyne Realty","84 Bloomfield Ave","Spartanburg","Spartanburg","SC",29301,"864-256-3620","864-594-4578","eun@yahoo.com","http://www.raycarolynerealty.com" "Sylvia","Cousey","Berg, Charles E","287 Youngstown Warren Rd","Hampstead","Carroll","MD",21074,"410-209-9545","410-863-8263","sylvia_cousey@cousey.org","http://www.bergcharlese.com" "Nana","Wrinkles","Ray, Milbern D","6 Van Buren St","Mount Vernon","Westchester","NY",10553,"914-855-2115","914-796-3775","nana@aol.com","http://www.raymilbernd.com" "Layla","Springe","Chadds Ford Winery","229 N Forty Driv","New York","New York","NY",10011,"212-260-3151","212-253-7448","layla.springe@cox.net","http://www.chaddsfordwinery.com" "Joesph","Degonia","A R Packaging","2887 Knowlton St #5435","Berkeley","Alameda","CA",94710,"510-677-9785","510-942-5916","joesph_degonia@degonia.org","http://www.arpackaging.com" "Annabelle","Boord","Corn Popper","523 Marquette Ave","Concord","Middlesex","MA","01742","978-697-6263","978-289-7717","annabelle.boord@cox.net","http://www.cornpopper.com" "Stephaine","Vinning","Birite Foodservice Distr","3717 Hamann Industrial Pky","San Francisco","San Francisco","CA",94104,"415-767-6596","415-712-9530","stephaine@cox.net","http://www.biritefoodservicedistr.com" "Nelida","Sawchuk","Anchorage Museum Of Hist & Art","3 State Route 35 S","Paramus","Bergen","NJ","07652","201-971-1638","201-247-8925","nelida@gmail.com","http://www.anchoragemuseumofhistart.com" "Marguerita","Hiatt","Haber, George D Md","82 N Highway 67","Oakley","Contra Costa","CA",94561,"925-634-7158","925-541-8521","marguerita.hiatt@gmail.com","http://www.habergeorgedmd.com" "Carmela","Cookey","Royal Pontiac Olds Inc","9 Murfreesboro Rd","Chicago","Cook","IL",60623,"773-494-4195","773-297-9391","ccookey@cookey.org","http://www.royalpontiacoldsinc.com" "Junita","Brideau","Leonards Antiques Inc","6 S Broadway St","Cedar Grove","Essex","NJ","07009","973-943-3423","973-582-5469","jbrideau@aol.com","http://www.leonardsantiquesinc.com" "Claribel","Varriano","Meca","6 Harry L Dr #6327","Perrysburg","Wood","OH",43551,"419-544-4900","419-573-2033","claribel_varriano@cox.net","http://www.meca.com" "Benton","Skursky","Nercon Engineering & Mfg Inc","47939 Porter Ave","Gardena","Los Angeles","CA",90248,"310-579-2907","310-694-8466","benton.skursky@aol.com","http://www.nerconengineeringmfginc.com" "Hillary","Skulski","Replica I","9 Wales Rd Ne #914","Homosassa","Citrus","FL",34448,"352-242-2570","352-990-5946","hillary.skulski@aol.com","http://www.replicai.com" "Merilyn","Bayless","20 20 Printing Inc","195 13n N","Santa Clara","Santa Clara","CA",95054,"408-758-5015","408-346-2180","merilyn_bayless@cox.net","http://www.printinginc.com" "Teri","Ennaco","Publishers Group West","99 Tank Farm Rd","Hazleton","Luzerne","PA",18201,"570-889-5187","570-355-1665","tennaco@gmail.com","http://www.publishersgroupwest.com" "Merlyn","Lawler","Nischwitz, Jeffrey L Esq","4671 Alemany Blvd","Jersey City","Hudson","NJ","07304","201-588-7810","201-858-9960","merlyn_lawler@hotmail.com","http://www.nischwitzjeffreylesq.com" "Georgene","Montezuma","Payne Blades & Wellborn Pa","98 University Dr","San Ramon","Contra Costa","CA",94583,"925-615-5185","925-943-3449","gmontezuma@cox.net","http://www.paynebladeswellbornpa.com" "Jettie","Mconnell","Coldwell Bnkr Wright Real Est","50 E Wacker Dr","Bridgewater","Somerset","NJ","08807","908-802-3564","908-602-5258","jmconnell@hotmail.com","http://www.coldwellbnkrwrightrealest.com" "Lemuel","Latzke","Computer Repair Service","70 Euclid Ave #722","Bohemia","Suffolk","NY",11716,"631-748-6479","631-291-4976","lemuel.latzke@gmail.com","http://www.computerrepairservice.com" "Melodie","Knipp","Fleetwood Building Block Inc","326 E Main St #6496","Thousand Oaks","Ventura","CA",91362,"805-690-1682","805-810-8964","mknipp@gmail.com","http://www.fleetwoodbuildingblockinc.com" "Candida","Corbley","Colts Neck Medical Assocs Inc","406 Main St","Somerville","Somerset","NJ","08876","908-275-8357","908-943-6103","candida_corbley@hotmail.com","http://www.coltsneckmedicalassocsinc.com" "Karan","Karpin","New England Taxidermy","3 Elmwood Dr","Beaverton","Washington","OR",97005,"503-940-8327","503-707-5812","karan_karpin@gmail.com","http://www.newenglandtaxidermy.com" "Andra","Scheyer","Ludcke, George O Esq","9 Church St","Salem","Marion","OR",97302,"503-516-2189","503-950-3068","andra@gmail.com","http://www.ludckegeorgeoesq.com" "Felicidad","Poullion","Mccorkle, Tom S Esq","9939 N 14th St","Riverton","Burlington","NJ","08077","856-305-9731","856-828-6021","fpoullion@poullion.com","http://www.mccorkletomsesq.com" "Belen","Strassner","Eagle Software Inc","5384 Southwyck Blvd","Douglasville","Douglas","GA",30135,"770-507-8791","770-802-4003","belen_strassner@aol.com","http://www.eaglesoftwareinc.com" "Gracia","Melnyk","Juvenile & Adult Super","97 Airport Loop Dr","Jacksonville","Duval","FL",32216,"904-235-3633","904-627-4341","gracia@melnyk.com","http://www.juvenileadultsuper.com" "Jolanda","Hanafan","Perez, Joseph J Esq","37855 Nolan Rd","Bangor","Penobscot","ME","04401","207-458-9196","207-233-6185","jhanafan@gmail.com","http://www.perezjosephjesq.com" "Barrett","Toyama","Case Foundation Co","4252 N Washington Ave #9","Kennedale","Tarrant","TX",76060,"817-765-5781","817-577-6151","barrett.toyama@toyama.org","http://www.casefoundationco.com" "Helga","Fredicks","Eis Environmental Engrs Inc","42754 S Ash Ave","Buffalo","Erie","NY",14228,"716-752-4114","716-854-9845","helga_fredicks@yahoo.com","http://www.eisenvironmentalengrsinc.com" "Ashlyn","Pinilla","Art Crafters","703 Beville Rd","Opa Locka","Miami-Dade","FL",33054,"305-670-9628","305-857-5489","apinilla@cox.net","http://www.artcrafters.com" "Fausto","Agramonte","Marriott Hotels Resorts Suites","5 Harrison Rd","New York","New York","NY",10038,"212-313-1783","212-778-3063","fausto_agramonte@yahoo.com","http://www.marriotthotelsresortssuites.com" "Ronny","Caiafa","Remaco Inc","73 Southern Blvd","Philadelphia","Philadelphia","PA",19103,"215-605-7570","215-511-3531","ronny.caiafa@caiafa.org","http://www.remacoinc.com" "Marge","Limmel","Bjork, Robert D Jr","189 Village Park Rd","Crestview","Okaloosa","FL",32536,"850-430-1663","850-330-8079","marge@gmail.com","http://www.bjorkrobertdjr.com" "Norah","Waymire","Carmichael, Jeffery L Esq","6 Middlegate Rd #106","San Francisco","San Francisco","CA",94107,"415-306-7897","415-874-2984","norah.waymire@gmail.com","http://www.carmichaeljefferylesq.com" "Aliza","Baltimore","Andrews, J Robert Esq","1128 Delaware St","San Jose","Santa Clara","CA",95132,"408-504-3552","408-425-1994","aliza@aol.com","http://www.andrewsjrobertesq.com" "Mozell","Pelkowski","Winship & Byrne","577 Parade St","South San Francisco","San Mateo","CA",94080,"650-947-1215","650-960-1069","mpelkowski@pelkowski.org","http://www.winshipbyrne.com" "Viola","Bitsuie","Burton & Davis","70 Mechanic St","Northridge","Los Angeles","CA",91325,"818-864-4875","818-481-5787","viola@gmail.com","http://www.burtondavis.com" "Franklyn","Emard","Olympic Graphic Arts","4379 Highway 116","Philadelphia","Philadelphia","PA",19103,"215-558-8189","215-483-3003","femard@emard.com","http://www.olympicgraphicarts.com" "Willodean","Konopacki","Magnuson","55 Hawthorne Blvd","Lafayette","Lafayette","LA",70506,"337-253-8384","337-774-7564","willodean_konopacki@konopacki.org","http://www.magnuson.com" "Beckie","Silvestrini","A All American Travel Inc","7116 Western Ave","Dearborn","Wayne","MI",48126,"313-533-4884","313-390-7855","beckie.silvestrini@silvestrini.com","http://www.aallamericantravelinc.com" "Rebecka","Gesick","Polykote Inc","2026 N Plankinton Ave #3","Austin","Travis","TX",78754,"512-213-8574","512-693-8345","rgesick@gesick.org","http://www.polykoteinc.com" "Frederica","Blunk","Jets Cybernetics","99586 Main St","Dallas","Dallas","TX",75207,"214-428-2285","214-529-1949","frederica_blunk@gmail.com","http://www.jetscybernetics.com" "Glen","Bartolet","Metlab Testing Services","8739 Hudson St","Vashon","King","WA",98070,"206-697-5796","206-389-1482","glen_bartolet@hotmail.com","http://www.metlabtestingservices.com" "Freeman","Gochal","Kellermann, William T Esq","383 Gunderman Rd #197","Coatesville","Chester","PA",19320,"610-476-3501","610-752-2683","freeman_gochal@aol.com","http://www.kellermannwilliamtesq.com" "Vincent","Meinerding","Arturi, Peter D Esq","4441 Point Term Mkt","Philadelphia","Philadelphia","PA",19143,"215-372-1718","215-829-4221","vincent.meinerding@hotmail.com","http://www.arturipeterdesq.com" "Rima","Bevelacqua","Mcauley Mfg Co","2972 Lafayette Ave","Gardena","Los Angeles","CA",90248,"310-858-5079","310-499-4200","rima@cox.net","http://www.mcauleymfgco.com" "Glendora","Sarbacher","Defur Voran Hanley Radcliff","2140 Diamond Blvd","Rohnert Park","Sonoma","CA",94928,"707-653-8214","707-881-3154","gsarbacher@gmail.com","http://www.defurvoranhanleyradcliff.com" "Avery","Steier","Dill Dill Carr & Stonbraker Pc","93 Redmond Rd #492","Orlando","Orange","FL",32803,"407-808-9439","407-945-8566","avery@cox.net","http://www.dilldillcarrstonbrakerpc.com" "Cristy","Lother","Kleensteel","3989 Portage Tr","Escondido","San Diego","CA",92025,"760-971-4322","760-465-4762","cristy@lother.com","http://www.kleensteel.com" "Nicolette","Brossart","Goulds Pumps Inc Slurry Pump","1 Midway Rd","Westborough","Worcester","MA","01581","508-837-9230","508-504-6388","nicolette_brossart@brossart.com","http://www.gouldspumpsincslurrypump.com" "Tracey","Modzelewski","Kansas City Insurance Report","77132 Coon Rapids Blvd Nw","Conroe","Montgomery","TX",77301,"936-264-9294","936-988-8171","tracey@hotmail.com","http://www.kansascityinsurancereport.com" "Virgina","Tegarden","Berhanu International Foods","755 Harbor Way","Milwaukee","Milwaukee","WI",53226,"414-214-8697","414-411-5744","virgina_tegarden@tegarden.com","http://www.berhanuinternationalfoods.com" "Tiera","Frankel","Roland Ashcroft","87 Sierra Rd","El Monte","Los Angeles","CA",91731,"626-636-4117","626-638-4241","tfrankel@aol.com","http://www.rolandashcroft.com" "Alaine","Bergesen","Hispanic Magazine","7667 S Hulen St #42","Yonkers","Westchester","NY",10701,"914-300-9193","914-654-1426","alaine_bergesen@cox.net","http://www.hispanicmagazine.com" "Earleen","Mai","Little Sheet Metal Co","75684 S Withlapopka Dr #32","Dallas","Dallas","TX",75227,"214-289-1973","214-785-6750","earleen_mai@cox.net","http://www.littlesheetmetalco.com" "Leonida","Gobern","Holmes, Armstead J Esq","5 Elmwood Park Blvd","Biloxi","Harrison","MS",39530,"228-235-5615","228-432-4635","leonida@gobern.org","http://www.holmesarmsteadjesq.com" "Ressie","Auffrey","Faw, James C Cpa","23 Palo Alto Sq","Miami","Miami-Dade","FL",33134,"305-604-8981","305-287-4743","ressie.auffrey@yahoo.com","http://www.fawjamesccpa.com" "Justine","Mugnolo","Evans Rule Company","38062 E Main St","New York","New York","NY",10048,"212-304-9225","212-311-6377","jmugnolo@yahoo.com","http://www.evansrulecompany.com" "Eladia","Saulter","Tyee Productions Inc","3958 S Dupont Hwy #7","Ramsey","Bergen","NJ","07446","201-474-4924","201-365-8698","eladia@saulter.com","http://www.tyeeproductionsinc.com" "Chaya","Malvin","Dunnells & Duvall","560 Civic Center Dr","Ann Arbor","Washtenaw","MI",48103,"734-928-5182","734-408-8174","chaya@malvin.com","http://www.dunnellsduvall.com" "Gwenn","Suffield","Deltam Systems Inc","3270 Dequindre Rd","Deer Park","Suffolk","NY",11729,"631-258-6558","631-295-9879","gwenn_suffield@suffield.org","http://www.deltamsystemsinc.com" "Salena","Karpel","Hammill Mfg Co","1 Garfield Ave #7","Canton","Stark","OH",44707,"330-791-8557","330-618-2579","skarpel@cox.net","http://www.hammillmfgco.com" "Yoko","Fishburne","Sams Corner Store","9122 Carpenter Ave","New Haven","New Haven","CT","06511","203-506-4706","203-840-8634","yoko@fishburne.com","http://www.samscornerstore.com" "Taryn","Moyd","Siskin, Mark J Esq","48 Lenox St","Fairfax","Fairfax City","VA",22030,"703-322-4041","703-938-7939","taryn.moyd@hotmail.com","http://www.siskinmarkjesq.com" "Katina","Polidori","Cape & Associates Real Estate","5 Little River Tpke","Wilmington","Middlesex","MA","01887","978-626-2978","978-679-7429","katina_polidori@aol.com","http://www.capeassociatesrealestate.com" "Rickie","Plumer","Merrill Lynch","3 N Groesbeck Hwy","Toledo","Lucas","OH",43613,"419-693-1334","419-313-5571","rickie.plumer@aol.com","http://www.merrilllynch.com" "Alex","Loader","Sublett, Scott Esq","37 N Elm St #916","Tacoma","Pierce","WA",98409,"253-660-7821","253-875-9222","alex@loader.com","http://www.sublettscottesq.com" "Lashon","Vizarro","Sentry Signs","433 Westminster Blvd #590","Roseville","Placer","CA",95661,"916-741-7884","916-289-4526","lashon@aol.com","http://www.sentrysigns.com" "Lauran","Burnard","Professionals Unlimited","66697 Park Pl #3224","Riverton","Fremont","WY",82501,"307-342-7795","307-453-7589","lburnard@burnard.com","http://www.professionalsunlimited.com" "Ceola","Setter","Southern Steel Shelving Co","96263 Greenwood Pl","Warren","Knox","ME","04864","207-627-7565","207-297-5029","ceola.setter@setter.org","http://www.southernsteelshelvingco.com" "My","Rantanen","Bosco, Paul J","8 Mcarthur Ln","Richboro","Bucks","PA",18954,"215-491-5633","215-647-2158","my@hotmail.com","http://www.boscopaulj.com" "Lorrine","Worlds","Longo, Nicholas J Esq","8 Fair Lawn Ave","Tampa","Hillsborough","FL",33614,"813-769-2939","813-863-6467","lorrine.worlds@worlds.com","http://www.longonicholasjesq.com" "Peggie","Sturiale","Henry County Middle School","9 N 14th St","El Cajon","San Diego","CA",92020,"619-608-1763","619-695-8086","peggie@cox.net","http://www.henrycountymiddleschool.com" "Marvel","Raymo","Edison Supply & Equipment Co","9 Vanowen St","College Station","Brazos","TX",77840,"979-718-8968","979-809-5770","mraymo@yahoo.com","http://www.edisonsupplyequipmentco.com" "Daron","Dinos","Wolf, Warren R Esq","18 Waterloo Geneva Rd","Highland Park","Lake","IL",60035,"847-233-3075","847-265-6609","daron_dinos@cox.net","http://www.wolfwarrenresq.com" "An","Fritz","Linguistic Systems Inc","506 S Hacienda Dr","Atlantic City","Atlantic","NJ","08401","609-228-5265","609-854-7156","an_fritz@hotmail.com","http://www.linguisticsystemsinc.com" "Portia","Stimmel","Peace Christian Center","3732 Sherman Ave","Bridgewater","Somerset","NJ","08807","908-722-7128","908-670-4712","portia.stimmel@aol.com","http://www.peacechristiancenter.com" "Rhea","Aredondo","Double B Foods Inc","25657 Live Oak St","Brooklyn","Kings","NY",11226,"718-560-9537","718-280-4183","rhea_aredondo@cox.net","http://www.doublebfoodsinc.com" "Benedict","Sama","Alexander & Alexander Inc","4923 Carey Ave","Saint Louis","Saint Louis City","MO",63104,"314-787-1588","314-858-4832","bsama@cox.net","http://www.alexanderalexanderinc.com" "Alyce","Arias","Fairbanks Scales","3196 S Rider Trl","Stockton","San Joaquin","CA",95207,"209-317-1801","209-242-7022","alyce@arias.org","http://www.fairbanksscales.com" "Heike","Berganza","Cali Sportswear Cutting Dept","3 Railway Ave #75","Little Falls","Passaic","NJ","07424","973-936-5095","973-822-8827","heike@gmail.com","http://www.calisportswearcuttingdept.com" "Carey","Dopico","Garofani, John Esq","87393 E Highland Rd","Indianapolis","Marion","IN",46220,"317-578-2453","317-441-5848","carey_dopico@dopico.org","http://www.garofanijohnesq.com" "Dottie","Hellickson","Thompson Fabricating Co","67 E Chestnut Hill Rd","Seattle","King","WA",98133,"206-540-6076","206-295-5631","dottie@hellickson.org","http://www.thompsonfabricatingco.com" "Deandrea","Hughey","Century 21 Krall Real Estate","33 Lewis Rd #46","Burlington","Alamance","NC",27215,"336-822-7652","336-467-3095","deandrea@yahoo.com","http://www.centurykrallrealestate.com" "Kimberlie","Duenas","Mid Contntl Rlty & Prop Mgmt","8100 Jacksonville Rd #7","Hays","Ellis","KS",67601,"785-629-8542","785-616-1685","kimberlie_duenas@yahoo.com","http://www.midcontntlrltypropmgmt.com" "Martina","Staback","Ace Signs Inc","7 W Wabansia Ave #227","Orlando","Orange","FL",32822,"407-471-6908","407-429-2145","martina_staback@staback.com","http://www.acesignsinc.com" "Skye","Fillingim","Rodeway Inn","25 Minters Chapel Rd #9","Minneapolis","Hennepin","MN",55401,"612-508-2655","612-664-6304","skye_fillingim@yahoo.com","http://www.rodewayinn.com" "Jade","Farrar","Bonnet & Daughter","6882 Torresdale Ave","Columbia","Richland","SC",29201,"803-352-5387","803-975-3405","jade.farrar@yahoo.com","http://www.bonnetdaughter.com" "Charlene","Hamilton","Oshins & Gibbons","985 E 6th Ave","Santa Rosa","Sonoma","CA",95407,"707-300-1771","707-821-8037","charlene.hamilton@hotmail.com","http://www.oshinsgibbons.com" "Geoffrey","Acey","Price Business Services","7 West Ave #1","Palatine","Cook","IL",60067,"847-222-1734","847-556-2909","geoffrey@gmail.com","http://www.pricebusinessservices.com" "Stevie","Westerbeck","Wise, Dennis W Md","26659 N 13th St","Costa Mesa","Orange","CA",92626,"949-867-4077","949-903-3898","stevie.westerbeck@yahoo.com","http://www.wisedenniswmd.com" "Pamella","Fortino","Super 8 Motel","669 Packerland Dr #1438","Denver","Denver","CO",80212,"303-404-2210","303-794-1341","pamella@fortino.com","http://www.supermotel.com" "Harrison","Haufler","John Wagner Associates","759 Eldora St","New Haven","New Haven","CT","06515","203-801-6193","203-801-8497","hhaufler@hotmail.com","http://www.johnwagnerassociates.com" "Johnna","Engelberg","Thrifty Oil Co","5 S Colorado Blvd #449","Bothell","Snohomish","WA",98021,"425-986-7573","425-700-3751","jengelberg@engelberg.org","http://www.thriftyoilco.com" "Buddy","Cloney","Larkfield Photo","944 Gaither Dr","Strongsville","Cuyahoga","OH",44136,"440-989-5826","440-327-2093","buddy.cloney@yahoo.com","http://www.larkfieldphoto.com" "Dalene","Riden","Silverman Planetarium","66552 Malone Rd","Plaistow","Rockingham","NH","03865","603-315-6839","603-745-7497","dalene.riden@aol.com","http://www.silvermanplanetarium.com" "Jerry","Zurcher","J & F Lumber","77 Massillon Rd #822","Satellite Beach","Brevard","FL",32937,"321-518-5938","321-597-2159","jzurcher@zurcher.org","http://www.jflumber.com" "Haydee","Denooyer","Cleaning Station Inc","25346 New Rd","New York","New York","NY",10016,"212-792-8658","212-782-3493","hdenooyer@denooyer.org","http://www.cleaningstationinc.com" "Joseph","Cryer","Ames Stationers","60 Fillmore Ave","Huntington Beach","Orange","CA",92647,"714-584-2237","714-698-2170","joseph_cryer@cox.net","http://www.amesstationers.com" "Deonna","Kippley","Midas Muffler Shops","57 Haven Ave #90","Southfield","Oakland","MI",48075,"248-913-4677","248-793-4966","deonna_kippley@hotmail.com","http://www.midasmufflershops.com" "Raymon","Calvaresi","Seaboard Securities Inc","6538 E Pomona St #60","Indianapolis","Marion","IN",46222,"317-825-4724","317-342-1532","raymon.calvaresi@gmail.com","http://www.seaboardsecuritiesinc.com" "Alecia","Bubash","Petersen, James E Esq","6535 Joyce St","Wichita Falls","Wichita","TX",76301,"940-276-7922","940-302-3036","alecia@aol.com","http://www.petersenjameseesq.com" "Ma","Layous","Development Authority","78112 Morris Ave","North Haven","New Haven","CT","06473","203-721-3388","203-564-1543","mlayous@hotmail.com","http://www.developmentauthority.com" "Detra","Coyier","Schott Fiber Optics Inc","96950 Hidden Ln","Aberdeen","Harford","MD",21001,"410-739-9277","410-259-2118","detra@aol.com","http://www.schottfiberopticsinc.com" "Terrilyn","Rodeigues","Stuart J Agins","3718 S Main St","New Orleans","Orleans","LA",70130,"504-463-4384","504-635-8518","terrilyn.rodeigues@cox.net","http://www.stuartjagins.com" "Salome","Lacovara","Mitsumi Electronics Corp","9677 Commerce Dr","Richmond","Richmond City","VA",23219,"804-550-5097","804-858-1011","slacovara@gmail.com","http://www.mitsumielectronicscorp.com" "Garry","Keetch","Italian Express Franchise Corp","5 Green Pond Rd #4","Southampton","Bucks","PA",18966,"215-979-8776","215-846-9046","garry_keetch@hotmail.com","http://www.italianexpressfranchisecorp.com" "Matthew","Neither","American Council On Sci & Hlth","636 Commerce Dr #42","Shakopee","Scott","MN",55379,"952-651-7597","952-906-4597","mneither@yahoo.com","http://www.americancouncilonscihlth.com" "Theodora","Restrepo","Kleri, Patricia S Esq","42744 Hamann Industrial Pky #82","Miami","Miami-Dade","FL",33136,"305-936-8226","305-573-1085","theodora.restrepo@restrepo.com","http://www.kleripatriciasesq.com" "Noah","Kalafatis","Twiggs Abrams Blanchard","1950 5th Ave","Milwaukee","Milwaukee","WI",53209,"414-263-5287","414-660-9766","noah.kalafatis@aol.com","http://www.twiggsabramsblanchard.com" "Carmen","Sweigard","Maui Research & Technology Pk","61304 N French Rd","Somerset","Somerset","NJ","08873","732-941-2621","732-445-6940","csweigard@sweigard.com","http://www.mauiresearchtechnologypk.com" "Lavonda","Hengel","Bradley Nameplate Corp","87 Imperial Ct #79","Fargo","Cass","ND",58102,"701-898-2154","701-421-7080","lavonda@cox.net","http://www.bradleynameplatecorp.com" "Junita","Stoltzman","Geonex Martel Inc","94 W Dodge Rd","Carson City","Carson City","NV",89701,"775-638-9963","775-578-1214","junita@aol.com","http://www.geonexmartelinc.com" "Herminia","Nicolozakes","Sea Island Div Of Fstr Ind Inc","4 58th St #3519","Scottsdale","Maricopa","AZ",85254,"602-954-5141","602-304-6433","herminia@nicolozakes.org","http://www.seaislanddivoffstrindinc.com" "Casie","Good","Papay, Debbie J Esq","5221 Bear Valley Rd","Nashville","Davidson","TN",37211,"615-390-2251","615-825-4297","casie.good@aol.com","http://www.papaydebbiejesq.com" "Reena","Maisto","Lane Promotions","9648 S Main","Salisbury","Wicomico","MD",21801,"410-351-1863","410-951-2667","reena@hotmail.com","http://www.lanepromotions.com" "Mirta","Mallett","Stephen Kennerly Archts Inc Pc","7 S San Marcos Rd","New York","New York","NY",10004,"212-870-1286","212-745-6948","mirta_mallett@gmail.com","http://www.stephenkennerlyarchtsincpc.com" "Cathrine","Pontoriero","Business Systems Of Wis Inc","812 S Haven St","Amarillo","Randall","TX",79109,"806-703-1435","806-558-5848","cathrine.pontoriero@pontoriero.com","http://www.businesssystemsofwisinc.com" "Filiberto","Tawil","Flash, Elena Salerno Esq","3882 W Congress St #799","Los Angeles","Los Angeles","CA",90016,"323-765-2528","323-842-8226","ftawil@hotmail.com","http://www.flashelenasalernoesq.com" "Raul","Upthegrove","Neeley, Gregory W Esq","4 E Colonial Dr","La Mesa","San Diego","CA",91942,"619-509-5282","619-666-4765","rupthegrove@yahoo.com","http://www.neeleygregorywesq.com" "Sarah","Candlish","Alabama Educational Tv Comm","45 2nd Ave #9759","Atlanta","Fulton","GA",30328,"770-732-1194","770-531-2842","sarah.candlish@gmail.com","http://www.alabamaeducationaltvcomm.com" "Lucy","Treston","Franz Inc","57254 Brickell Ave #372","Worcester","Worcester","MA","01602","508-769-5250","508-502-5634","lucy@cox.net","http://www.franzinc.com" "Judy","Aquas","Plantation Restaurant","8977 Connecticut Ave Nw #3","Niles","Berrien","MI",49120,"269-756-7222","269-431-9464","jaquas@aquas.com","http://www.plantationrestaurant.com" "Yvonne","Tjepkema","Radio Communications Co","9 Waydell St","Fairfield","Essex","NJ","07004","973-714-1721","973-976-8627","yvonne.tjepkema@hotmail.com","http://www.radiocommunicationsco.com" "Kayleigh","Lace","Dentalaw Divsn Hlth Care","43 Huey P Long Ave","Lafayette","Lafayette","LA",70508,"337-740-9323","337-751-2326","kayleigh.lace@yahoo.com","http://www.dentalawdivsnhlthcare.com" "Felix","Hirpara","American Speedy Printing Ctrs","7563 Cornwall Rd #4462","Denver","Lancaster","PA",17517,"717-491-5643","717-583-1497","felix_hirpara@cox.net","http://www.americanspeedyprintingctrs.com" "Tresa","Sweely","Grayson, Grant S Esq","22 Bridle Ln","Valley Park","Saint Louis","MO",63088,"314-359-9566","314-231-3514","tresa_sweely@hotmail.com","http://www.graysongrantsesq.com" "Kristeen","Turinetti","Jeanerette Middle School","70099 E North Ave","Arlington","Tarrant","TX",76013,"817-213-8851","817-947-9480","kristeen@gmail.com","http://www.jeanerettemiddleschool.com" "Jenelle","Regusters","Haavisto, Brian F Esq","3211 E Northeast Loop","Tampa","Hillsborough","FL",33619,"813-932-8715","813-357-7296","jregusters@regusters.com","http://www.haavistobrianfesq.com" "Renea","Monterrubio","Wmmt Radio Station","26 Montgomery St","Atlanta","Fulton","GA",30328,"770-679-4752","770-930-9967","renea@hotmail.com","http://www.wmmtradiostation.com" "Olive","Matuszak","Colony Paints Sales Ofc & Plnt","13252 Lighthouse Ave","Cathedral City","Riverside","CA",92234,"760-938-6069","760-745-2649","olive@aol.com","http://www.colonypaintssalesofcplnt.com" "Ligia","Reiber","Floral Expressions","206 Main St #2804","Lansing","Ingham","MI",48933,"517-906-1108","517-747-7664","lreiber@cox.net","http://www.floralexpressions.com" "Christiane","Eschberger","Casco Services Inc","96541 W Central Blvd","Phoenix","Maricopa","AZ",85034,"602-390-4944","602-330-6894","christiane.eschberger@yahoo.com","http://www.cascoservicesinc.com" "Goldie","Schirpke","Reuter, Arthur C Jr","34 Saint George Ave #2","Bangor","Penobscot","ME","04401","207-295-7569","207-748-3722","goldie.schirpke@yahoo.com","http://www.reuterarthurcjr.com" "Loreta","Timenez","Kaminski, Katherine Andritsaki","47857 Coney Island Ave","Clinton","Prince Georges","MD",20735,"301-696-6420","301-392-6698","loreta.timenez@hotmail.com","http://www.kaminskikatherineandritsaki.com" "Fabiola","Hauenstein","Sidewinder Products Corp","8573 Lincoln Blvd","York","York","PA",17404,"717-809-3119","717-344-2804","fabiola.hauenstein@hauenstein.org","http://www.sidewinderproductscorp.com" "Amie","Perigo","General Foam Corporation","596 Santa Maria Ave #7913","Mesquite","Dallas","TX",75150,"972-419-7946","972-898-1033","amie.perigo@yahoo.com","http://www.generalfoamcorporation.com" "Raina","Brachle","Ikg Borden Divsn Harsco Corp","3829 Ventura Blvd","Butte","Silver Bow","MT",59701,"406-318-1515","406-374-7752","raina.brachle@brachle.org","http://www.ikgbordendivsnharscocorp.com" "Erinn","Canlas","Anchor Computer Inc","13 S Hacienda Dr","Livingston","Essex","NJ","07039","973-767-3008","973-563-9502","erinn.canlas@canlas.com","http://www.anchorcomputerinc.com" "Cherry","Lietz","Sebring & Co","40 9th Ave Sw #91","Waterford","Oakland","MI",48329,"248-980-6904","248-697-7722","cherry@lietz.com","http://www.sebringco.com" "Kattie","Vonasek","H A C Farm Lines Co Optv Assoc","2845 Boulder Crescent St","Cleveland","Cuyahoga","OH",44103,"216-923-3715","216-270-9653","kattie@vonasek.org","http://www.hacfarmlinescooptvassoc.com" "Lilli","Scriven","Hunter, John J Esq","33 State St","Abilene","Taylor","TX",79601,"325-631-1560","325-667-7868","lilli@aol.com","http://www.hunterjohnjesq.com" "Whitley","Tomasulo","Freehold Fence Co","2 S 15th St","Fort Worth","Tarrant","TX",76107,"817-526-4408","817-819-7799","whitley.tomasulo@aol.com","http://www.freeholdfenceco.com" "Barbra","Adkin","Binswanger","4 Kohler Memorial Dr","Brooklyn","Kings","NY",11230,"718-201-3751","718-732-9475","badkin@hotmail.com","http://www.binswanger.com" "Hermila","Thyberg","Chilton Malting Co","1 Rancho Del Mar Shopping C","Providence","Providence","RI","02903","401-893-4882","401-885-7681","hermila_thyberg@hotmail.com","http://www.chiltonmaltingco.com" "Jesusita","Flister","Schoen, Edward J Jr","3943 N Highland Ave","Lancaster","Lancaster","PA",17601,"717-885-9118","717-686-7564","jesusita.flister@hotmail.com","http://www.schoenedwardjjr.com" "Caitlin","Julia","Helderman, Seymour Cpa","5 Williams St","Johnston","Providence","RI","02919","401-948-4982","401-552-9059","caitlin.julia@julia.org","http://www.heldermanseymourcpa.com" "Roosevelt","Hoffis","Denbrook, Myron","60 Old Dover Rd","Hialeah","Miami-Dade","FL",33014,"305-622-4739","305-302-1135","roosevelt.hoffis@aol.com","http://www.denbrookmyron.com" "Helaine","Halter","Lippitt, Mike","8 Sheridan Rd","Jersey City","Hudson","NJ","07304","201-832-4168","201-412-3040","hhalter@yahoo.com","http://www.lippittmike.com" "Lorean","Martabano","Hiram, Hogg P Esq","85092 Southern Blvd","San Antonio","Bexar","TX",78204,"210-856-4979","210-634-2447","lorean.martabano@hotmail.com","http://www.hiramhoggpesq.com" "France","Buzick","In Travel Agency","64 Newman Springs Rd E","Brooklyn","Kings","NY",11219,"718-478-8504","718-853-3740","france.buzick@yahoo.com","http://www.intravelagency.com" "Justine","Ferrario","Newhart Foods Inc","48 Stratford Ave","Pomona","Los Angeles","CA",91768,"909-993-3242","909-631-5703","jferrario@hotmail.com","http://www.newhartfoodsinc.com" "Adelina","Nabours","Courtyard By Marriott","80 Pittsford Victor Rd #9","Cleveland","Cuyahoga","OH",44103,"216-230-4892","216-937-5320","adelina_nabours@gmail.com","http://www.courtyardbymarriott.com" "Derick","Dhamer","Studer, Eugene A Esq","87163 N Main Ave","New York","New York","NY",10013,"212-304-4515","212-225-9676","ddhamer@cox.net","http://www.studereugeneaesq.com" "Jerry","Dallen","Seashore Supply Co Waretown","393 Lafayette Ave","Richmond","Richmond City","VA",23219,"804-762-9576","804-808-9574","jerry.dallen@yahoo.com","http://www.seashoresupplycowaretown.com" "Leota","Ragel","Mayar Silk Inc","99 5th Ave #33","Trion","Chattooga","GA",30753,"706-221-4243","706-616-5131","leota.ragel@gmail.com","http://www.mayarsilkinc.com" "Jutta","Amyot","National Medical Excess Corp","49 N Mays St","Broussard","Lafayette","LA",70518,"337-515-1438","337-991-8070","jamyot@hotmail.com","http://www.nationalmedicalexcesscorp.com" "Aja","Gehrett","Stero Company","993 Washington Ave","Nutley","Essex","NJ","07110","973-544-2677","973-986-4456","aja_gehrett@hotmail.com","http://www.sterocompany.com" "Kirk","Herritt","Hasting, H Duane Esq","88 15th Ave Ne","Vestal","Broome","NY",13850,"607-407-3716","607-350-7690","kirk.herritt@aol.com","http://www.hastinghduaneesq.com" "Leonora","Mauson","Insty Prints","3381 E 40th Ave","Passaic","Passaic","NJ","07055","973-412-2995","973-355-2120","leonora@yahoo.com","http://www.instyprints.com" "Winfred","Brucato","Glenridge Manor Mobile Home Pk","201 Ridgewood Rd","Moscow","Latah","ID",83843,"208-252-4552","208-793-4108","winfred_brucato@hotmail.com","http://www.glenridgemanormobilehomepk.com" "Tarra","Nachor","Circuit Solution Inc","39 Moccasin Dr","San Francisco","San Francisco","CA",94104,"415-411-1775","415-284-2730","tarra.nachor@cox.net","http://www.circuitsolutioninc.com" "Corinne","Loder","Local Office","4 Carroll St","North Attleboro","Bristol","MA","02760","508-942-4186","508-618-7826","corinne@loder.org","http://www.localoffice.com" "Dulce","Labreche","Lee Kilkelly Paulson & Kabaker","9581 E Arapahoe Rd","Rochester","Oakland","MI",48307,"248-357-8718","248-811-5696","dulce_labreche@yahoo.com","http://www.leekilkellypaulsonkabaker.com" "Kate","Keneipp","Davis, Maxon R Esq","33 N Michigan Ave","Green Bay","Brown","WI",54301,"920-353-6377","920-355-1610","kate_keneipp@yahoo.com","http://www.davismaxonresq.com" "Kaitlyn","Ogg","Garrison, Paul E Esq","2 S Biscayne Blvd","Baltimore","Baltimore City","MD",21230,"410-665-4903","410-773-3862","kaitlyn.ogg@gmail.com","http://www.garrisonpauleesq.com" "Sherita","Saras","Black History Resource Center","8 Us Highway 22","Colorado Springs","El Paso","CO",80937,"719-669-1664","719-547-9543","sherita.saras@cox.net","http://www.blackhistoryresourcecenter.com" "Lashawnda","Stuer","Rodriguez, J Christopher Esq","7422 Martin Ave #8","Toledo","Lucas","OH",43607,"419-588-8719","419-399-1744","lstuer@cox.net","http://www.rodriguezjchristopheresq.com" "Ernest","Syrop","Grant Family Health Center","94 Chase Rd","Hyattsville","Prince Georges","MD",20785,"301-998-9644","301-257-4883","ernest@cox.net","http://www.grantfamilyhealthcenter.com" "Nobuko","Halsey","Goeman Wood Products Inc","8139 I Hwy 10 #92","New Bedford","Bristol","MA","02745","508-855-9887","508-897-7916","nobuko.halsey@yahoo.com","http://www.goemanwoodproductsinc.com" "Lavonna","Wolny","Linhares, Kenneth A Esq","5 Cabot Rd","Mc Lean","Fairfax","VA",22102,"703-483-1970","703-892-2914","lavonna.wolny@hotmail.com","http://www.linhareskennethaesq.com" "Lashaunda","Lizama","Earnhardt Printing","3387 Ryan Dr","Hanover","Anne Arundel","MD",21076,"410-678-2473","410-912-6032","llizama@cox.net","http://www.earnhardtprinting.com" "Mariann","Bilden","H P G Industrys Inc","3125 Packer Ave #9851","Austin","Travis","TX",78753,"512-223-4791","512-742-1149","mariann.bilden@aol.com","http://www.hpgindustrysinc.com" "Helene","Rodenberger","Bailey Transportation Prod Inc","347 Chestnut St","Peoria","Maricopa","AZ",85381,"623-461-8551","623-426-4907","helene@aol.com","http://www.baileytransportationprodinc.com" "Roselle","Estell","Mcglynn Bliss Pc","8116 Mount Vernon Ave","Bucyrus","Crawford","OH",44820,"419-571-5920","419-488-6648","roselle.estell@hotmail.com","http://www.mcglynnblisspc.com" "Samira","Heintzman","Mutual Fish Co","8772 Old County Rd #5410","Kent","King","WA",98032,"206-311-4137","206-923-6042","sheintzman@hotmail.com","http://www.mutualfishco.com" "Margart","Meisel","Yeates, Arthur L Aia","868 State St #38","Cincinnati","Hamilton","OH",45251,"513-617-2362","513-747-9603","margart_meisel@yahoo.com","http://www.yeatesarthurlaia.com" "Kristofer","Bennick","Logan, Ronald J Esq","772 W River Dr","Bloomington","Monroe","IN",47404,"812-368-1511","812-442-8544","kristofer.bennick@yahoo.com","http://www.loganronaldjesq.com" "Weldon","Acuff","Advantage Martgage Company","73 W Barstow Ave","Arlington Heights","Cook","IL",60004,"847-353-2156","847-613-5866","wacuff@gmail.com","http://www.advantagemartgagecompany.com" "Shalon","Shadrick","Germer And Gertz Llp","61047 Mayfield Ave","Brooklyn","Kings","NY",11223,"718-232-2337","718-394-4974","shalon@cox.net","http://www.germerandgertzllp.com" "Denise","Patak","Spence Law Offices","2139 Santa Rosa Ave","Orlando","Orange","FL",32801,"407-446-4358","407-808-3254","denise@patak.org","http://www.spencelawoffices.com" "Louvenia","Beech","John Ortiz Nts Therapy Center","598 43rd St","Beverly Hills","Los Angeles","CA",90210,"310-820-2117","310-652-2379","louvenia.beech@beech.com","http://www.johnortizntstherapycenter.com" "Audry","Yaw","Mike Uchrin Htg & Air Cond Inc","70295 Pioneer Ct","Brandon","Hillsborough","FL",33511,"813-797-4816","813-744-7100","audry.yaw@yaw.org","http://www.mikeuchrinhtgaircondinc.com" "Kristel","Ehmann","Mccoy, Joy Reynolds Esq","92899 Kalakaua Ave","El Paso","El Paso","TX",79925,"915-452-1290","915-300-6100","kristel.ehmann@aol.com","http://www.mccoyjoyreynoldsesq.com" "Vincenza","Zepp","Kbor 1600 Am","395 S 6th St #2","El Cajon","San Diego","CA",92020,"619-603-5125","619-935-6661","vzepp@gmail.com","http://www.kboram.com" "Elouise","Gwalthney","Quality Inn Northwest","9506 Edgemore Ave","Bladensburg","Prince Georges","MD",20710,"301-841-5012","301-591-3034","egwalthney@yahoo.com","http://www.qualityinnnorthwest.com" "Venita","Maillard","Wallace Church Assoc Inc","72119 S Walker Ave #63","Anaheim","Orange","CA",92801,"714-523-6653","714-663-9740","venita_maillard@gmail.com","http://www.wallacechurchassocinc.com" "Kasandra","Semidey","Can Tron","369 Latham St #500","Saint Louis","Saint Louis City","MO",63102,"314-732-9131","314-697-3652","kasandra_semidey@semidey.com","http://www.cantron.com" "Xochitl","Discipio","Ravaal Enterprises Inc","3158 Runamuck Pl","Round Rock","Williamson","TX",78664,"512-233-1831","512-942-3411","xdiscipio@gmail.com","http://www.ravaalenterprisesinc.com" "Maile","Linahan","Thompson Steel Company Inc","9 Plainsboro Rd #598","Greensboro","Guilford","NC",27409,"336-670-2640","336-364-6037","mlinahan@yahoo.com","http://www.thompsonsteelcompanyinc.com" "Krissy","Rauser","Anderson, Mark A Esq","8728 S Broad St","Coram","Suffolk","NY",11727,"631-443-4710","631-288-2866","krauser@cox.net","http://www.andersonmarkaesq.com" "Pete","Dubaldi","Womack & Galich","2215 Prosperity Dr","Lyndhurst","Bergen","NJ","07071","201-825-2514","201-749-8866","pdubaldi@hotmail.com","http://www.womackgalich.com" "Linn","Paa","Valerie & Company","1 S Pine St","Memphis","Shelby","TN",38112,"901-412-4381","901-573-9024","linn_paa@paa.com","http://www.valeriecompany.com" "Paris","Wide","Gehring Pumps Inc","187 Market St","Atlanta","Fulton","GA",30342,"404-505-4445","404-607-8435","paris@hotmail.com","http://www.gehringpumpsinc.com" "Wynell","Dorshorst","Haehnel, Craig W Esq","94290 S Buchanan St","Pacifica","San Mateo","CA",94044,"650-473-1262","650-749-9879","wynell_dorshorst@dorshorst.org","http://www.haehnelcraigwesq.com" "Quentin","Birkner","Spoor Behrins Campbell & Young","7061 N 2nd St","Burnsville","Dakota","MN",55337,"952-702-7993","952-314-5871","qbirkner@aol.com","http://www.spoorbehrinscampbellyoung.com" "Regenia","Kannady","Ken Jeter Store Equipment Inc","10759 Main St","Scottsdale","Maricopa","AZ",85260,"480-726-1280","480-205-5121","regenia.kannady@cox.net","http://www.kenjeterstoreequipmentinc.com" "Sheron","Louissant","Potter, Brenda J Cpa","97 E 3rd St #9","Long Island City","Queens","NY",11101,"718-976-8610","718-613-9994","sheron@aol.com","http://www.potterbrendajcpa.com" "Izetta","Funnell","Baird Kurtz & Dobson","82 Winsor St #54","Atlanta","Dekalb","GA",30340,"770-844-3447","770-584-4119","izetta.funnell@hotmail.com","http://www.bairdkurtzdobson.com" "Rodolfo","Butzen","Minor, Cynthia A Esq","41 Steel Ct","Northfield","Rice","MN",55057,"507-210-3510","507-590-5237","rodolfo@hotmail.com","http://www.minorcynthiaaesq.com" "Zona","Colla","Solove, Robert A Esq","49440 Dearborn St","Norwalk","Fairfield","CT","06854","203-461-1949","203-938-2557","zona@hotmail.com","http://www.soloverobertaesq.com" "Serina","Zagen","Mark Ii Imports Inc","7 S Beverly Dr","Fort Wayne","Allen","IN",46802,"260-273-3725","260-382-4869","szagen@aol.com","http://www.markiiimportsinc.com" "Paz","Sahagun","White Sign Div Ctrl Equip Co","919 Wall Blvd","Meridian","Lauderdale","MS",39307,"601-927-8287","601-249-4511","paz_sahagun@cox.net","http://www.whitesigndivctrlequipco.com" "Markus","Lukasik","M & M Store Fixtures Co Inc","89 20th St E #779","Sterling Heights","Macomb","MI",48310,"586-970-7380","586-247-1614","markus@yahoo.com","http://www.mmstorefixturescoinc.com" "Jaclyn","Bachman","Judah Caster & Wheel Co","721 Interstate 45 S","Colorado Springs","El Paso","CO",80919,"719-853-3600","719-223-2074","jaclyn@aol.com","http://www.judahcasterwheelco.com" "Cyril","Daufeldt","Galaxy International Inc","3 Lawton St","New York","New York","NY",10013,"212-745-8484","212-422-5427","cyril_daufeldt@daufeldt.com","http://www.galaxyinternationalinc.com" "Gayla","Schnitzler","Sigma Corp Of America","38 Pleasant Hill Rd","Hayward","Alameda","CA",94545,"510-686-3407","510-441-4055","gschnitzler@gmail.com","http://www.sigmacorpofamerica.com" "Erick","Nievas","Soward, Anne Esq","45 E Acacia Ct","Chicago","Cook","IL",60624,"773-704-9903","773-359-6109","erick_nievas@aol.com","http://www.sowardanneesq.com" "Jennie","Drymon","Osborne, Michelle M Esq","63728 Poway Rd #1","Scranton","Lackawanna","PA",18509,"570-218-4831","570-868-8688","jennie@cox.net","http://www.osbornemichellemesq.com" "Mitsue","Scipione","Students In Free Entrprs Natl","77 222 Dr","Oroville","Butte","CA",95965,"530-986-9272","530-399-3254","mscipione@scipione.com","http://www.studentsinfreeentrprsnatl.com" "Ciara","Ventura","Johnson, Robert M Esq","53 W Carey St","Port Jervis","Orange","NY",12771,"845-823-8877","845-694-7919","cventura@yahoo.com","http://www.johnsonrobertmesq.com" "Galen","Cantres","Del Charro Apartments","617 Nw 36th Ave","Brook Park","Cuyahoga","OH",44142,"216-600-6111","216-871-6876","galen@yahoo.com","http://www.delcharroapartments.com" "Truman","Feichtner","Legal Search Inc","539 Coldwater Canyon Ave","Bloomfield","Essex","NJ","07003","973-852-2736","973-473-5108","tfeichtner@yahoo.com","http://www.legalsearchinc.com" "Gail","Kitty","Service Supply Co Inc","735 Crawford Dr","Anchorage","Anchorage","AK",99501,"907-435-9166","907-770-3542","gail@kitty.com","http://www.servicesupplycoinc.com" "Dalene","Schoeneck","Sameshima, Douglas J Esq","910 Rahway Ave","Philadelphia","Philadelphia","PA",19102,"215-268-1275","215-380-8820","dalene@schoeneck.org","http://www.sameshimadouglasjesq.com" "Gertude","Witten","Thompson, John Randolph Jr","7 Tarrytown Rd","Cincinnati","Hamilton","OH",45217,"513-977-7043","513-863-9471","gertude.witten@gmail.com","http://www.thompsonjohnrandolphjr.com" "Lizbeth","Kohl","E T Balancing Co Inc","35433 Blake St #588","Gardena","Los Angeles","CA",90248,"310-699-1222","310-955-5788","lizbeth@yahoo.com","http://www.etbalancingcoinc.com" "Glenn","Berray","Griswold, John E Esq","29 Cherry St #7073","Des Moines","Polk","IA",50315,"515-370-7348","515-372-1738","gberray@gmail.com","http://www.griswoldjohneesq.com" "Lashandra","Klang","Acqua Group","810 N La Brea Ave","King of Prussia","Montgomery","PA",19406,"610-809-1818","610-378-7332","lashandra@yahoo.com","http://www.acquagroup.com" "Lenna","Newville","Brooks, Morris J Jr","987 Main St","Raleigh","Wake","NC",27601,"919-623-2524","919-254-5987","lnewville@newville.com","http://www.brooksmorrisjjr.com" "Laurel","Pagliuca","Printing Images Corp","36 Enterprise St Se","Richland","Benton","WA",99352,"509-695-5199","509-595-6485","laurel@yahoo.com","http://www.printingimagescorp.com" "Mireya","Frerking","Roberts Supply Co Inc","8429 Miller Rd","Pelham","Westchester","NY",10803,"914-868-5965","914-883-3061","mireya.frerking@hotmail.com","http://www.robertssupplycoinc.com" "Annelle","Tagala","Vico Products Mfg Co","5 W 7th St","Parkville","Baltimore","MD",21234,"410-757-1035","410-234-2267","annelle@yahoo.com","http://www.vicoproductsmfgco.com" "Dean","Ketelsen","J M Custom Design Millwork","2 Flynn Rd","Hicksville","Nassau","NY",11801,"516-847-4418","516-732-6649","dean_ketelsen@gmail.com","http://www.jmcustomdesignmillwork.com" "Levi","Munis","Farrell & Johnson Office Equip","2094 Ne 36th Ave","Worcester","Worcester","MA","01603","508-456-4907","508-658-7802","levi.munis@gmail.com","http://www.farrelljohnsonofficeequip.com" "Sylvie","Ryser","Millers Market & Deli","649 Tulane Ave","Tulsa","Tulsa","OK",74105,"918-644-9555","918-565-1706","sylvie@aol.com","http://www.millersmarketdeli.com" "Sharee","Maile","Holiday Inn Naperville","2094 Montour Blvd","Muskegon","Muskegon","MI",49442,"231-467-9978","231-265-6940","sharee_maile@aol.com","http://www.holidayinnnaperville.com" "Cordelia","Storment","Burrows, Jon H Esq","393 Hammond Dr","Lafayette","Lafayette","LA",70506,"337-566-6001","337-255-3427","cordelia_storment@aol.com","http://www.burrowsjonhesq.com" "Mollie","Mcdoniel","Dock Seal Specialty","8590 Lake Lizzie Dr","Bowling Green","Wood","OH",43402,"419-975-3182","419-417-4674","mollie_mcdoniel@yahoo.com","http://www.docksealspecialty.com" "Brett","Mccullan","Five Star Limousines Of Tx Inc","87895 Concord Rd","La Mesa","San Diego","CA",91942,"619-461-9984","619-727-3892","brett.mccullan@mccullan.com","http://www.fivestarlimousinesoftxinc.com" "Teddy","Pedrozo","Barkan, Neal J Esq","46314 Route 130","Bridgeport","Fairfield","CT","06610","203-892-3863","203-918-3939","teddy_pedrozo@aol.com","http://www.barkannealjesq.com" "Tasia","Andreason","Campbell, Robert A","4 Cowesett Ave","Kearny","Hudson","NJ","07032","201-920-9002","201-969-7063","tasia_andreason@yahoo.com","http://www.campbellroberta.com" "Hubert","Walthall","Dee, Deanna","95 Main Ave #2","Barberton","Summit","OH",44203,"330-903-1345","330-566-8898","hubert@walthall.org","http://www.deedeanna.com" "Arthur","Farrow","Young, Timothy L Esq","28 S 7th St #2824","Englewood","Bergen","NJ","07631","201-238-5688","201-772-4377","arthur.farrow@yahoo.com","http://www.youngtimothylesq.com" "Vilma","Berlanga","Wells, D Fred Esq","79 S Howell Ave","Grand Rapids","Kent","MI",49546,"616-737-3085","616-568-4113","vberlanga@berlanga.com","http://www.wellsdfredesq.com" "Billye","Miro","Gray, Francine H Esq","36 Lancaster Dr Se","Pearl","Rankin","MS",39208,"601-567-5386","601-637-5479","billye_miro@cox.net","http://www.grayfrancinehesq.com" "Glenna","Slayton","Toledo Iv Care","2759 Livingston Ave","Memphis","Shelby","TN",38118,"901-640-9178","901-869-4314","glenna_slayton@cox.net","http://www.toledoivcare.com" "Mitzie","Hudnall","Cangro Transmission Co","17 Jersey Ave","Englewood","Arapahoe","CO",80110,"303-402-1940","303-997-7760","mitzie_hudnall@yahoo.com","http://www.cangrotransmissionco.com" "Bernardine","Rodefer","Sat Poly Inc","2 W Grand Ave","Memphis","Shelby","TN",38112,"901-901-4726","901-739-5892","bernardine_rodefer@yahoo.com","http://www.satpolyinc.com" "Staci","Schmaltz","Midwest Contracting & Mfg Inc","18 Coronado Ave #563","Pasadena","Los Angeles","CA",91106,"626-866-2339","626-293-7678","staci_schmaltz@aol.com","http://www.midwestcontractingmfginc.com" "Nichelle","Meteer","Print Doctor","72 Beechwood Ter","Chicago","Cook","IL",60657,"773-225-9985","773-857-2231","nichelle_meteer@meteer.com","http://www.printdoctor.com" "Janine","Rhoden","Nordic Group Inc","92 Broadway","Astoria","Queens","NY",11103,"718-228-5894","718-728-5051","jrhoden@yahoo.com","http://www.nordicgroupinc.com" "Ettie","Hoopengardner","Jackson Millwork Co","39 Franklin Ave","Richland","Benton","WA",99352,"509-755-5393","509-847-3352","ettie.hoopengardner@hotmail.com","http://www.jacksonmillworkco.com" "Eden","Jayson","Harris Corporation","4 Iwaena St","Baltimore","Baltimore City","MD",21202,"410-890-7866","410-429-4888","eden_jayson@yahoo.com","http://www.harriscorporation.com" "Lynelle","Auber","United Cerebral Palsy Of Ne Pa","32820 Corkwood Rd","Newark","Essex","NJ","07104","973-860-8610","973-605-6492","lynelle_auber@gmail.com","http://www.unitedcerebralpalsyofnepa.com" "Merissa","Tomblin","One Day Surgery Center Inc","34 Raritan Center Pky","Bellflower","Los Angeles","CA",90706,"562-579-6900","562-719-7922","merissa.tomblin@gmail.com","http://www.onedaysurgerycenterinc.com" "Golda","Kaniecki","Calaveras Prospect","6201 S Nevada Ave","Toms River","Ocean","NJ","08755","732-628-9909","732-617-5310","golda_kaniecki@yahoo.com","http://www.calaverasprospect.com" "Catarina","Gleich","Terk, Robert E Esq","78 Maryland Dr #146","Denville","Morris","NJ","07834","973-210-3994","973-491-8723","catarina_gleich@hotmail.com","http://www.terkroberteesq.com" "Virgie","Kiel","Cullen, Terrence P Esq","76598 Rd I 95 #1","Denver","Denver","CO",80216,"303-776-7548","303-845-5408","vkiel@hotmail.com","http://www.cullenterrencepesq.com" "Jolene","Ostolaza","Central Die Casting Mfg Co Inc","1610 14th St Nw","Newport News","Newport News City","VA",23608,"757-682-7116","757-940-1741","jolene@yahoo.com","http://www.centraldiecastingmfgcoinc.com" "Keneth","Borgman","Centerline Engineering","86350 Roszel Rd","Phoenix","Maricopa","AZ",85012,"602-919-4211","602-442-3092","keneth@yahoo.com","http://www.centerlineengineering.com" "Rikki","Nayar","Targan & Kievit Pa","1644 Clove Rd","Miami","Miami-Dade","FL",33155,"305-968-9487","305-978-2069","rikki@nayar.com","http://www.targankievitpa.com" "Elke","Sengbusch","Riley Riper Hollin & Colagreco","9 W Central Ave","Phoenix","Maricopa","AZ",85013,"602-896-2993","602-575-3457","elke_sengbusch@yahoo.com","http://www.rileyriperhollincolagreco.com" "Hoa","Sarao","Kaplan, Joel S Esq","27846 Lafayette Ave","Oak Hill","Volusia","FL",32759,"386-526-7800","386-599-7296","hoa@sarao.org","http://www.kaplanjoelsesq.com" "Trinidad","Mcrae","Water Office","10276 Brooks St","San Francisco","San Francisco","CA",94105,"415-331-9634","415-419-1597","trinidad_mcrae@yahoo.com","http://www.wateroffice.com" "Mari","Lueckenbach","Westbrooks, Nelson E Jr","1 Century Park E","San Diego","San Diego","CA",92110,"858-793-9684","858-228-5683","mari_lueckenbach@yahoo.com","http://www.westbrooksnelsonejr.com" "Selma","Husser","Armon Communications","9 State Highway 57 #22","Jersey City","Hudson","NJ","07306","201-991-8369","201-772-7699","selma.husser@cox.net","http://www.armoncommunications.com" "Antione","Onofrio","Jacobs & Gerber Inc","4 S Washington Ave","San Bernardino","San Bernardino","CA",92410,"909-430-7765","909-665-3223","aonofrio@onofrio.com","http://www.jacobsgerberinc.com" "Luisa","Jurney","Forest Fire Laboratory","25 Se 176th Pl","Cambridge","Middlesex","MA","02138","617-365-2134","617-544-2541","ljurney@hotmail.com","http://www.forestfirelaboratory.com" "Clorinda","Heimann","Haughey, Charles Jr","105 Richmond Valley Rd","Escondido","San Diego","CA",92025,"760-291-5497","760-261-4786","clorinda.heimann@hotmail.com","http://www.haugheycharlesjr.com" "Dick","Wenzinger","Wheaton Plastic Products","22 Spruce St #595","Gardena","Los Angeles","CA",90248,"310-510-9713","310-936-2258","dick@yahoo.com","http://www.wheatonplasticproducts.com" "Ahmed","Angalich","Reese Plastics","2 W Beverly Blvd","Harrisburg","Dauphin","PA",17110,"717-528-8996","717-632-5831","ahmed.angalich@angalich.com","http://www.reeseplastics.com" "Iluminada","Ohms","Nazette Marner Good Wendt","72 Southern Blvd","Mesa","Maricopa","AZ",85204,"480-293-2882","480-866-6544","iluminada.ohms@yahoo.com","http://www.nazettemarnergoodwendt.com" "Joanna","Leinenbach","Levinson Axelrod Wheaton","1 Washington St","Lake Worth","Palm Beach","FL",33461,"561-470-4574","561-951-9734","joanna_leinenbach@hotmail.com","http://www.levinsonaxelrodwheaton.com" "Caprice","Suell","Egnor, W Dan Esq","90177 N 55th Ave","Nashville","Davidson","TN",37211,"615-246-1824","615-726-4537","caprice@aol.com","http://www.egnorwdanesq.com" "Stephane","Myricks","Portland Central Thriftlodge","9 Tower Ave","Burlington","Boone","KY",41005,"859-717-7638","859-308-4286","stephane_myricks@cox.net","http://www.portlandcentralthriftlodge.com" "Quentin","Swayze","Ulbrich Trucking","278 Bayview Ave","Milan","Monroe","MI",48160,"734-561-6170","734-851-8571","quentin_swayze@yahoo.com","http://www.ulbrichtrucking.com" "Annmarie","Castros","Tipiak Inc","80312 W 32nd St","Conroe","Montgomery","TX",77301,"936-751-7961","936-937-2334","annmarie_castros@gmail.com","http://www.tipiakinc.com" "Shonda","Greenbush","Saint George Well Drilling","82 Us Highway 46","Clifton","Passaic","NJ","07011","973-482-2430","973-644-2974","shonda_greenbush@cox.net","http://www.saintgeorgewelldrilling.com" "Cecil","Lapage","Hawkes, Douglas D","4 Stovall St #72","Union City","Hudson","NJ","07087","201-693-3967","201-856-2720","clapage@lapage.com","http://www.hawkesdouglasd.com" "Jeanice","Claucherty","Accurel Systems Intrntl Corp","19 Amboy Ave","Miami","Miami-Dade","FL",33142,"305-988-4162","305-306-7834","jeanice.claucherty@yahoo.com","http://www.accurelsystemsintrntlcorp.com" "Josphine","Villanueva","Santa Cruz Community Internet","63 Smith Ln #8343","Moss","Clay","TN",38575,"931-553-9774","931-486-6946","josphine_villanueva@villanueva.com","http://www.santacruzcommunityinternet.com" "Daniel","Perruzza","Gersh & Danielson","11360 S Halsted St","Santa Ana","Orange","CA",92705,"714-771-3880","714-531-1391","dperruzza@perruzza.com","http://www.gershdanielson.com" "Cassi","Wildfong","Cobb, James O Esq","26849 Jefferson Hwy","Rolling Meadows","Cook","IL",60008,"847-633-3216","847-755-9041","cassi.wildfong@aol.com","http://www.cobbjamesoesq.com" "Britt","Galam","Wheatley Trucking Company","2500 Pringle Rd Se #508","Hatfield","Montgomery","PA",19440,"215-888-3304","215-351-8523","britt@galam.org","http://www.wheatleytruckingcompany.com" "Adell","Lipkin","Systems Graph Inc Ab Dick Dlr","65 Mountain View Dr","Whippany","Morris","NJ","07981","973-654-1561","973-662-8988","adell.lipkin@lipkin.com","http://www.systemsgraphincabdickdlr.com" "Jacqueline","Rowling","John Hancock Mutl Life Ins Co","1 N San Saba","Erie","Erie","PA",16501,"814-865-8113","814-481-1700","jacqueline.rowling@yahoo.com","http://www.johnhancockmutllifeinsco.com" "Lonny","Weglarz","History Division Of State","51120 State Route 18","Salt Lake City","Salt Lake","UT",84115,"801-293-9853","801-892-8781","lonny_weglarz@gmail.com","http://www.historydivisionofstate.com" "Lonna","Diestel","Dimmock, Thomas J Esq","1482 College Ave","Fayetteville","Cumberland","NC",28301,"910-922-3672","910-200-7912","lonna_diestel@gmail.com","http://www.dimmockthomasjesq.com" "Cristal","Samara","Intermed Inc","4119 Metropolitan Dr","Los Angeles","Los Angeles","CA",90021,"213-975-8026","213-696-8004","cristal@cox.net","http://www.intermedinc.com" "Kenneth","Grenet","Bank Of New York","2167 Sierra Rd","East Lansing","Ingham","MI",48823,"517-499-2322","517-867-8077","kenneth.grenet@grenet.org","http://www.bankofnewyork.com" "Elli","Mclaird","Sportmaster Intrnatl","6 Sunrise Ave","Utica","Oneida","NY",13501,"315-818-2638","315-474-5570","emclaird@mclaird.com","http://www.sportmasterintrnatl.com" "Alline","Jeanty","W W John Holden Inc","55713 Lake City Hwy","South Bend","St Joseph","IN",46601,"574-656-2800","574-405-1983","ajeanty@gmail.com","http://www.wwjohnholdeninc.com" "Sharika","Eanes","Maccani & Delp","75698 N Fiesta Blvd","Orlando","Orange","FL",32806,"407-312-1691","407-472-1332","sharika.eanes@aol.com","http://www.maccanidelp.com" "Nu","Mcnease","Amazonia Film Project","88 Sw 28th Ter","Harrison","Hudson","NJ","07029","973-751-9003","973-903-4175","nu@gmail.com","http://www.amazoniafilmproject.com" "Daniela","Comnick","Water & Sewer Department","7 Flowers Rd #403","Trenton","Mercer","NJ","08611","609-200-8577","609-398-2805","dcomnick@cox.net","http://www.watersewerdepartment.com" "Cecilia","Colaizzo","Switchcraft Inc","4 Nw 12th St #3849","Madison","Dane","WI",53717,"608-382-4541","608-302-3387","cecilia_colaizzo@colaizzo.com","http://www.switchcraftinc.com" "Leslie","Threets","C W D C Metal Fabricators","2 A Kelley Dr","Katonah","Westchester","NY",10536,"914-861-9748","914-396-2615","leslie@cox.net","http://www.cwdcmetalfabricators.com" "Nan","Koppinger","Shimotani, Grace T","88827 Frankford Ave","Greensboro","Guilford","NC",27401,"336-370-5333","336-564-1492","nan@koppinger.com","http://www.shimotanigracet.com" "Izetta","Dewar","Lisatoni, Jean Esq","2 W Scyene Rd #3","Baltimore","Baltimore City","MD",21217,"410-473-1708","410-522-7621","idewar@dewar.com","http://www.lisatonijeanesq.com" "Tegan","Arceo","Ceramic Tile Sales Inc","62260 Park Stre","Monroe Township","Middlesex","NJ","08831","732-730-2692","732-705-6719","tegan.arceo@arceo.org","http://www.ceramictilesalesinc.com" "Ruthann","Keener","Maiden Craft Inc","3424 29th St Se","Kerrville","Kerr","TX",78028,"830-258-2769","830-919-5991","ruthann@hotmail.com","http://www.maidencraftinc.com" "Joni","Breland","Carriage House Cllsn Rpr Inc","35 E Main St #43","Elk Grove Village","Cook","IL",60007,"847-519-5906","847-740-5304","joni_breland@cox.net","http://www.carriagehousecllsnrprinc.com" "Vi","Rentfro","Video Workshop","7163 W Clark Rd","Freehold","Monmouth","NJ","07728","732-605-4781","732-724-7251","vrentfro@cox.net","http://www.videoworkshop.com" "Colette","Kardas","Fresno Tile Center Inc","21575 S Apple Creek Rd","Omaha","Douglas","NE",68124,"402-896-5943","402-707-1602","colette.kardas@yahoo.com","http://www.fresnotilecenterinc.com" "Malcolm","Tromblay","Versatile Sash & Woodwork","747 Leonis Blvd","Annandale","Fairfax","VA",22003,"703-221-5602","703-874-4248","malcolm_tromblay@cox.net","http://www.versatilesashwoodwork.com" "Ryan","Harnos","Warner Electric Brk & Cltch Co","13 Gunnison St","Plano","Collin","TX",75075,"972-558-1665","972-961-4968","ryan@cox.net","http://www.warnerelectricbrkcltchco.com" "Jess","Chaffins","New York Public Library","18 3rd Ave","New York","New York","NY",10016,"212-510-4633","212-428-9538","jess.chaffins@chaffins.org","http://www.newyorkpubliclibrary.com" "Sharen","Bourbon","Mccaleb, John A Esq","62 W Austin St","Syosset","Nassau","NY",11791,"516-816-1541","516-749-3188","sbourbon@yahoo.com","http://www.mccalebjohnaesq.com" "Nickolas","Juvera","United Oil Co Inc","177 S Rider Trl #52","Crystal River","Citrus","FL",34429,"352-598-8301","352-947-6152","nickolas_juvera@cox.net","http://www.unitedoilcoinc.com" "Gary","Nunlee","Irving Foot Center","2 W Mount Royal Ave","Fortville","Hancock","IN",46040,"317-542-6023","317-887-8486","gary_nunlee@nunlee.org","http://www.irvingfootcenter.com" "Diane","Devreese","Acme Supply Co","1953 Telegraph Rd","Saint Joseph","Buchanan","MO",64504,"816-557-9673","816-329-5565","diane@cox.net","http://www.acmesupplyco.com" "Roslyn","Chavous","Mcrae, James L","63517 Dupont St","Jackson","Hinds","MS",39211,"601-234-9632","601-973-5754","roslyn.chavous@chavous.org","http://www.mcraejamesl.com" "Glory","Schieler","Mcgraths Seafood","5 E Truman Rd","Abilene","Taylor","TX",79602,"325-869-2649","325-740-3778","glory@yahoo.com","http://www.mcgrathsseafood.com" "Rasheeda","Sayaphon","Kummerer, J Michael Esq","251 Park Ave #979","Saratoga","Santa Clara","CA",95070,"408-805-4309","408-997-7490","rasheeda@aol.com","http://www.kummererjmichaelesq.com" "Alpha","Palaia","Stoffer, James M Jr","43496 Commercial Dr #29","Cherry Hill","Camden","NJ","08003","856-312-2629","856-513-7024","alpha@yahoo.com","http://www.stofferjamesmjr.com" "Refugia","Jacobos","North Central Fl Sfty Cncl","2184 Worth St","Hayward","Alameda","CA",94545,"510-974-8671","510-509-3496","refugia.jacobos@jacobos.com","http://www.northcentralflsftycncl.com" "Shawnda","Yori","Fiorucci Foods Usa Inc","50126 N Plankinton Ave","Longwood","Seminole","FL",32750,"407-538-5106","407-564-8113","shawnda.yori@yahoo.com","http://www.fioruccifoodsusainc.com" "Mona","Delasancha","Sign All","38773 Gravois Ave","Cheyenne","Laramie","WY",82001,"307-403-1488","307-816-7115","mdelasancha@hotmail.com","http://www.signall.com" "Gilma","Liukko","Sammys Steak Den","16452 Greenwich St","Garden City","Nassau","NY",11530,"516-393-9967","516-407-9573","gilma_liukko@gmail.com","http://www.sammyssteakden.com" "Janey","Gabisi","Dobscha, Stephen F Esq","40 Cambridge Ave","Madison","Dane","WI",53715,"608-967-7194","608-586-6912","jgabisi@hotmail.com","http://www.dobschastephenfesq.com" "Lili","Paskin","Morgan Custom Homes","20113 4th Ave E","Kearny","Hudson","NJ","07032","201-431-2989","201-478-8540","lili.paskin@cox.net","http://www.morgancustomhomes.com" "Loren","Asar","Olsen Payne & Company","6 Ridgewood Center Dr","Old Forge","Lackawanna","PA",18518,"570-648-3035","570-569-2356","loren.asar@aol.com","http://www.olsenpaynecompany.com" "Dorothy","Chesterfield","Cowan & Kelly","469 Outwater Ln","San Diego","San Diego","CA",92126,"858-617-7834","858-732-1884","dorothy@cox.net","http://www.cowankelly.com" "Gail","Similton","Johnson, Wes Esq","62 Monroe St","Thousand Palms","Riverside","CA",92276,"760-616-5388","760-493-9208","gail_similton@similton.com","http://www.johnsonwesesq.com" "Catalina","Tillotson","Icn Pharmaceuticals Inc","3338 A Lockport Pl #6","Margate City","Atlantic","NJ","08402","609-373-3332","609-826-4990","catalina@hotmail.com","http://www.icnpharmaceuticalsinc.com" "Lawrence","Lorens","New England Sec Equip Co Inc","9 Hwy","Providence","Providence","RI","02906","401-465-6432","401-893-1820","lawrence.lorens@hotmail.com","http://www.newenglandsecequipcoinc.com" "Carlee","Boulter","Tippett, Troy M Ii","8284 Hart St","Abilene","Dickinson","KS",67410,"785-347-1805","785-253-7049","carlee.boulter@hotmail.com","http://www.tippetttroymii.com" "Thaddeus","Ankeny","Atc Contracting","5 Washington St #1","Roseville","Placer","CA",95678,"916-920-3571","916-459-2433","tankeny@ankeny.org","http://www.atccontracting.com" "Jovita","Oles","Pagano, Philip G Esq","8 S Haven St","Daytona Beach","Volusia","FL",32114,"386-248-4118","386-208-6976","joles@gmail.com","http://www.paganophilipgesq.com" "Alesia","Hixenbaugh","Kwikprint","9 Front St","Washington","District of Columbia","DC",20001,"202-646-7516","202-276-6826","alesia_hixenbaugh@hixenbaugh.org","http://www.kwikprint.com" "Lai","Harabedian","Buergi & Madden Scale","1933 Packer Ave #2","Novato","Marin","CA",94945,"415-423-3294","415-926-6089","lai@gmail.com","http://www.buergimaddenscale.com" "Brittni","Gillaspie","Inner Label","67 Rv Cent","Boise","Ada","ID",83709,"208-709-1235","208-206-9848","bgillaspie@gillaspie.com","http://www.innerlabel.com" "Raylene","Kampa","Hermar Inc","2 Sw Nyberg Rd","Elkhart","Elkhart","IN",46514,"574-499-1454","574-330-1884","rkampa@kampa.org","http://www.hermarinc.com" "Flo","Bookamer","Simonton Howe & Schneider Pc","89992 E 15th St","Alliance","Box Butte","NE",69301,"308-726-2182","308-250-6987","flo.bookamer@cox.net","http://www.simontonhoweschneiderpc.com" "Jani","Biddy","Warehouse Office & Paper Prod","61556 W 20th Ave","Seattle","King","WA",98104,"206-711-6498","206-395-6284","jbiddy@yahoo.com","http://www.warehouseofficepaperprod.com" "Chauncey","Motley","Affiliated With Travelodge","63 E Aurora Dr","Orlando","Orange","FL",32804,"407-413-4842","407-557-8857","chauncey_motley@aol.com","http://www.affiliatedwithtravelodge.com" \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/pom.xml b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/pom.xml new file mode 100644 index 000000000..d7d560582 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + org.apache.nifi + nifi-standard-services + 1.1.0-SNAPSHOT + + + nifi-record-serialization-service-api + jar + + + org.apache.nifi + nifi-api + + + diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/DataTypeValidator.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/DataTypeValidator.java new file mode 100644 index 000000000..5a7108682 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/DataTypeValidator.java @@ -0,0 +1,82 @@ +/* + * 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.serialization; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.apache.nifi.components.ValidationContext; +import org.apache.nifi.components.ValidationResult; +import org.apache.nifi.components.Validator; + +public class DataTypeValidator implements Validator { + private static final Set validValues; + private static final Set allowsFormatting; + + static { + final Set values = new HashSet<>(); + values.add("string"); + values.add("boolean"); + values.add("byte"); + values.add("char"); + values.add("int"); + values.add("long"); + values.add("float"); + values.add("double"); + values.add("time"); + values.add("date"); + values.add("timestamp"); + validValues = Collections.unmodifiableSet(values); + + final Set formattable = new HashSet<>(); + formattable.add("date"); + formattable.add("time"); + formattable.add("timestmap"); + allowsFormatting = Collections.unmodifiableSet(formattable); + } + + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + final String[] splits = input.split("\\:"); + + final boolean valid; + if (splits.length == 2) { + final String type = splits[0].trim(); + if (validValues.contains(type)) { + if (allowsFormatting.contains(splits[0].trim())) { + valid = true; + } else { + valid = false; + } + } else { + valid = false; + } + } else { + valid = validValues.contains(input.trim()); + } + + return new ValidationResult.Builder() + .subject(subject) + .input(input) + .valid(valid) + .explanation("Valid values for this property are: " + validValues + + ", where date, time, and timestamp may optionally contain a format (e.g., date:MM-dd-yyyy)") + .build(); + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/MalformedRecordException.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/MalformedRecordException.java new file mode 100644 index 000000000..d45a8501d --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/MalformedRecordException.java @@ -0,0 +1,31 @@ +/* + * 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.serialization; + +/** + * An Exception that can be thrown to indicate that data was read but could not properly be parsed + */ +public class MalformedRecordException extends Exception { + public MalformedRecordException(final String message) { + super(message); + } + + public MalformedRecordException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/RecordReader.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/RecordReader.java new file mode 100644 index 000000000..a0cfc791d --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/RecordReader.java @@ -0,0 +1,55 @@ +/* + * 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.serialization; + +import java.io.Closeable; +import java.io.IOException; + +import org.apache.nifi.serialization.record.Record; +import org.apache.nifi.serialization.record.RecordSchema; + +/** + *

+ * A RowRecordReader is responsible for parsing data and returning a record at a time + * in order to allow the caller to iterate over the records individually. + *

+ * + *

+ * PLEASE NOTE: This interface is still considered 'unstable' and may change in a non-backward-compatible + * manner between minor or incremental releases of NiFi. + *

+ */ +public interface RecordReader extends Closeable { + + /** + * Returns the next record in the stream or null if no more records are available. + * + * @param schema the schema to use in order to determine how to interprets the fields in a record + * @return the next record in the stream or null if no more records are available. + * + * @throws IOException if unable to read from the underlying data + * @throws MalformedRecordException if an unrecoverable failure occurs when trying to parse a record + */ + Record nextRecord() throws IOException, MalformedRecordException; + + /** + * @return a RecordSchema that is appropriate for the records in the stream + * @throws MalformedRecordException if an unrecoverable failure occurs when trying to parse the underlying data + */ + RecordSchema getSchema() throws MalformedRecordException; +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/RecordSetWriter.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/RecordSetWriter.java new file mode 100644 index 000000000..7d6fa1c0f --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/RecordSetWriter.java @@ -0,0 +1,45 @@ +/* + * 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.serialization; + +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.nifi.serialization.record.RecordSet; + +/** + *

+ * A ResultSetWriter is responsible for writing a ResultSet to a given {@link OutputStream}. + *

+ * + *

+ * PLEASE NOTE: This interface is still considered 'unstable' and may change in a non-backward-compatible + * manner between minor or incremental releases of NiFi. + *

+ */ +public interface RecordSetWriter extends RecordWriter { + /** + * Writes the given result set to the given output stream + * + * @param recordSet the record set to serialize + * @param out the OutputStream to write to + * @return the results of writing the data + * @throws IOException if unable to write to the given OutputStream + */ + WriteResult write(RecordSet recordSet, OutputStream out) throws IOException; +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/RecordSetWriterFactory.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/RecordSetWriterFactory.java new file mode 100644 index 000000000..2286f3f9f --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/RecordSetWriterFactory.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.serialization; + +import org.apache.nifi.controller.ControllerService; +import org.apache.nifi.logging.ComponentLog; + +/** + *

+ * A Controller Service that is responsible for creating a {@link RecordSetWriter}. + *

+ */ +public interface RecordSetWriterFactory extends ControllerService { + RecordSetWriter createWriter(ComponentLog logger); +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/RecordWriter.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/RecordWriter.java new file mode 100644 index 000000000..eef8d823f --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/RecordWriter.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.serialization; + +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.nifi.serialization.record.Record; + +public interface RecordWriter { + /** + * Writes the given result set to the given output stream + * + * @param recordSet the record set to serialize + * @param out the OutputStream to write to + * @return the results of writing the data + * @throws IOException if unable to write to the given OutputStream + */ + WriteResult write(Record record, OutputStream out) throws IOException; + + /** + * @return the MIME Type that the Result Set Writer produces. This will be added to FlowFiles using + * the mime.type attribute. + */ + String getMimeType(); +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/RowRecordReaderFactory.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/RowRecordReaderFactory.java new file mode 100644 index 000000000..5ef4c7c7a --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/RowRecordReaderFactory.java @@ -0,0 +1,33 @@ +/* + * 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.serialization; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.nifi.controller.ControllerService; +import org.apache.nifi.logging.ComponentLog; + +/** + *

+ * A Controller Service that is responsible for creating a {@link RecordReader}. + *

+ */ +public interface RowRecordReaderFactory extends ControllerService { + RecordReader createRecordReader(InputStream in, ComponentLog logger) throws MalformedRecordException, IOException; +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/SimpleRecordSchema.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/SimpleRecordSchema.java new file mode 100644 index 000000000..246e0af95 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/SimpleRecordSchema.java @@ -0,0 +1,126 @@ +/* + * 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.serialization; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.stream.Collectors; + +import org.apache.nifi.serialization.record.DataType; +import org.apache.nifi.serialization.record.RecordField; +import org.apache.nifi.serialization.record.RecordSchema; + +public class SimpleRecordSchema implements RecordSchema { + private final List fields; + private final Map fieldIndices; + + public SimpleRecordSchema(final List fields) { + this.fields = Collections.unmodifiableList(new ArrayList<>(fields)); + this.fieldIndices = new HashMap<>(fields.size()); + + int index = 0; + for (final RecordField field : fields) { + fieldIndices.put(field.getFieldName(), index++); + } + } + + @Override + public List getFields() { + return fields; + } + + @Override + public int getFieldCount() { + return fields.size(); + } + + @Override + public RecordField getField(final int index) { + return fields.get(index); + } + + @Override + public List getDataTypes() { + return getFields().stream().map(recordField -> recordField.getDataType()) + .collect(Collectors.toList()); + } + + @Override + public List getFieldNames() { + return getFields().stream().map(recordField -> recordField.getFieldName()) + .collect(Collectors.toList()); + } + + @Override + public Optional getDataType(final String fieldName) { + final OptionalInt idx = getFieldIndex(fieldName); + return idx.isPresent() ? Optional.of(fields.get(idx.getAsInt()).getDataType()) : Optional.empty(); + } + + private OptionalInt getFieldIndex(final String fieldName) { + final Integer index = fieldIndices.get(fieldName); + return index == null ? OptionalInt.empty() : OptionalInt.of(index); + } + + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (!(obj instanceof RecordSchema)) { + return false; + } + + final RecordSchema other = (RecordSchema) obj; + return fields.equals(other.getFields()); + } + + @Override + public int hashCode() { + return 143 + 3 * fields.hashCode(); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("["); + + for (int i = 0; i < fields.size(); i++) { + final RecordField field = fields.get(i); + + sb.append("\""); + sb.append(field.getFieldName()); + sb.append("\" : \""); + sb.append(field.getDataType()); + sb.append("\""); + + if (i < fields.size() - 1) { + sb.append(", "); + } + } + sb.append("]"); + return sb.toString(); + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/WriteResult.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/WriteResult.java new file mode 100644 index 000000000..3fb2741a2 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/WriteResult.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.serialization; + +import java.util.Collections; +import java.util.Map; + +/** + *

+ * Provides information about what was written to an OutputStream by a {@link RecordSetWriter}. + * Instances of WriteResult are typically instantiated by calling the static method {@link WriteResult#of(int, Map)} + * or using {@link WriteResult#EMPTY}. + *

+ * + *

+ * PLEASE NOTE: This interface is still considered 'unstable' and may change in a non-backward-compatible + * manner between minor or incremental releases of NiFi. + *

+ */ +public interface WriteResult { + + /** + * @return the number of records written + */ + int getRecordCount(); + + /** + * @return values that should be added to the FlowFile as attributes + */ + Map getAttributes(); + + /** + * Creates a WriteResult with the given record count and attributes + * + * @param recordCount the number of records written + * @param attributes the attributes to add to the FlowFile + * @return A {@link WriteResult} representing the given parameters + */ + public static WriteResult of(final int recordCount, final Map attributes) { + return new WriteResult() { + @Override + public int getRecordCount() { + return recordCount; + } + + @Override + public Map getAttributes() { + return attributes; + } + }; + } + + public static final WriteResult EMPTY = of(0, Collections.emptyMap()); +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/DataType.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/DataType.java new file mode 100644 index 000000000..0c187f17b --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/DataType.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.serialization.record; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +public class DataType { + private final RecordFieldType fieldType; + private final String format; + + private final RecordSchema childSchema; + private final List childTypes; + + DataType(final RecordFieldType fieldType, final String format) { + this(fieldType, format, (RecordSchema) null); + } + + DataType(final RecordFieldType fieldType, final String format, final RecordSchema childSchema) { + this.fieldType = fieldType; + this.format = format; + this.childSchema = childSchema; + this.childTypes = Collections.emptyList(); + } + + DataType(final RecordFieldType fieldType, final String format, final List childTypes) { + this.fieldType = fieldType; + this.format = format; + this.childSchema = null; + this.childTypes = Collections.unmodifiableList(childTypes); + } + + + public String getFormat() { + return format; + } + + public RecordFieldType getFieldType() { + return fieldType; + } + + public Optional getChildRecordSchema() { + return Optional.ofNullable(childSchema); + } + + public List getPossibleTypes() { + return childTypes; + } + + @Override + public int hashCode() { + return 31 + 41 * fieldType.hashCode() + 41 * (format == null ? 0 : format.hashCode()); + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof DataType)) { + return false; + } + + final DataType other = (DataType) obj; + return fieldType.equals(other.fieldType) && ((format == null && other.format == null) || (format != null && format.equals(other.format))); + } + + @Override + public String toString() { + if (format == null) { + return fieldType.toString(); + } else { + return fieldType.toString() + ":" + format; + } + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/ListRecordSet.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/ListRecordSet.java new file mode 100644 index 000000000..3968f5022 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/ListRecordSet.java @@ -0,0 +1,44 @@ +/* + * 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.serialization.record; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class ListRecordSet implements RecordSet { + private final Iterator recordItr; + private final RecordSchema schema; + + public ListRecordSet(final RecordSchema schema, final List records) { + this.schema = schema; + + final List copy = new ArrayList<>(records); + recordItr = copy.iterator(); + } + + @Override + public RecordSchema getSchema() { + return schema; + } + + @Override + public Record next() { + return recordItr.hasNext() ? recordItr.next() : null; + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/MapRecord.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/MapRecord.java new file mode 100644 index 000000000..f3f90241a --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/MapRecord.java @@ -0,0 +1,322 @@ +/* + * 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.serialization.record; + +import java.sql.Time; +import java.sql.Timestamp; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +public class MapRecord implements Record { + private final RecordSchema schema; + private final Map values; + + public MapRecord(final RecordSchema schema, final Map values) { + this.schema = Objects.requireNonNull(schema); + this.values = Objects.requireNonNull(values); + } + + @Override + public RecordSchema getSchema() { + return schema; + } + + @Override + public Object[] getValues() { + final Object[] values = new Object[schema.getFieldCount()]; + int i = 0; + for (final String fieldName : schema.getFieldNames()) { + values[i++] = getValue(fieldName); + } + return values; + } + + @Override + public Object getValue(final String fieldName) { + return values.get(fieldName); + } + + @Override + public String getAsString(final String fieldName) { + final Optional dataTypeOption = schema.getDataType(fieldName); + if (!dataTypeOption.isPresent()) { + return null; + } + + return convertToString(getValue(fieldName), dataTypeOption.get().getFormat()); + } + + @Override + public String getAsString(final String fieldName, final String format) { + return convertToString(getValue(fieldName), format); + } + + private String getFormat(final String optionalFormat, final RecordFieldType fieldType) { + return (optionalFormat == null) ? fieldType.getDefaultFormat() : optionalFormat; + } + + private String convertToString(final Object value, final String format) { + if (value == null) { + return null; + } + + if (value instanceof java.sql.Date) { + java.sql.Date date = (java.sql.Date) value; + final long time = date.getTime(); + return new SimpleDateFormat(getFormat(format, RecordFieldType.DATE)).format(new java.util.Date(time)); + } + if (value instanceof java.util.Date) { + return new SimpleDateFormat(getFormat(format, RecordFieldType.DATE)).format((java.util.Date) value); + } + if (value instanceof Timestamp) { + java.sql.Timestamp date = (java.sql.Timestamp) value; + final long time = date.getTime(); + return new SimpleDateFormat(getFormat(format, RecordFieldType.TIMESTAMP)).format(new java.util.Date(time)); + } + if (value instanceof Time) { + java.sql.Time date = (java.sql.Time) value; + final long time = date.getTime(); + return new SimpleDateFormat(getFormat(format, RecordFieldType.TIME)).format(new java.util.Date(time)); + } + + return value.toString(); + } + + @Override + public Long getAsLong(final String fieldName) { + return convertToLong(getValue(fieldName), fieldName); + } + + private Long convertToLong(final Object value, final Object fieldDesc) { + if (value == null) { + return null; + } + + if (value instanceof Number) { + return ((Number) value).longValue(); + } + if (value instanceof String) { + return Long.parseLong((String) value); + } + if (value instanceof Date) { + return ((Date) value).getTime(); + } + + throw new TypeMismatchException("Cannot convert value of type " + value.getClass() + " to Long for field " + fieldDesc); + } + + @Override + public Integer getAsInt(final String fieldName) { + return convertToInt(getValue(fieldName), fieldName); + } + + private Integer convertToInt(final Object value, final Object fieldDesc) { + if (value == null) { + return null; + } + + if (value instanceof Number) { + return ((Number) value).intValue(); + } + if (value instanceof String) { + return Integer.parseInt((String) value); + } + + throw new TypeMismatchException("Cannot convert value of type " + value.getClass() + " to Integer for field " + fieldDesc); + } + + + @Override + public Double getAsDouble(final String fieldName) { + return convertToDouble(getValue(fieldName), fieldName); + } + + private Double convertToDouble(final Object value, final Object fieldDesc) { + if (value == null) { + return null; + } + + if (value instanceof Number) { + return ((Number) value).doubleValue(); + } + if (value instanceof String) { + return Double.parseDouble((String) value); + } + + throw new TypeMismatchException("Cannot convert value of type " + value.getClass() + " to Double for field " + fieldDesc); + } + + @Override + public Float getAsFloat(final String fieldName) { + return convertToFloat(getValue(fieldName), fieldName); + } + + private Float convertToFloat(final Object value, final Object fieldDesc) { + if (value == null) { + return null; + } + + if (value instanceof Number) { + return ((Number) value).floatValue(); + } + if (value instanceof String) { + return Float.parseFloat((String) value); + } + + throw new TypeMismatchException("Cannot convert value of type " + value.getClass() + " to Float for field " + fieldDesc); + } + + @Override + public Record getAsRecord(String fieldName) { + return convertToRecord(getValue(fieldName), fieldName); + } + + private Record convertToRecord(final Object value, final Object fieldDesc) { + if (value == null) { + return null; + } + + if (value instanceof Record) { + return (Record) value; + } + + throw new TypeMismatchException("Cannot convert value of type " + value.getClass() + " to Record for field " + fieldDesc); + } + + + @Override + public Boolean getAsBoolean(final String fieldName) { + return convertToBoolean(getValue(fieldName), fieldName); + } + + private Boolean convertToBoolean(final Object value, final Object fieldDesc) { + if (value == null) { + return null; + } + + if (value instanceof Boolean) { + return (Boolean) value; + } + if (value instanceof String) { + final String string = (String) value; + if (string.equalsIgnoreCase("true") || string.equalsIgnoreCase("t")) { + return Boolean.TRUE; + } + + if (string.equalsIgnoreCase("false") || string.equals("f")) { + return Boolean.FALSE; + } + + throw new TypeMismatchException("Cannot convert String value to Boolean for field " + fieldDesc + " because it is not a valid boolean value"); + } + + throw new TypeMismatchException("Cannot convert value of type " + value.getClass() + " to Boolean for field " + fieldDesc); + } + + @Override + public Date getAsDate(final String fieldName) { + final Optional dataTypeOption = schema.getDataType(fieldName); + if (!dataTypeOption.isPresent()) { + return null; + } + + return convertToDate(getValue(fieldName), fieldName, dataTypeOption.get().getFormat()); + } + + @Override + public Date getAsDate(final String fieldName, final String format) { + return convertToDate(getValue(fieldName), fieldName, format); + } + + private Date convertToDate(final Object value, final Object fieldDesc, final String format) { + if (value == null) { + return null; + } + + if (value instanceof Date) { + return (Date) value; + } + if (value instanceof Number) { + final Long time = ((Number) value).longValue(); + return new Date(time); + } + if (value instanceof java.sql.Date) { + return new Date(((java.sql.Date) value).getTime()); + } + if (value instanceof String) { + try { + return new SimpleDateFormat(getFormat(format, RecordFieldType.DATE)).parse((String) value); + } catch (final ParseException e) { + throw new TypeMismatchException("Cannot convert String value to date for field " + fieldDesc + " because it is not in the correct format of: " + format, e); + } + } + + throw new TypeMismatchException("Cannot convert value of type " + value.getClass() + " to Boolean for field " + fieldDesc); + } + + @Override + public Object[] getAsArray(final String fieldName) { + return convertToArray(getValue(fieldName)); + } + + private Object[] convertToArray(final Object value) { + if (value == null) { + return null; + } + + if (value instanceof Object[]) { + return (Object[]) value; + } + + if (value instanceof List) { + return ((List) value).toArray(); + } + + return new Object[] {value}; + } + + @Override + public int hashCode() { + return 31 + 41 * values.hashCode() + 7 * schema.hashCode(); + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof MapRecord)) { + return false; + } + final MapRecord other = (MapRecord) obj; + return schema.equals(other.schema) && values.equals(other.values); + } + + @Override + public String toString() { + return "MapRecord[values=" + values + "]"; + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/Record.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/Record.java new file mode 100644 index 000000000..ca8574174 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/Record.java @@ -0,0 +1,62 @@ +/* + * 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.serialization.record; + +import java.util.Date; + +public interface Record { + + RecordSchema getSchema(); + + /** + *

+ * Returns a view of the the values of the fields in this Record. + *

+ * + * NOTE: The array that is returned may be an underlying array that is backing + * the contents of the Record. As such, modifying the array in any way may result in + * modifying the record. + * + * @return a view of the values of the fields in this Record + */ + Object[] getValues(); + + Object getValue(String fieldName); + + String getAsString(String fieldName); + + String getAsString(String fieldName, String format); + + Long getAsLong(String fieldName); + + Integer getAsInt(String fieldName); + + Double getAsDouble(String fieldName); + + Float getAsFloat(String fieldName); + + Record getAsRecord(String fieldName); + + Boolean getAsBoolean(String fieldName); + + Date getAsDate(String fieldName); + + Date getAsDate(String fieldName, String format); + + Object[] getAsArray(String fieldName); +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/RecordField.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/RecordField.java new file mode 100644 index 000000000..135ae66f0 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/RecordField.java @@ -0,0 +1,64 @@ +/* + * 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.serialization.record; + +public class RecordField { + private final String fieldName; + private final DataType dataType; + + public RecordField(final String fieldName, final DataType dataType) { + this.fieldName = fieldName; + this.dataType = dataType; + } + + public String getFieldName() { + return fieldName; + } + + public DataType getDataType() { + return dataType; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((dataType == null) ? 0 : dataType.hashCode()); + result = prime * result + ((fieldName == null) ? 0 : fieldName.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + + if (getClass() != obj.getClass()) { + return false; + } + + RecordField other = (RecordField) obj; + return dataType.equals(other.getDataType()) && fieldName.equals(other.getFieldName()); + } + + +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/RecordFieldType.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/RecordFieldType.java new file mode 100644 index 000000000..8ad212ba8 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/RecordFieldType.java @@ -0,0 +1,114 @@ +/* + * 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.serialization.record; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public enum RecordFieldType { + STRING("string"), + BOOLEAN("boolean"), + BYTE("byte"), + CHAR("char"), + SHORT("short"), + INT("int"), + BIGINT("bigint"), + LONG("long"), + FLOAT("float"), + DOUBLE("double"), + DATE("date", "yyyy-MM-dd"), + TIME("time", "HH:mm:ss"), + TIMESTAMP("timestamp", "yyyy-MM-dd HH:mm:ss"), + RECORD("record"), + CHOICE("choice"), + ARRAY("array"); + + + private static final Map SIMPLE_NAME_MAP = new HashMap(); + + static { + for (RecordFieldType value : values()) { + SIMPLE_NAME_MAP.put(value.simpleName, value); + } + } + + private final String simpleName; + private final String defaultFormat; + private final DataType defaultDataType; + + private RecordFieldType(final String simpleName) { + this(simpleName, null); + } + + private RecordFieldType(final String simpleName, final String defaultFormat) { + this.simpleName = simpleName; + this.defaultFormat = defaultFormat; + this.defaultDataType = new DataType(this, defaultFormat); + } + + public String getDefaultFormat() { + return defaultFormat; + } + + /** + * @return the DataType with the default format + */ + public DataType getDataType() { + return defaultDataType; + } + + public DataType getDataType(final String format) { + return new DataType(this, format); + } + + /** + * Returns a Data Type that represents a "RECORD" type with the given schema. + * + * @param childSchema the Schema for the Record + * @return a DataType that represents a Record with the given schema, or null if this RecordFieldType + * is not the RECORD type. + */ + public DataType getDataType(final RecordSchema childSchema) { + if (this != RECORD) { + return null; + } + + return new DataType(this, getDefaultFormat(), childSchema); + } + + /** + * Returns a Data Type that represents a "CHOICE" of multiple possible types. This method is + * only applicable for a RecordFieldType of {@link #CHOICE}. + * + * @param possibleChildTypes the possible types that are allowable + * @return a DataType that represents a "CHOICE" of multiple possible types, or null if this RecordFieldType + * is not the CHOICE type. + */ + public DataType getDataType(final List possibleChildTypes) { + if (this != CHOICE) { + return null; + } + + return new DataType(this, getDefaultFormat(), possibleChildTypes); + } + + public static RecordFieldType of(final String typeString) { + return SIMPLE_NAME_MAP.get(typeString); + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/RecordSchema.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/RecordSchema.java new file mode 100644 index 000000000..115fb5165 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/RecordSchema.java @@ -0,0 +1,58 @@ +/* + * 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.serialization.record; + +import java.util.List; +import java.util.Optional; + +public interface RecordSchema { + /** + * @return the list of fields that are present in the schema + */ + List getFields(); + + /** + * @return the number of fields in the schema + */ + int getFieldCount(); + + /** + * @param index the 0-based index of which field to return + * @return the index'th field + * + * @throws IndexOutOfBoundsException if the index is < 0 or >= the number of fields (determined by {@link #getFieldCount()}). + */ + RecordField getField(int index); + + /** + * @return the data types of the fields + */ + List getDataTypes(); + + /** + * @return the names of the fields + */ + List getFieldNames(); + + /** + * @param fieldName the name of the field whose type is desired + * @return the RecordFieldType associated with the field that has the given name, or + * null if the schema does not contain a field with the given name + */ + Optional getDataType(String fieldName); +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/RecordSet.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/RecordSet.java new file mode 100644 index 000000000..25bbcdcb6 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/RecordSet.java @@ -0,0 +1,53 @@ +/* + * 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.serialization.record; + +import java.io.IOException; + +public interface RecordSet { + + /** + * @return the {@link RecordSchema} that applies to the records in this RecordSet + */ + RecordSchema getSchema() throws IOException; + + /** + * @return the next {@link Record} in the set or null if there are no more records + */ + Record next() throws IOException; + + public static RecordSet of(final RecordSchema schema, final Record... records) { + return new RecordSet() { + private int index = 0; + + @Override + public RecordSchema getSchema() { + return schema; + } + + @Override + public Record next() { + if (index >= records.length) { + return null; + } + + return records[index++]; + } + }; + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/ResultSetRecordSet.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/ResultSetRecordSet.java new file mode 100644 index 000000000..e166918fa --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/ResultSetRecordSet.java @@ -0,0 +1,169 @@ +/* + * 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.serialization.record; + +import java.io.Closeable; +import java.io.IOException; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.nifi.serialization.SimpleRecordSchema; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ResultSetRecordSet implements RecordSet, Closeable { + private static final Logger logger = LoggerFactory.getLogger(ResultSetRecordSet.class); + private final ResultSet rs; + private final RecordSchema schema; + private final Set rsColumnNames; + + public ResultSetRecordSet(final ResultSet rs) throws SQLException { + this.rs = rs; + this.schema = createSchema(rs); + + rsColumnNames = new HashSet<>(); + final ResultSetMetaData metadata = rs.getMetaData(); + for (int i = 0; i < metadata.getColumnCount(); i++) { + rsColumnNames.add(metadata.getColumnLabel(i + 1)); + } + } + + @Override + public RecordSchema getSchema() { + return schema; + } + + @Override + public Record next() throws IOException { + try { + if (rs.next()) { + return createRecord(rs); + } + } catch (final SQLException e) { + throw new IOException("Could not obtain next record from ResultSet", e); + } + + return null; + } + + @Override + public void close() { + try { + rs.close(); + } catch (final SQLException e) { + logger.error("Failed to close ResultSet", e); + } + } + + private Record createRecord(final ResultSet rs) throws SQLException { + final Map values = new HashMap<>(schema.getFieldCount()); + + for (final RecordField field : schema.getFields()) { + final String fieldName = field.getFieldName(); + + final Object value; + if (rsColumnNames.contains(fieldName)) { + value = rs.getObject(field.getFieldName()); + } else { + value = null; + } + + values.put(fieldName, value); + } + + return new MapRecord(schema, values); + } + + private static RecordSchema createSchema(final ResultSet rs) throws SQLException { + final ResultSetMetaData metadata = rs.getMetaData(); + final int numCols = metadata.getColumnCount(); + final List fields = new ArrayList<>(numCols); + + for (int i = 0; i < numCols; i++) { + final int column = i + 1; + final int sqlType = metadata.getColumnType(column); + + final RecordFieldType fieldType = getFieldType(sqlType); + final String fieldName = metadata.getColumnLabel(column); + final RecordField field = new RecordField(fieldName, fieldType.getDataType()); + fields.add(field); + } + + return new SimpleRecordSchema(fields); + } + + private static RecordFieldType getFieldType(final int sqlType) { + switch (sqlType) { + case Types.ARRAY: + return RecordFieldType.ARRAY; + case Types.BIGINT: + case Types.ROWID: + return RecordFieldType.LONG; + case Types.BINARY: + case Types.LONGVARBINARY: + case Types.VARBINARY: + return RecordFieldType.ARRAY; + case Types.BIT: + case Types.BOOLEAN: + return RecordFieldType.BOOLEAN; + case Types.CHAR: + return RecordFieldType.CHAR; + case Types.DATE: + return RecordFieldType.DATE; + case Types.DECIMAL: + case Types.DOUBLE: + case Types.NUMERIC: + case Types.REAL: + return RecordFieldType.DOUBLE; + case Types.FLOAT: + return RecordFieldType.FLOAT; + case Types.INTEGER: + return RecordFieldType.INT; + case Types.SMALLINT: + return RecordFieldType.SHORT; + case Types.TINYINT: + return RecordFieldType.BYTE; + case Types.LONGNVARCHAR: + case Types.LONGVARCHAR: + case Types.NCHAR: + case Types.NULL: + case Types.NVARCHAR: + case Types.VARCHAR: + return RecordFieldType.STRING; + case Types.OTHER: + case Types.JAVA_OBJECT: + return RecordFieldType.RECORD; + case Types.TIME: + case Types.TIME_WITH_TIMEZONE: + return RecordFieldType.TIME; + case Types.TIMESTAMP: + case Types.TIMESTAMP_WITH_TIMEZONE: + return RecordFieldType.TIMESTAMP; + } + + return RecordFieldType.STRING; + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/TypeMismatchException.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/TypeMismatchException.java new file mode 100644 index 000000000..af5f90917 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-service-api/src/main/java/org/apache/nifi/serialization/record/TypeMismatchException.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.serialization.record; + +public class TypeMismatchException extends RuntimeException { + public TypeMismatchException(String message) { + super(message); + } + + public TypeMismatchException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services-nar/pom.xml b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services-nar/pom.xml new file mode 100644 index 000000000..2a6f240f8 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services-nar/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + org.apache.nifi + nifi-record-serialization-services-bundle + 1.2.0-SNAPSHOT + + nifi-record-serialization-services-nar + nar + + true + true + + + + org.apache.nifi + nifi-standard-services-api-nar + nar + + + org.apache.nifi + nifi-record-serialization-services + 1.2.0-SNAPSHOT + compile + + + diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services-nar/src/main/resources/META-INF/LICENSE b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services-nar/src/main/resources/META-INF/LICENSE new file mode 100644 index 000000000..581eac4ac --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services-nar/src/main/resources/META-INF/LICENSE @@ -0,0 +1,269 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. + +APACHE NIFI SUBCOMPONENTS: + +The Apache NiFi project contains subcomponents with separate copyright +notices and license terms. Your use of the source code for the these +subcomponents is subject to the terms and conditions of the following +licenses. + +The binary distribution of this product bundles 'Hamcrest' which is available +under a BSD license. More details found here: http://hamcrest.org. + + Copyright (c) 2000-2006, www.hamcrest.org + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. Redistributions in binary form must reproduce + the above copyright notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + Neither the name of Hamcrest nor the names of its contributors may be used to endorse + or promote products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + DAMAGE. + +This product bundles 'asm' which is available under a 3-Clause BSD style license. +For details see http://asm.ow2.org/asmdex-license.html + + Copyright (c) 2012 France Télécom + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services-nar/src/main/resources/META-INF/NOTICE b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services-nar/src/main/resources/META-INF/NOTICE new file mode 100644 index 000000000..1848020c3 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services-nar/src/main/resources/META-INF/NOTICE @@ -0,0 +1,77 @@ +nifi-record-serialization-services-nar +Copyright 2014-2017 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + +=========================================== +Apache Software License v2 +=========================================== + +The following binary components are provided under the Apache Software License v2 + + (ASLv2) Apache Commons Lang + The following NOTICE information applies: + Apache Commons Lang + Copyright 2001-2015 The Apache Software Foundation + + This product includes software from the Spring Framework, + under the Apache License 2.0 (see: StringUtils.containsWhitespace()) + + (ASLv2) Grok + The following NOTICE information applies: + Grok + Copyright 2014 Anthony Corbacho, and contributors. + + (ASLv2) Groovy (org.codehaus.groovy:groovy:jar:2.4.5 - http://www.groovy-lang.org) + The following NOTICE information applies: + Groovy Language + Copyright 2003-2015 The respective authors and developers + Developers and Contributors are listed in the project POM file + and Gradle build file + + This product includes software developed by + The Groovy community (http://groovy.codehaus.org/). + + (ASLv2) Google GSON + The following NOTICE information applies: + Copyright 2008 Google Inc. + + (ASLv2) Jackson JSON processor + The following NOTICE information applies: + # Jackson JSON processor + + Jackson is a high-performance, Free/Open Source JSON processing library. + It was originally written by Tatu Saloranta (tatu.saloranta@iki.fi), and has + been in development since 2007. + It is currently developed by a community of developers, as well as supported + commercially by FasterXML.com. + + ## Licensing + + Jackson core and extension components may licensed under different licenses. + To find the details that apply to this artifact see the accompanying LICENSE file. + For more information, including possible other licensing options, contact + FasterXML.com (http://fasterxml.com). + + ## Credits + + A list of contributors may be found from CREDITS file, which is included + in some artifacts (usually source distributions); but is always available + from the source code management (SCM) system project uses. + + (ASLv2) JSON-SMART + The following NOTICE information applies: + Copyright 2011 JSON-SMART authors + + (ASLv2) JsonPath + The following NOTICE information applies: + Copyright 2011 JsonPath authors + + (ASLv2) opencsv (net.sf.opencsv:opencsv:2.3) + + (ASLv2) Apache Avro + The following NOTICE information applies: + Apache Avro + Copyright 2009-2013 The Apache Software Foundation + \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/.gitignore b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/.gitignore new file mode 100644 index 000000000..ae3c17260 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/pom.xml b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/pom.xml new file mode 100644 index 000000000..9b2a56c81 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/pom.xml @@ -0,0 +1,94 @@ + + + 4.0.0 + + org.apache.nifi + nifi-record-serialization-services-bundle + 1.2.0-SNAPSHOT + + + nifi-record-serialization-services + jar + + + + org.apache.nifi + nifi-api + + + org.apache.nifi + nifi-utils + + + org.apache.nifi + nifi-record-serialization-service-api + + + com.jayway.jsonpath + json-path + + + org.codehaus.jackson + jackson-mapper-asl + + + com.fasterxml.jackson.core + jackson-databind + + + org.apache.commons + commons-lang3 + + + net.sf.opencsv + opencsv + 2.3 + + + io.thekraken + grok + 0.1.5 + + + org.apache.avro + avro + + + + + + + org.apache.rat + apache-rat-plugin + + + src/test/resources/csv/extra-white-space.csv + src/test/resources/csv/multi-bank-account.csv + src/test/resources/csv/single-bank-account.csv + src/test/resources/grok/error-with-stack-trace.log + src/test/resources/grok/nifi-log-sample-multiline-with-stacktrace.log + src/test/resources/grok/nifi-log-sample.log + src/test/resources/grok/single-line-log-messages.txt + src/test/resources/json/bank-account-array-different-schemas.json + src/test/resources/json/bank-account-array.json + src/test/resources/json/json-with-unicode.json + src/test/resources/json/primitive-type-array.json + src/test/resources/json/single-bank-account.json + src/test/resources/json/single-element-nested-array.json + src/test/resources/json/single-element-nested.json + + + + + + diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/avro/AvroReader.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/avro/AvroReader.java new file mode 100644 index 000000000..fc0c59823 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/avro/AvroReader.java @@ -0,0 +1,40 @@ +/* + * 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.avro; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.controller.AbstractControllerService; +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.serialization.MalformedRecordException; +import org.apache.nifi.serialization.RecordReader; +import org.apache.nifi.serialization.RowRecordReaderFactory; + +@Tags({"avro", "parse", "record", "row", "reader", "delimited", "comma", "separated", "values"}) +@CapabilityDescription("Parses Avro data and returns each Avro record as an separate record.") +public class AvroReader extends AbstractControllerService implements RowRecordReaderFactory { + + @Override + public RecordReader createRecordReader(final InputStream in, final ComponentLog logger) throws MalformedRecordException, IOException { + return new AvroRecordReader(in); + } + +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/avro/AvroRecordReader.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/avro/AvroRecordReader.java new file mode 100644 index 000000000..e98a5ad97 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/avro/AvroRecordReader.java @@ -0,0 +1,254 @@ +/* + * 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.avro; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.avro.Schema; +import org.apache.avro.Schema.Field; +import org.apache.avro.Schema.Type; +import org.apache.avro.file.DataFileStream; +import org.apache.avro.generic.GenericData; +import org.apache.avro.generic.GenericData.Array; +import org.apache.avro.generic.GenericData.StringType; +import org.apache.avro.generic.GenericDatumReader; +import org.apache.avro.generic.GenericFixed; +import org.apache.avro.generic.GenericRecord; +import org.apache.avro.util.Utf8; +import org.apache.nifi.serialization.MalformedRecordException; +import org.apache.nifi.serialization.RecordReader; +import org.apache.nifi.serialization.SimpleRecordSchema; +import org.apache.nifi.serialization.record.DataType; +import org.apache.nifi.serialization.record.MapRecord; +import org.apache.nifi.serialization.record.Record; +import org.apache.nifi.serialization.record.RecordField; +import org.apache.nifi.serialization.record.RecordFieldType; +import org.apache.nifi.serialization.record.RecordSchema; + +public class AvroRecordReader implements RecordReader { + private final InputStream in; + private final Schema avroSchema; + private final DataFileStream dataFileStream; + private RecordSchema recordSchema; + + public AvroRecordReader(final InputStream in) throws IOException, MalformedRecordException { + this.in = in; + + dataFileStream = new DataFileStream<>(in, new GenericDatumReader()); + this.avroSchema = dataFileStream.getSchema(); + GenericData.setStringType(this.avroSchema, StringType.String); + } + + @Override + public void close() throws IOException { + dataFileStream.close(); + in.close(); + } + + @Override + public Record nextRecord() throws IOException, MalformedRecordException { + if (!dataFileStream.hasNext()) { + return null; + } + + GenericRecord record = null; + while (record == null && dataFileStream.hasNext()) { + record = dataFileStream.next(); + } + + final RecordSchema schema = getSchema(); + final Map values = convertRecordToObjectArray(record, schema); + return new MapRecord(schema, values); + } + + + private Map convertRecordToObjectArray(final GenericRecord record, final RecordSchema schema) { + final Map values = new HashMap<>(schema.getFieldCount()); + + for (final String fieldName : schema.getFieldNames()) { + final Object value = record.get(fieldName); + + final Field avroField = record.getSchema().getField(fieldName); + if (avroField == null) { + values.put(fieldName, null); + continue; + } + + final Schema fieldSchema = avroField.schema(); + final DataType dataType = schema.getDataType(fieldName).orElse(null); + final Object converted = convertValue(value, fieldSchema, avroField.name(), dataType); + values.put(fieldName, converted); + } + + return values; + } + + + @Override + public RecordSchema getSchema() throws MalformedRecordException { + if (recordSchema != null) { + return recordSchema; + } + + recordSchema = createSchema(avroSchema); + return recordSchema; + } + + private RecordSchema createSchema(final Schema avroSchema) { + final List recordFields = new ArrayList<>(avroSchema.getFields().size()); + for (final Field field : avroSchema.getFields()) { + final String fieldName = field.name(); + final DataType dataType = determineDataType(field.schema()); + recordFields.add(new RecordField(fieldName, dataType)); + } + + final RecordSchema recordSchema = new SimpleRecordSchema(recordFields); + return recordSchema; + } + + private Object convertValue(final Object value, final Schema avroSchema, final String fieldName, final DataType desiredType) { + if (value == null) { + return null; + } + + switch (avroSchema.getType()) { + case UNION: + if (value instanceof GenericData.Record) { + final GenericData.Record record = (GenericData.Record) value; + return convertValue(value, record.getSchema(), fieldName, desiredType); + } + break; + case RECORD: + final GenericData.Record record = (GenericData.Record) value; + final Schema recordSchema = record.getSchema(); + final List recordFields = recordSchema.getFields(); + final Map values = new HashMap<>(recordFields.size()); + for (final Field field : recordFields) { + final DataType desiredFieldType = determineDataType(field.schema()); + final Object avroFieldValue = record.get(field.name()); + final Object fieldValue = convertValue(avroFieldValue, field.schema(), field.name(), desiredFieldType); + values.put(field.name(), fieldValue); + } + final RecordSchema childSchema = createSchema(recordSchema); + return new MapRecord(childSchema, values); + case BYTES: + final ByteBuffer bb = (ByteBuffer) value; + return bb.array(); + case FIXED: + final GenericFixed fixed = (GenericFixed) value; + return fixed.bytes(); + case ENUM: + return value.toString(); + case NULL: + return null; + case STRING: + return value.toString(); + case ARRAY: + final Array array = (Array) value; + final Object[] valueArray = new Object[array.size()]; + for (int i = 0; i < array.size(); i++) { + final Schema elementSchema = avroSchema.getElementType(); + valueArray[i] = convertValue(array.get(i), elementSchema, fieldName, determineDataType(elementSchema)); + } + return valueArray; + case MAP: + final Map avroMap = (Map) value; + final Map map = new HashMap<>(avroMap.size()); + for (final Map.Entry entry : avroMap.entrySet()) { + Object obj = entry.getValue(); + if (obj instanceof Utf8 || obj instanceof CharSequence) { + obj = obj.toString(); + } + + map.put(entry.getKey().toString(), obj); + } + return map; + } + + return value; + } + + + private DataType determineDataType(final Schema avroSchema) { + final Type avroType = avroSchema.getType(); + + switch (avroType) { + case ARRAY: + case BYTES: + case FIXED: + return RecordFieldType.ARRAY.getDataType(); + case BOOLEAN: + return RecordFieldType.BOOLEAN.getDataType(); + case DOUBLE: + return RecordFieldType.DOUBLE.getDataType(); + case ENUM: + case STRING: + return RecordFieldType.STRING.getDataType(); + case FLOAT: + return RecordFieldType.FLOAT.getDataType(); + case INT: + return RecordFieldType.INT.getDataType(); + case LONG: + return RecordFieldType.LONG.getDataType(); + case RECORD: { + final List avroFields = avroSchema.getFields(); + final List recordFields = new ArrayList<>(avroFields.size()); + + for (final Field field : avroFields) { + final String fieldName = field.name(); + final Schema fieldSchema = field.schema(); + final DataType fieldType = determineDataType(fieldSchema); + recordFields.add(new RecordField(fieldName, fieldType)); + } + + final RecordSchema recordSchema = new SimpleRecordSchema(recordFields); + return RecordFieldType.RECORD.getDataType(recordSchema); + } + case NULL: + case MAP: + return RecordFieldType.RECORD.getDataType(); + case UNION: { + final List nonNullSubSchemas = avroSchema.getTypes().stream() + .filter(s -> s.getType() != Type.NULL) + .collect(Collectors.toList()); + + if (nonNullSubSchemas.size() == 1) { + return determineDataType(nonNullSubSchemas.get(0)); + } + + final List possibleChildTypes = new ArrayList<>(nonNullSubSchemas.size()); + for (final Schema subSchema : nonNullSubSchemas) { + final DataType childDataType = determineDataType(subSchema); + possibleChildTypes.add(childDataType); + } + + return RecordFieldType.CHOICE.getDataType(possibleChildTypes); + } + } + + return null; + } + +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/avro/AvroRecordSetWriter.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/avro/AvroRecordSetWriter.java new file mode 100644 index 000000000..d56c716f0 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/avro/AvroRecordSetWriter.java @@ -0,0 +1,67 @@ +/* + * 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.avro; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.avro.Schema; +import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.annotation.lifecycle.OnEnabled; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.controller.ConfigurationContext; +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.serialization.AbstractRecordSetWriter; +import org.apache.nifi.serialization.RecordSetWriter; +import org.apache.nifi.serialization.RecordSetWriterFactory; + +@Tags({"avro", "result", "set", "writer", "serializer", "record", "row"}) +@CapabilityDescription("Writes the contents of a Database ResultSet in Binary Avro format. The data types in the Result Set must match those " + + "specified by the Avro Schema. No type coercion will occur, with the exception of Date, Time, and Timestamps fields because Avro does not provide " + + "support for these types specifically. As a result, they will be converted to String fields using the configured formats. In addition, the label" + + "of the column must be a valid Avro field name.") +public class AvroRecordSetWriter extends AbstractRecordSetWriter implements RecordSetWriterFactory { + static final PropertyDescriptor SCHEMA = new PropertyDescriptor.Builder() + .name("Avro Schema") + .description("The Avro Schema to use when writing out the Result Set") + .addValidator(new AvroSchemaValidator()) + .expressionLanguageSupported(false) + .required(true) + .build(); + + private volatile Schema schema; + + @Override + protected List getSupportedPropertyDescriptors() { + final List properties = new ArrayList<>(super.getSupportedPropertyDescriptors()); + properties.add(SCHEMA); + return properties; + } + + @OnEnabled + public void storePropertyValues(final ConfigurationContext context) { + schema = new Schema.Parser().parse(context.getProperty(SCHEMA).getValue()); + } + + @Override + public RecordSetWriter createWriter(final ComponentLog logger) { + return new WriteAvroResult(schema, getDateFormat(), getTimeFormat(), getTimestampFormat()); + } + +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/avro/AvroSchemaValidator.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/avro/AvroSchemaValidator.java new file mode 100644 index 000000000..7151348f7 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/avro/AvroSchemaValidator.java @@ -0,0 +1,45 @@ +/* + * 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.avro; + +import org.apache.avro.Schema; +import org.apache.nifi.components.ValidationContext; +import org.apache.nifi.components.ValidationResult; +import org.apache.nifi.components.Validator; + +public class AvroSchemaValidator implements Validator { + + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + try { + new Schema.Parser().parse(input); + + return new ValidationResult.Builder() + .valid(true) + .build(); + } catch (final Exception e) { + return new ValidationResult.Builder() + .input(input) + .subject(subject) + .valid(false) + .explanation("Not a valid Avro Schema: " + e.getMessage()) + .build(); + } + } + +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/avro/WriteAvroResult.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/avro/WriteAvroResult.java new file mode 100644 index 000000000..d75d86d98 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/avro/WriteAvroResult.java @@ -0,0 +1,286 @@ +/* + * 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.avro; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.SQLException; +import java.sql.Time; +import java.sql.Timestamp; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.avro.Schema; +import org.apache.avro.Schema.Field; +import org.apache.avro.file.DataFileWriter; +import org.apache.avro.generic.GenericData; +import org.apache.avro.generic.GenericDatumWriter; +import org.apache.avro.generic.GenericRecord; +import org.apache.avro.io.DatumWriter; +import org.apache.nifi.serialization.RecordSetWriter; +import org.apache.nifi.serialization.WriteResult; +import org.apache.nifi.serialization.record.Record; +import org.apache.nifi.serialization.record.RecordSchema; +import org.apache.nifi.serialization.record.RecordSet; + +public class WriteAvroResult implements RecordSetWriter { + private final Schema schema; + private final DateFormat dateFormat; + private final DateFormat timeFormat; + private final DateFormat timestampFormat; + + public WriteAvroResult(final Schema schema, final String dateFormat, final String timeFormat, final String timestampFormat) { + this.schema = schema; + this.dateFormat = new SimpleDateFormat(dateFormat); + this.timeFormat = new SimpleDateFormat(timeFormat); + this.timestampFormat = new SimpleDateFormat(timestampFormat); + } + + @Override + public WriteResult write(final RecordSet rs, final OutputStream outStream) throws IOException { + Record record = rs.next(); + if (record == null) { + return WriteResult.of(0, Collections.emptyMap()); + } + + final GenericRecord rec = new GenericData.Record(schema); + + int nrOfRows = 0; + final DatumWriter datumWriter = new GenericDatumWriter<>(schema); + try (final DataFileWriter dataFileWriter = new DataFileWriter<>(datumWriter)) { + dataFileWriter.create(schema, outStream); + + final RecordSchema recordSchema = rs.getSchema(); + + do { + for (final String fieldName : recordSchema.getFieldNames()) { + final Object value = record.getValue(fieldName); + + final Field field = schema.getField(fieldName); + if (field == null) { + continue; + } + + final Object converted; + try { + converted = convert(value, field.schema(), fieldName); + } catch (final SQLException e) { + throw new IOException("Failed to write records to stream", e); + } + + rec.put(fieldName, converted); + } + + dataFileWriter.append(rec); + nrOfRows++; + } while ((record = rs.next()) != null); + } + + return WriteResult.of(nrOfRows, Collections.emptyMap()); + } + + @Override + public WriteResult write(final Record record, final OutputStream out) throws IOException { + final GenericRecord rec = new GenericData.Record(schema); + + final DatumWriter datumWriter = new GenericDatumWriter<>(schema); + try (final DataFileWriter dataFileWriter = new DataFileWriter<>(datumWriter)) { + dataFileWriter.create(schema, out); + final RecordSchema recordSchema = record.getSchema(); + + for (final String fieldName : recordSchema.getFieldNames()) { + final Object value = record.getValue(fieldName); + + final Field field = schema.getField(fieldName); + if (field == null) { + continue; + } + + final Object converted; + try { + converted = convert(value, field.schema(), fieldName); + } catch (final SQLException e) { + throw new IOException("Failed to write records to stream", e); + } + + rec.put(fieldName, converted); + } + + dataFileWriter.append(rec); + } + + return WriteResult.of(1, Collections.emptyMap()); + } + + + private Object convert(final Object value, final Schema schema, final String fieldName) throws SQLException, IOException { + if (value == null) { + return null; + } + + // Need to handle CLOB and BLOB before getObject() is called, due to ResultSet's maximum portability statement + if (value instanceof Clob) { + final Clob clob = (Clob) value; + + long numChars = clob.length(); + char[] buffer = new char[(int) numChars]; + InputStream is = clob.getAsciiStream(); + int index = 0; + int c = is.read(); + while (c > 0) { + buffer[index++] = (char) c; + c = is.read(); + } + + clob.free(); + return new String(buffer); + } + + if (value instanceof Blob) { + final Blob blob = (Blob) value; + + final long numChars = blob.length(); + final byte[] buffer = new byte[(int) numChars]; + final InputStream is = blob.getBinaryStream(); + int index = 0; + int c = is.read(); + while (c > 0) { + buffer[index++] = (byte) c; + c = is.read(); + } + + final ByteBuffer bb = ByteBuffer.wrap(buffer); + blob.free(); + return bb; + } + + if (value instanceof byte[]) { + // bytes requires little bit different handling + return ByteBuffer.wrap((byte[]) value); + } else if (value instanceof Byte) { + // tinyint(1) type is returned by JDBC driver as java.sql.Types.TINYINT + // But value is returned by JDBC as java.lang.Byte + // (at least H2 JDBC works this way) + // direct put to avro record results: + // org.apache.avro.AvroRuntimeException: Unknown datum type java.lang.Byte + return ((Byte) value).intValue(); + } else if (value instanceof Short) { + //MS SQL returns TINYINT as a Java Short, which Avro doesn't understand. + return ((Short) value).intValue(); + } else if (value instanceof BigDecimal) { + // Avro can't handle BigDecimal as a number - it will throw an AvroRuntimeException such as: "Unknown datum type: java.math.BigDecimal: 38" + return value.toString(); + } else if (value instanceof BigInteger) { + // Check the precision of the BIGINT. Some databases allow arbitrary precision (> 19), but Avro won't handle that. + // It the SQL type is BIGINT and the precision is between 0 and 19 (inclusive); if so, the BigInteger is likely a + // long (and the schema says it will be), so try to get its value as a long. + // Otherwise, Avro can't handle BigInteger as a number - it will throw an AvroRuntimeException + // such as: "Unknown datum type: java.math.BigInteger: 38". In this case the schema is expecting a string. + final BigInteger bigInt = (BigInteger) value; + if (bigInt.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) { + return value.toString(); + } else { + return bigInt.longValue(); + } + } else if (value instanceof Boolean) { + return value; + } else if (value instanceof Map) { + // TODO: Revisit how we handle a lot of these cases.... + switch (schema.getType()) { + case MAP: + return value; + case RECORD: + final GenericData.Record avroRecord = new GenericData.Record(schema); + + final Record record = (Record) value; + for (final String recordFieldName : record.getSchema().getFieldNames()) { + final Object recordFieldValue = record.getValue(recordFieldName); + + final Field field = schema.getField(recordFieldName); + if (field == null) { + continue; + } + + final Object converted = convert(recordFieldValue, field.schema(), recordFieldName); + avroRecord.put(recordFieldName, converted); + } + return avroRecord; + } + + return value.toString(); + + } else if (value instanceof List) { + return value; + } else if (value instanceof Object[]) { + final List list = new ArrayList<>(); + for (final Object o : ((Object[]) value)) { + final Object converted = convert(o, schema.getElementType(), fieldName); + list.add(converted); + } + return list; + } else if (value instanceof Number) { + return value; + } else if (value instanceof java.util.Date) { + final java.util.Date date = (java.util.Date) value; + return dateFormat.format(date); + } else if (value instanceof java.sql.Date) { + final java.sql.Date sqlDate = (java.sql.Date) value; + final java.util.Date date = new java.util.Date(sqlDate.getTime()); + return dateFormat.format(date); + } else if (value instanceof Time) { + final Time time = (Time) value; + final java.util.Date date = new java.util.Date(time.getTime()); + return timeFormat.format(date); + } else if (value instanceof Timestamp) { + final Timestamp time = (Timestamp) value; + final java.util.Date date = new java.util.Date(time.getTime()); + return timestampFormat.format(date); + } + + // The different types that we support are numbers (int, long, double, float), + // as well as boolean values and Strings. Since Avro doesn't provide + // timestamp types, we want to convert those to Strings. So we will cast anything other + // than numbers or booleans to strings by using the toString() method. + return value.toString(); + } + + + @Override + public String getMimeType() { + return "application/avro-binary"; + } + + + public static String normalizeNameForAvro(String inputName) { + String normalizedName = inputName.replaceAll("[^A-Za-z0-9_]", "_"); + if (Character.isDigit(normalizedName.charAt(0))) { + normalizedName = "_" + normalizedName; + } + return normalizedName; + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/csv/CSVReader.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/csv/CSVReader.java new file mode 100644 index 000000000..eccad7d67 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/csv/CSVReader.java @@ -0,0 +1,49 @@ +/* + * 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.csv; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.nifi.annotation.behavior.DynamicProperty; +import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.serialization.RecordReader; +import org.apache.nifi.serialization.RowRecordReaderFactory; +import org.apache.nifi.serialization.UserTypeOverrideRowReader; + +@Tags({"csv", "parse", "record", "row", "reader", "delimited", "comma", "separated", "values"}) +@CapabilityDescription("Parses CSV-formatted data, returning each row in the CSV file as a separate record. " + + "This reader assumes that the first line in the content is the column names and all subsequent lines are " + + "the values. By default, the reader will assume that all columns are of 'String' type, but this can be " + + "overridden by adding a user-defined Property where the key is the name of a column and the value is the " + + "type of the column. For example, if a Property has the name \"balance\" with a value of float, it the " + + "reader will attempt to coerce all values in the \"balance\" column into a floating-point number. See " + + "Controller Service's Usage for further documentation.") +@DynamicProperty(name = "", value = "", + description = "User-defined properties are used to indicate that the values of a specific column should be interpreted as a " + + "user-defined data type (e.g., int, double, float, date, etc.)", supportsExpressionLanguage = false) +public class CSVReader extends UserTypeOverrideRowReader implements RowRecordReaderFactory { + + @Override + public RecordReader createRecordReader(final InputStream in, final ComponentLog logger) throws IOException { + return new CSVRecordReader(in, logger, getFieldTypeOverrides()); + } + +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/csv/CSVRecordReader.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/csv/CSVRecordReader.java new file mode 100644 index 000000000..c2e896356 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/csv/CSVRecordReader.java @@ -0,0 +1,216 @@ +/* + * 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.csv; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.serialization.MalformedRecordException; +import org.apache.nifi.serialization.RecordReader; +import org.apache.nifi.serialization.SimpleRecordSchema; +import org.apache.nifi.serialization.record.DataType; +import org.apache.nifi.serialization.record.MapRecord; +import org.apache.nifi.serialization.record.Record; +import org.apache.nifi.serialization.record.RecordField; +import org.apache.nifi.serialization.record.RecordFieldType; +import org.apache.nifi.serialization.record.RecordSchema; + +import au.com.bytecode.opencsv.CSVReader; + +public class CSVRecordReader implements RecordReader { + private final ComponentLog logger; + private final CSVReader reader; + private final String[] firstLine; + private final Map fieldTypeOverrides; + private RecordSchema schema; + + public CSVRecordReader(final InputStream in, final ComponentLog logger, final Map fieldTypeOverrides) throws IOException { + this.logger = logger; + reader = new CSVReader(new InputStreamReader(new BufferedInputStream(in))); + firstLine = reader.readNext(); + this.fieldTypeOverrides = fieldTypeOverrides; + } + + @Override + public Record nextRecord() throws IOException, MalformedRecordException { + final RecordSchema schema = getSchema(); + + while (true) { + final String[] line = reader.readNext(); + if (line == null) { + return null; + } + + final List fieldTypes = schema.getDataTypes(); + if (fieldTypes.size() != line.length) { + logger.warn("Found record with incorrect number of fields. Expected {} but found {}; skipping record", new Object[] {fieldTypes.size(), line.length}); + continue; + } + + try { + final Map rowValues = new HashMap<>(schema.getFieldCount()); + + int i = 0; + for (final String fieldName : schema.getFieldNames()) { + if (i >= line.length) { + rowValues.put(fieldName, null); + continue; + } + + final String rawValue = line[i++].trim(); + final Object converted = convert(schema.getDataType(fieldName).orElse(null), rawValue); + rowValues.put(fieldName, converted); + } + + return new MapRecord(schema, rowValues); + } catch (final Exception e) { + throw new MalformedRecordException("Found invalid CSV record", e); + } + } + } + + @Override + public RecordSchema getSchema() { + if (schema != null) { + return schema; + } + + final List recordFields = new ArrayList<>(); + for (final String element : firstLine) { + + final String name = element.trim(); + final DataType dataType; + + final DataType overriddenDataType = fieldTypeOverrides.get(name); + if (overriddenDataType != null) { + dataType = overriddenDataType; + } else { + dataType = RecordFieldType.STRING.getDataType(); + } + + final RecordField field = new RecordField(name, dataType); + recordFields.add(field); + } + + if (recordFields.isEmpty()) { + recordFields.add(new RecordField("line", RecordFieldType.STRING.getDataType())); + } + + schema = new SimpleRecordSchema(recordFields); + return schema; + } + + protected Object convert(final DataType dataType, final String value) { + if (dataType == null) { + return value; + } + + switch (dataType.getFieldType()) { + case BOOLEAN: + if (value.length() == 0) { + return null; + } + return Boolean.parseBoolean(value); + case BYTE: + if (value.length() == 0) { + return null; + } + return Byte.parseByte(value); + case SHORT: + if (value.length() == 0) { + return null; + } + return Short.parseShort(value); + case INT: + if (value.length() == 0) { + return null; + } + return Integer.parseInt(value); + case LONG: + case BIGINT: + if (value.length() == 0) { + return null; + } + return Long.parseLong(value); + case FLOAT: + if (value.length() == 0) { + return null; + } + return Float.parseFloat(value); + case DOUBLE: + if (value.length() == 0) { + return null; + } + return Double.parseDouble(value); + case DATE: + if (value.length() == 0) { + return null; + } + try { + final Date date = new SimpleDateFormat(dataType.getFormat()).parse(value); + return new java.sql.Date(date.getTime()); + } catch (final ParseException e) { + logger.warn("Found invalid value for DATE field: " + value + " does not match expected format of " + + dataType.getFormat() + "; will substitute a NULL value for this field"); + return null; + } + case TIME: + if (value.length() == 0) { + return null; + } + try { + final Date date = new SimpleDateFormat(dataType.getFormat()).parse(value); + return new java.sql.Time(date.getTime()); + } catch (final ParseException e) { + logger.warn("Found invalid value for TIME field: " + value + " does not match expected format of " + + dataType.getFormat() + "; will substitute a NULL value for this field"); + return null; + } + case TIMESTAMP: + if (value.length() == 0) { + return null; + } + try { + final Date date = new SimpleDateFormat(dataType.getFormat()).parse(value); + return new java.sql.Timestamp(date.getTime()); + } catch (final ParseException e) { + logger.warn("Found invalid value for TIMESTAMP field: " + value + " does not match expected format of " + + dataType.getFormat() + "; will substitute a NULL value for this field"); + return null; + } + case STRING: + default: + return value; + } + } + + @Override + public void close() throws IOException { + reader.close(); + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/csv/CSVRecordSetWriter.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/csv/CSVRecordSetWriter.java new file mode 100644 index 000000000..906e9c46d --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/csv/CSVRecordSetWriter.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.csv; + +import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.serialization.AbstractRecordSetWriter; +import org.apache.nifi.serialization.RecordSetWriter; +import org.apache.nifi.serialization.RecordSetWriterFactory; + +@Tags({"csv", "result", "set", "writer", "serializer", "record", "row"}) +@CapabilityDescription("Writes the contents of a Database ResultSet as CSV data. The first line written " + + "will be the column names. All subsequent lines will be the values corresponding to those columns.") +public class CSVRecordSetWriter extends AbstractRecordSetWriter implements RecordSetWriterFactory { + + @Override + public RecordSetWriter createWriter(final ComponentLog logger) { + return new WriteCSVResult(getDateFormat(), getTimeFormat(), getTimestampFormat()); + } + +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/csv/WriteCSVResult.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/csv/WriteCSVResult.java new file mode 100644 index 000000000..79c602d77 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/csv/WriteCSVResult.java @@ -0,0 +1,127 @@ +/* + * 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.csv; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.util.Collections; +import java.util.Optional; + +import org.apache.nifi.serialization.RecordSetWriter; +import org.apache.nifi.serialization.WriteResult; +import org.apache.nifi.serialization.record.DataType; +import org.apache.nifi.serialization.record.Record; +import org.apache.nifi.serialization.record.RecordSchema; +import org.apache.nifi.serialization.record.RecordSet; +import org.apache.nifi.stream.io.NonCloseableOutputStream; + +import au.com.bytecode.opencsv.CSVWriter; + +public class WriteCSVResult implements RecordSetWriter { + private final String dateFormat; + private final String timeFormat; + private final String timestampFormat; + + public WriteCSVResult(final String dateFormat, final String timeFormat, final String timestampFormat) { + this.dateFormat = dateFormat; + this.timeFormat = timeFormat; + this.timestampFormat = timestampFormat; + } + + private String getFormat(final Record record, final String fieldName) { + final Optional dataTypeOption = record.getSchema().getDataType(fieldName); + if (!dataTypeOption.isPresent()) { + return null; + } + + final DataType dataType = dataTypeOption.get(); + switch (dataType.getFieldType()) { + case DATE: + return dateFormat == null ? dataType.getFormat() : dateFormat; + case TIME: + return timeFormat == null ? dataType.getFormat() : timeFormat; + case TIMESTAMP: + return timestampFormat == null ? dataType.getFormat() : timestampFormat; + } + + return dataType.getFormat(); + } + + @Override + public WriteResult write(final RecordSet rs, final OutputStream rawOut) throws IOException { + int count = 0; + try (final OutputStream nonCloseable = new NonCloseableOutputStream(rawOut); + final OutputStreamWriter streamWriter = new OutputStreamWriter(nonCloseable); + final CSVWriter writer = new CSVWriter(streamWriter)) { + + try { + final RecordSchema schema = rs.getSchema(); + final String[] columnNames = schema.getFieldNames().toArray(new String[0]); + writer.writeNext(columnNames); + + Record record; + while ((record = rs.next()) != null) { + final String[] colVals = new String[schema.getFieldCount()]; + int i = 0; + for (final String fieldName : schema.getFieldNames()) { + colVals[i++] = record.getAsString(fieldName, getFormat(record, fieldName)); + } + + writer.writeNext(colVals); + count++; + } + } catch (final Exception e) { + throw new IOException("Failed to serialize results", e); + } + } + + return WriteResult.of(count, Collections.emptyMap()); + } + + @Override + public WriteResult write(final Record record, final OutputStream rawOut) throws IOException { + try (final OutputStream nonCloseable = new NonCloseableOutputStream(rawOut); + final OutputStreamWriter streamWriter = new OutputStreamWriter(nonCloseable); + final CSVWriter writer = new CSVWriter(streamWriter)) { + + try { + final RecordSchema schema = record.getSchema(); + final String[] columnNames = schema.getFieldNames().toArray(new String[0]); + writer.writeNext(columnNames); + + final String[] colVals = new String[schema.getFieldCount()]; + int i = 0; + for (final String fieldName : schema.getFieldNames()) { + colVals[i++] = record.getAsString(fieldName, getFormat(record, fieldName)); + } + + writer.writeNext(colVals); + } catch (final Exception e) { + throw new IOException("Failed to serialize results", e); + } + } + + return WriteResult.of(1, Collections.emptyMap()); + } + + @Override + public String getMimeType() { + return "text/csv"; + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/grok/GrokExpressionValidator.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/grok/GrokExpressionValidator.java new file mode 100644 index 000000000..dd9c4e05d --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/grok/GrokExpressionValidator.java @@ -0,0 +1,48 @@ +/* + * 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.grok; + +import org.apache.nifi.components.ValidationContext; +import org.apache.nifi.components.ValidationResult; +import org.apache.nifi.components.Validator; + +import io.thekraken.grok.api.Grok; + +public class GrokExpressionValidator implements Validator { + + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + try { + new Grok().compile(input); + } catch (final Exception e) { + return new ValidationResult.Builder() + .input(input) + .subject(subject) + .valid(false) + .explanation("Invalid Grok pattern: " + e.getMessage()) + .build(); + } + + return new ValidationResult.Builder() + .input(input) + .subject(subject) + .valid(true) + .build(); + } + +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/grok/GrokReader.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/grok/GrokReader.java new file mode 100644 index 000000000..f72d5d528 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/grok/GrokReader.java @@ -0,0 +1,99 @@ +/* + * 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.grok; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; + +import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.annotation.lifecycle.OnEnabled; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.controller.ConfigurationContext; +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.processor.util.StandardValidators; +import org.apache.nifi.serialization.RecordReader; +import org.apache.nifi.serialization.RowRecordReaderFactory; +import org.apache.nifi.serialization.UserTypeOverrideRowReader; + +import io.thekraken.grok.api.Grok; +import io.thekraken.grok.api.exception.GrokException; + +@Tags({"grok", "logs", "logfiles", "parse", "unstructured", "text", "record", "reader", "regex", "pattern", "logstash"}) +@CapabilityDescription("Provides a mechanism for reading unstructured text data, such as log files, and structuring the data " + + "so that it can be processed. The service is configured using Grok patterns. " + + "The service reads from a stream of data and splits each message that it finds into a separate Record, each containing the fields that are configured. " + + "If a line in the input does not match the expected message pattern, the line of text is considered to be part of the previous " + + "message, with the exception of stack traces. A stack trace that is found at the end of a log message is considered to be part " + + "of the previous message but is added to the 'STACK_TRACE' field of the Record. If a record has no stack trace, it will have a NULL value " + + "for the STACK_TRACE field.") +public class GrokReader extends UserTypeOverrideRowReader implements RowRecordReaderFactory { + private volatile Grok grok; + + private static final String DEFAULT_PATTERN_NAME = "/default-grok-patterns.txt"; + + static final PropertyDescriptor PATTERN_FILE = new PropertyDescriptor.Builder() + .name("Grok Pattern File") + .description("Path to a file that contains Grok Patterns to use for parsing logs. If not specified, a built-in default Pattern file " + + "will be used. If specified, all patterns in the given pattern file will override the default patterns. See the Controller Service's " + + "Additional Details for a list of pre-defined patterns.") + .addValidator(StandardValidators.FILE_EXISTS_VALIDATOR) + .expressionLanguageSupported(true) + .required(false) + .build(); + static final PropertyDescriptor GROK_EXPRESSION = new PropertyDescriptor.Builder() + .name("Grok Expression") + .description("Specifies the format of a log line in Grok format. This allows the Record Reader to understand how to parse each log line. " + + "If a line in the log file does not match this pattern, the line will be assumed to belong to the previous log message.") + .addValidator(new GrokExpressionValidator()) + .required(true) + .build(); + + @Override + protected List getSupportedPropertyDescriptors() { + final List properties = new ArrayList<>(); + properties.add(PATTERN_FILE); + properties.add(GROK_EXPRESSION); + return properties; + } + + @OnEnabled + public void preCompile(final ConfigurationContext context) throws GrokException, IOException { + grok = new Grok(); + + try (final InputStream in = getClass().getResourceAsStream(DEFAULT_PATTERN_NAME); + final Reader reader = new InputStreamReader(in)) { + grok.addPatternFromReader(reader); + } + + if (context.getProperty(PATTERN_FILE).isSet()) { + grok.addPatternFromFile(context.getProperty(PATTERN_FILE).getValue()); + } + + grok.compile(context.getProperty(GROK_EXPRESSION).getValue()); + } + + @Override + public RecordReader createRecordReader(final InputStream in, final ComponentLog logger) throws IOException { + return new GrokRecordReader(in, grok, getFieldTypeOverrides()); + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/grok/GrokRecordReader.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/grok/GrokRecordReader.java new file mode 100644 index 000000000..bdf12f993 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/grok/GrokRecordReader.java @@ -0,0 +1,323 @@ +/* + * 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.grok; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.time.FastDateFormat; +import org.apache.nifi.serialization.MalformedRecordException; +import org.apache.nifi.serialization.RecordReader; +import org.apache.nifi.serialization.SimpleRecordSchema; +import org.apache.nifi.serialization.record.DataType; +import org.apache.nifi.serialization.record.MapRecord; +import org.apache.nifi.serialization.record.Record; +import org.apache.nifi.serialization.record.RecordField; +import org.apache.nifi.serialization.record.RecordFieldType; +import org.apache.nifi.serialization.record.RecordSchema; + +import io.thekraken.grok.api.Grok; +import io.thekraken.grok.api.GrokUtils; +import io.thekraken.grok.api.Match; + +public class GrokRecordReader implements RecordReader { + private final BufferedReader reader; + private final Grok grok; + private final Map fieldTypeOverrides; + + private String nextLine; + private RecordSchema schema; + + static final String STACK_TRACE_COLUMN_NAME = "STACK_TRACE"; + private static final Pattern STACK_TRACE_PATTERN = Pattern.compile( + "^\\s*(?:(?: |\\t)+at )|" + + "(?:(?: |\\t)+\\[CIRCULAR REFERENCE\\:)|" + + "(?:Caused by\\: )|" + + "(?:Suppressed\\: )|" + + "(?:\\s+... \\d+ (?:more|common frames? omitted)$)"); + + private static final FastDateFormat TIME_FORMAT_DATE; + private static final FastDateFormat TIME_FORMAT_TIME; + private static final FastDateFormat TIME_FORMAT_TIMESTAMP; + + static { + final TimeZone gmt = TimeZone.getTimeZone("GMT"); + TIME_FORMAT_DATE = FastDateFormat.getInstance("yyyy-MM-dd", gmt); + TIME_FORMAT_TIME = FastDateFormat.getInstance("HH:mm:ss", gmt); + TIME_FORMAT_TIMESTAMP = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss", gmt); + } + + public GrokRecordReader(final InputStream in, final Grok grok, final Map fieldTypeOverrides) { + this.reader = new BufferedReader(new InputStreamReader(in)); + this.grok = grok; + this.fieldTypeOverrides = fieldTypeOverrides; + } + + @Override + public void close() throws IOException { + reader.close(); + } + + @Override + public Record nextRecord() throws IOException, MalformedRecordException { + final String line = nextLine == null ? reader.readLine() : nextLine; + nextLine = null; // ensure that we don't process nextLine again + if (line == null) { + return null; + } + + final RecordSchema schema = getSchema(); + + final Match match = grok.match(line); + match.captures(); + final Map valueMap = match.toMap(); + if (valueMap.isEmpty()) { // We were unable to match the pattern so return an empty Object array. + return new MapRecord(schema, Collections.emptyMap()); + } + + // Read the next line to see if it matches the pattern (in which case we will simply leave it for + // the next call to nextRecord()) or we will attach it to the previously read record. + String stackTrace = null; + final StringBuilder toAppend = new StringBuilder(); + while ((nextLine = reader.readLine()) != null) { + final Match nextLineMatch = grok.match(nextLine); + nextLineMatch.captures(); + final Map nextValueMap = nextLineMatch.toMap(); + if (nextValueMap.isEmpty()) { + // next line did not match. Check if it indicates a Stack Trace. If so, read until + // the stack trace ends. Otherwise, append the next line to the last field in the record. + if (isStartOfStackTrace(nextLine)) { + stackTrace = readStackTrace(nextLine); + break; + } else { + toAppend.append("\n").append(nextLine); + } + } else { + // The next line matched our pattern. + break; + } + } + + try { + final List fieldTypes = schema.getDataTypes(); + final Map values = new HashMap<>(fieldTypes.size()); + + for (final String fieldName : schema.getFieldNames()) { + final Object value = valueMap.get(fieldName); + if (value == null) { + values.put(fieldName, null); + continue; + } + + final DataType fieldType = schema.getDataType(fieldName).orElse(null); + final Object converted = convert(fieldType, value.toString()); + values.put(fieldName, converted); + } + + final String lastFieldBeforeStackTrace = schema.getFieldNames().get(schema.getFieldCount() - 2); + if (toAppend.length() > 0) { + final Object existingValue = values.get(lastFieldBeforeStackTrace); + final String updatedValue = existingValue == null ? toAppend.toString() : existingValue + toAppend.toString(); + values.put(lastFieldBeforeStackTrace, updatedValue); + } + + values.put(STACK_TRACE_COLUMN_NAME, stackTrace); + + return new MapRecord(schema, values); + } catch (final Exception e) { + throw new MalformedRecordException("Found invalid log record and will skip it. Record: " + line, e); + } + } + + + private boolean isStartOfStackTrace(final String line) { + if (line == null) { + return false; + } + + // Stack Traces are generally of the form: + // java.lang.IllegalArgumentException: My message + // at org.apache.nifi.MyClass.myMethod(MyClass.java:48) + // at java.lang.Thread.run(Thread.java:745) [na:1.8.0_60] + // Caused by: java.net.SocketTimeoutException: null + // ... 13 common frames omitted + + int index = line.indexOf("Exception: "); + if (index < 0) { + index = line.indexOf("Error: "); + } + + if (index < 0) { + return false; + } + + if (line.indexOf(" ") < index) { + return false; + } + + return true; + } + + private String readStackTrace(final String firstLine) throws IOException { + final StringBuilder sb = new StringBuilder(firstLine); + + String line; + while ((line = reader.readLine()) != null) { + if (isLineInStackTrace(line)) { + sb.append("\n").append(line); + } else { + nextLine = line; + break; + } + } + + return sb.toString(); + } + + private boolean isLineInStackTrace(final String line) { + return STACK_TRACE_PATTERN.matcher(line).find(); + } + + + protected Object convert(final DataType fieldType, final String string) { + if (fieldType == null) { + return string; + } + switch (fieldType.getFieldType()) { + case BOOLEAN: + if (string.length() == 0) { + return null; + } + return Boolean.parseBoolean(string); + case BYTE: + if (string.length() == 0) { + return null; + } + return Byte.parseByte(string); + case SHORT: + if (string.length() == 0) { + return null; + } + return Short.parseShort(string); + case INT: + if (string.length() == 0) { + return null; + } + return Integer.parseInt(string); + case LONG: + if (string.length() == 0) { + return null; + } + return Long.parseLong(string); + case FLOAT: + if (string.length() == 0) { + return null; + } + return Float.parseFloat(string); + case DOUBLE: + if (string.length() == 0) { + return null; + } + return Double.parseDouble(string); + case DATE: + if (string.length() == 0) { + return null; + } + try { + Date date = TIME_FORMAT_DATE.parse(string); + return new java.sql.Date(date.getTime()); + } catch (ParseException e) { + return null; + } + case TIME: + if (string.length() == 0) { + return null; + } + try { + Date date = TIME_FORMAT_TIME.parse(string); + return new java.sql.Time(date.getTime()); + } catch (ParseException e) { + return null; + } + case TIMESTAMP: + if (string.length() == 0) { + return null; + } + try { + Date date = TIME_FORMAT_TIMESTAMP.parse(string); + return new java.sql.Timestamp(date.getTime()); + } catch (ParseException e) { + return null; + } + case STRING: + default: + return string; + } + } + + + @Override + public RecordSchema getSchema() { + if (schema != null) { + return schema; + } + + final List fields = new ArrayList<>(); + + String grokExpression = grok.getOriginalGrokPattern(); + while (grokExpression.length() > 0) { + final Matcher matcher = GrokUtils.GROK_PATTERN.matcher(grokExpression); + if (matcher.find()) { + final Map namedGroups = GrokUtils.namedGroups(matcher, grokExpression); + final String fieldName = namedGroups.get("subname"); + + DataType dataType = fieldTypeOverrides.get(fieldName); + if (dataType == null) { + dataType = RecordFieldType.STRING.getDataType(); + } + + final RecordField recordField = new RecordField(fieldName, dataType); + fields.add(recordField); + + if (grokExpression.length() > matcher.end() + 1) { + grokExpression = grokExpression.substring(matcher.end() + 1); + } else { + break; + } + } + } + + fields.add(new RecordField(STACK_TRACE_COLUMN_NAME, RecordFieldType.STRING.getDataType())); + + schema = new SimpleRecordSchema(fields); + return schema; + } + +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/AbstractJsonRowRecordReader.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/AbstractJsonRowRecordReader.java new file mode 100644 index 000000000..286326a85 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/AbstractJsonRowRecordReader.java @@ -0,0 +1,307 @@ +/* + * 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.json; + +import java.io.IOException; +import java.io.InputStream; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.TimeZone; + +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.serialization.MalformedRecordException; +import org.apache.nifi.serialization.RecordReader; +import org.apache.nifi.serialization.SimpleRecordSchema; +import org.apache.nifi.serialization.record.DataType; +import org.apache.nifi.serialization.record.Record; +import org.apache.nifi.serialization.record.RecordField; +import org.apache.nifi.serialization.record.RecordFieldType; +import org.apache.nifi.serialization.record.RecordSchema; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonNode; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonToken; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.node.ArrayNode; + + +public abstract class AbstractJsonRowRecordReader implements RecordReader { + private final ComponentLog logger; + private final JsonParser jsonParser; + private final JsonFactory jsonFactory; + private final boolean array; + private final JsonNode firstJsonNode; + + private boolean firstObjectConsumed = false; + + private static final TimeZone gmt = TimeZone.getTimeZone("GMT"); + + + public AbstractJsonRowRecordReader(final InputStream in, final ComponentLog logger) throws IOException, MalformedRecordException { + this.logger = logger; + + jsonFactory = new JsonFactory(); + try { + jsonParser = jsonFactory.createJsonParser(in); + jsonParser.setCodec(new ObjectMapper()); + + JsonToken token = jsonParser.nextToken(); + if (token == JsonToken.START_ARRAY) { + array = true; + token = jsonParser.nextToken(); // advance to START_OBJECT token + } else { + array = false; + } + + if (token == JsonToken.START_OBJECT) { // could be END_ARRAY also + firstJsonNode = jsonParser.readValueAsTree(); + } else { + firstJsonNode = null; + } + } catch (final JsonParseException e) { + throw new MalformedRecordException("Could not parse data as JSON", e); + } + } + + @Override + public Record nextRecord() throws IOException, MalformedRecordException { + if (firstObjectConsumed && !array) { + return null; + } + + final JsonNode nextNode = getNextJsonNode(); + final RecordSchema schema = getSchema(); + try { + return convertJsonNodeToRecord(nextNode, schema); + } catch (final MalformedRecordException mre) { + throw mre; + } catch (final IOException ioe) { + throw ioe; + } catch (final Exception e) { + logger.debug("Failed to convert JSON Element {} into a Record object using schema {} due to {}", new Object[] {nextNode, schema, e.toString(), e}); + throw new MalformedRecordException("Successfully parsed a JSON object from input but failed to convert into a Record object with the given schema", e); + } + } + + protected DataType determineFieldType(final JsonNode node) { + if (node.isDouble()) { + return RecordFieldType.DOUBLE.getDataType(); + } + if (node.isBoolean()) { + return RecordFieldType.BOOLEAN.getDataType(); + } + if (node.isFloatingPointNumber()) { + return RecordFieldType.FLOAT.getDataType(); + } + if (node.isBigInteger()) { + return RecordFieldType.BIGINT.getDataType(); + } + if (node.isBigDecimal()) { + return RecordFieldType.DOUBLE.getDataType(); + } + if (node.isLong()) { + return RecordFieldType.LONG.getDataType(); + } + if (node.isInt()) { + return RecordFieldType.INT.getDataType(); + } + if (node.isTextual()) { + return RecordFieldType.STRING.getDataType(); + } + if (node.isArray()) { + return RecordFieldType.ARRAY.getDataType(); + } + + final RecordSchema childSchema = determineSchema(node); + return RecordFieldType.RECORD.getDataType(childSchema); + } + + protected RecordSchema determineSchema(final JsonNode jsonNode) { + final List recordFields = new ArrayList<>(); + + final Iterator> itr = jsonNode.getFields(); + while (itr.hasNext()) { + final Map.Entry entry = itr.next(); + final String elementName = entry.getKey(); + final JsonNode node = entry.getValue(); + + DataType dataType = determineFieldType(node); + recordFields.add(new RecordField(elementName, dataType)); + } + + return new SimpleRecordSchema(recordFields); + } + + protected Object convertField(final JsonNode fieldNode, final String fieldName, final DataType desiredType) throws IOException, MalformedRecordException { + if (fieldNode == null || fieldNode.isNull()) { + return null; + } + + switch (desiredType.getFieldType()) { + case BOOLEAN: + return fieldNode.asBoolean(); + case BYTE: + return (byte) fieldNode.asInt(); + case CHAR: + final String text = fieldNode.asText(); + if (text.isEmpty()) { + return null; + } + return text.charAt(0); + case DOUBLE: + return fieldNode.asDouble(); + case FLOAT: + return (float) fieldNode.asDouble(); + case INT: + return fieldNode.asInt(); + case LONG: + return fieldNode.asLong(); + case SHORT: + return (short) fieldNode.asInt(); + case STRING: + return fieldNode.asText(); + case DATE: { + final String string = fieldNode.asText(); + if (string.isEmpty()) { + return null; + } + + try { + final DateFormat dateFormat = new SimpleDateFormat(desiredType.getFormat()); + dateFormat.setTimeZone(gmt); + final Date date = dateFormat.parse(string); + return new java.sql.Date(date.getTime()); + } catch (ParseException e) { + logger.warn("Failed to convert JSON field to Date for field {} (value {})", new Object[] {fieldName, string, e}); + return null; + } + } + case TIME: { + final String string = fieldNode.asText(); + if (string.isEmpty()) { + return null; + } + + try { + final DateFormat dateFormat = new SimpleDateFormat(desiredType.getFormat()); + dateFormat.setTimeZone(gmt); + final Date date = dateFormat.parse(string); + return new java.sql.Date(date.getTime()); + } catch (ParseException e) { + logger.warn("Failed to convert JSON field to Time for field {} (value {})", new Object[] {fieldName, string, e}); + return null; + } + } + case TIMESTAMP: { + final String string = fieldNode.asText(); + if (string.isEmpty()) { + return null; + } + + try { + final DateFormat dateFormat = new SimpleDateFormat(desiredType.getFormat()); + dateFormat.setTimeZone(gmt); + final Date date = dateFormat.parse(string); + return new java.sql.Date(date.getTime()); + } catch (ParseException e) { + logger.warn("Failed to convert JSON field to Timestamp for field {} (value {})", new Object[] {fieldName, string, e}); + return null; + } + } + case ARRAY: { + final ArrayNode arrayNode = (ArrayNode) fieldNode; + final int numElements = arrayNode.size(); + final Object[] arrayElements = new Object[numElements]; + int count = 0; + for (final JsonNode node : arrayNode) { + final Object converted = convertField(node, fieldName, determineFieldType(node)); + arrayElements[count++] = converted; + } + + return arrayElements; + } + case RECORD: { + if (fieldNode.isObject()) { + final Optional childSchema = desiredType.getChildRecordSchema(); + if (!childSchema.isPresent()) { + return null; + } + + return convertJsonNodeToRecord(fieldNode, childSchema.get()); + } else { + return fieldNode.toString(); + } + } + } + + return fieldNode.toString(); + } + + private JsonNode getNextJsonNode() throws JsonParseException, IOException, MalformedRecordException { + if (!firstObjectConsumed) { + firstObjectConsumed = true; + return firstJsonNode; + } + + while (true) { + final JsonToken token = jsonParser.nextToken(); + if (token == null) { + return null; + } + + switch (token) { + case END_OBJECT: + continue; + case START_OBJECT: + return jsonParser.readValueAsTree(); + case END_ARRAY: + case START_ARRAY: + return null; + default: + throw new MalformedRecordException("Expected to get a JSON Object but got a token of type " + token.name()); + } + } + } + + @Override + public void close() throws IOException { + jsonParser.close(); + } + + protected JsonParser getJsonParser() { + return jsonParser; + } + + protected JsonFactory getJsonFactory() { + return jsonFactory; + } + + protected Optional getFirstJsonNode() { + return Optional.ofNullable(firstJsonNode); + } + + protected abstract Record convertJsonNodeToRecord(final JsonNode nextNode, final RecordSchema schema) throws IOException, MalformedRecordException; +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/JsonPathReader.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/JsonPathReader.java new file mode 100644 index 000000000..b43b1c150 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/JsonPathReader.java @@ -0,0 +1,126 @@ +/* + * 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.json; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; + +import org.apache.nifi.annotation.behavior.DynamicProperty; +import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.SeeAlso; +import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.annotation.lifecycle.OnEnabled; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.ValidationContext; +import org.apache.nifi.components.ValidationResult; +import org.apache.nifi.controller.AbstractControllerService; +import org.apache.nifi.controller.ConfigurationContext; +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.serialization.MalformedRecordException; +import org.apache.nifi.serialization.RecordReader; +import org.apache.nifi.serialization.RowRecordReaderFactory; +import org.apache.nifi.serialization.record.DataType; + +import com.jayway.jsonpath.JsonPath; + +@Tags({"json", "jsonpath", "record", "reader", "parser"}) +@CapabilityDescription("Parses JSON records and evaluates user-defined JSON Path's against each JSON object. The root element may be either " + + "a single JSON object or a JSON array. If a JSON array is found, each JSON object within that array is treated as a separate record. " + + "User-defined properties define the fields that should be extracted from the JSON in order to form the fields of a Record. Any JSON field " + + "that is not extracted via a JSONPath will not be returned in the JSON Records.") +@SeeAlso(JsonTreeReader.class) +@DynamicProperty(name = "The field name for the record. If it is desirable to enforce that the value be coerced into a given type, its type can be included " + + "in the name by using a syntax of :. For example, \"balance:double\".", + value="A JSONPath Expression that will be evaluated against each JSON record. The result of the JSONPath will be the value of the " + + "field whose name is the same as the property name.", + description="User-defined properties identifiy how to extract specific fields from a JSON object in order to create a Record", + supportsExpressionLanguage=false) +public class JsonPathReader extends AbstractControllerService implements RowRecordReaderFactory { + + private volatile LinkedHashMap jsonPaths; + private volatile Map fieldTypeOverrides; + + @Override + protected PropertyDescriptor getSupportedDynamicPropertyDescriptor(final String propertyDescriptorName) { + return new PropertyDescriptor.Builder() + .name(propertyDescriptorName) + .description("JsonPath Expression that indicates how to retrieve the value from a JSON Object for the '" + propertyDescriptorName + "' column") + .dynamic(true) + .required(false) + .addValidator(new JsonPathValidator()) + .build(); + } + + @OnEnabled + public void compileJsonPaths(final ConfigurationContext context) { + final Map fieldTypes = new HashMap<>(context.getProperties().size()); + + final LinkedHashMap compiled = new LinkedHashMap<>(); + for (final PropertyDescriptor descriptor : context.getProperties().keySet()) { + if (!descriptor.isDynamic()) { + continue; + } + + final String fieldName = PropertyNameUtil.getFieldName(descriptor.getName()); + final Optional dataTypeOption = PropertyNameUtil.getDataType(descriptor.getName()); + if (dataTypeOption.isPresent()) { + fieldTypes.put(fieldName, dataTypeOption.get()); + } + + final String expression = context.getProperty(descriptor).getValue(); + final JsonPath jsonPath = JsonPath.compile(expression); + compiled.put(fieldName, jsonPath); + } + + jsonPaths = compiled; + fieldTypeOverrides = fieldTypes; + } + + @Override + protected Collection customValidate(final ValidationContext validationContext) { + boolean pathSpecified = false; + for (final PropertyDescriptor property : validationContext.getProperties().keySet()) { + if (property.isDynamic()) { + pathSpecified = true; + break; + } + } + + if (pathSpecified) { + return Collections.emptyList(); + } + + return Collections.singleton(new ValidationResult.Builder() + .subject("JSON Paths") + .valid(false) + .explanation("No JSON Paths were specified") + .build()); + } + + @Override + public RecordReader createRecordReader(final InputStream in, final ComponentLog logger) throws IOException, MalformedRecordException { + return new JsonPathRowRecordReader(jsonPaths, fieldTypeOverrides, in, logger); + } + +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/JsonPathRowRecordReader.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/JsonPathRowRecordReader.java new file mode 100644 index 000000000..9654b978f --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/JsonPathRowRecordReader.java @@ -0,0 +1,280 @@ +/* + * 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.json; + +import java.io.IOException; +import java.io.InputStream; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.TimeZone; +import java.util.stream.Collectors; + +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.serialization.DataTypeUtils; +import org.apache.nifi.serialization.MalformedRecordException; +import org.apache.nifi.serialization.SimpleRecordSchema; +import org.apache.nifi.serialization.record.DataType; +import org.apache.nifi.serialization.record.MapRecord; +import org.apache.nifi.serialization.record.Record; +import org.apache.nifi.serialization.record.RecordField; +import org.apache.nifi.serialization.record.RecordFieldType; +import org.apache.nifi.serialization.record.RecordSchema; +import org.codehaus.jackson.JsonNode; + +import com.jayway.jsonpath.Configuration; +import com.jayway.jsonpath.DocumentContext; +import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.PathNotFoundException; +import com.jayway.jsonpath.spi.json.JacksonJsonProvider; + +public class JsonPathRowRecordReader extends AbstractJsonRowRecordReader { + private static final Configuration STRICT_PROVIDER_CONFIGURATION = Configuration.builder().jsonProvider(new JacksonJsonProvider()).build(); + + private static final String TIME_FORMAT_DATE = "yyyy-MM-dd"; + private static final String TIME_FORMAT_TIME = "HH:mm:ss"; + private static final String TIME_FORMAT_TIMESTAMP = "yyyy-MM-dd HH:mm:ss"; + private static final TimeZone gmt = TimeZone.getTimeZone("GMT"); + + private final LinkedHashMap jsonPaths; + private final Map fieldTypeOverrides; + private final InputStream in; + private RecordSchema schema; + + public JsonPathRowRecordReader(final LinkedHashMap jsonPaths, final Map fieldTypeOverrides, final InputStream in, final ComponentLog logger) + throws MalformedRecordException, IOException { + super(in, logger); + + this.jsonPaths = jsonPaths; + this.fieldTypeOverrides = fieldTypeOverrides; + this.in = in; + } + + @Override + public void close() throws IOException { + in.close(); + } + + @Override + public RecordSchema getSchema() { + if (schema != null) { + return schema; + } + + final Optional firstNodeOption = getFirstJsonNode(); + + final List recordFields = new ArrayList<>(); + if (firstNodeOption.isPresent()) { + final DocumentContext ctx = JsonPath.using(STRICT_PROVIDER_CONFIGURATION).parse(firstNodeOption.get().toString()); + for (final Map.Entry entry : jsonPaths.entrySet()) { + final String fieldName = PropertyNameUtil.getFieldName(entry.getKey()); + final JsonPath jsonPath = entry.getValue(); + + final DataType dataType; + final DataType dataTypeOverride = fieldTypeOverrides.get(fieldName); + if (dataTypeOverride == null) { + Object value; + try { + value = ctx.read(jsonPath); + } catch (final PathNotFoundException pnfe) { + value = null; + } + + if (value == null) { + dataType = RecordFieldType.STRING.getDataType(); + } else { + dataType = DataTypeUtils.inferDataType(value); + } + } else { + dataType = dataTypeOverride; + } + + recordFields.add(new RecordField(fieldName, dataType)); + } + } + + // If there are any overridden field types that we didn't find, add as the last fields. + final Set knownFieldNames = recordFields.stream() + .map(f -> f.getFieldName()) + .collect(Collectors.toSet()); + + for (final Map.Entry entry : fieldTypeOverrides.entrySet()) { + if (!knownFieldNames.contains(entry.getKey())) { + recordFields.add(new RecordField(entry.getKey(), entry.getValue())); + } + } + + schema = new SimpleRecordSchema(recordFields); + return schema; + } + + + @Override + @SuppressWarnings("unchecked") + protected Record convertJsonNodeToRecord(final JsonNode jsonNode, final RecordSchema schema) throws IOException { + if (jsonNode == null) { + return null; + } + + final DocumentContext ctx = JsonPath.using(STRICT_PROVIDER_CONFIGURATION).parse(jsonNode.toString()); + final Map values = new HashMap<>(schema.getFieldCount()); + + for (final Map.Entry entry : jsonPaths.entrySet()) { + final JsonPath jsonPath = entry.getValue(); + + Object value; + try { + value = ctx.read(jsonPath); + } catch (final PathNotFoundException pnfe) { + value = null; + } + + final String fieldName = entry.getKey(); + if (value != null) { + final DataType determinedType = DataTypeUtils.inferDataType(value); + final DataType desiredType = schema.getDataType(fieldName).orElse(null); + + if (value instanceof List) { + value = ((List) value).toArray(); + } else if (value instanceof Map && desiredType.getFieldType() == RecordFieldType.RECORD) { + value = convert(desiredType, value); + } else if (desiredType != null && !determinedType.equals(desiredType) && shouldConvert(value, determinedType.getFieldType())) { + value = convert(desiredType, value); + } + } + + values.put(fieldName, value); + } + + return new MapRecord(schema, values); + } + + private boolean shouldConvert(final Object value, final RecordFieldType determinedType) { + return determinedType != null + && determinedType != RecordFieldType.ARRAY; + } + + + protected Object convert(final DataType dataType, final Object value) { + if (dataType.getFieldType() == RecordFieldType.RECORD && dataType.getChildRecordSchema().isPresent() && value instanceof Map) { + @SuppressWarnings("unchecked") + final Map map = (Map) value; + return new MapRecord(dataType.getChildRecordSchema().get(), map); + } else { + return convertString(dataType, value.toString()); + } + } + + /** + * Coerces the given string into the provided data type, if possible + * + * @param dataType the desired type + * @param string the string representation of the value + * @return an Object representing the same value as the given string but in the requested data type + */ + protected Object convertString(final DataType dataType, final String string) { + if (dataType == null) { + return string; + } + + switch (dataType.getFieldType()) { + case BOOLEAN: + if (string.length() == 0) { + return null; + } + return Boolean.parseBoolean(string); + case BYTE: + if (string.length() == 0) { + return null; + } + return Byte.parseByte(string); + case SHORT: + if (string.length() == 0) { + return null; + } + return Short.parseShort(string); + case INT: + if (string.length() == 0) { + return null; + } + return Integer.parseInt(string); + case LONG: + if (string.length() == 0) { + return null; + } + return Long.parseLong(string); + case FLOAT: + if (string.length() == 0) { + return null; + } + return Float.parseFloat(string); + case DOUBLE: + if (string.length() == 0) { + return null; + } + return Double.parseDouble(string); + case DATE: + if (string.length() == 0) { + return null; + } + try { + final DateFormat format = new SimpleDateFormat(TIME_FORMAT_DATE); + format.setTimeZone(gmt); + Date date = format.parse(string); + return new java.sql.Date(date.getTime()); + } catch (ParseException e) { + return null; + } + case TIME: + if (string.length() == 0) { + return null; + } + try { + final DateFormat format = new SimpleDateFormat(TIME_FORMAT_TIME); + format.setTimeZone(gmt); + Date date = format.parse(string); + return new java.sql.Time(date.getTime()); + } catch (ParseException e) { + return null; + } + case TIMESTAMP: + if (string.length() == 0) { + return null; + } + try { + final DateFormat format = new SimpleDateFormat(TIME_FORMAT_TIMESTAMP); + format.setTimeZone(gmt); + Date date = format.parse(string); + return new java.sql.Timestamp(date.getTime()); + } catch (ParseException e) { + return null; + } + case STRING: + default: + return string; + } + } +} \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/JsonPathValidator.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/JsonPathValidator.java new file mode 100644 index 000000000..626f56c48 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/JsonPathValidator.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.json; + +import org.apache.nifi.components.ValidationContext; +import org.apache.nifi.components.ValidationResult; +import org.apache.nifi.components.Validator; + +import com.jayway.jsonpath.JsonPath; + +public class JsonPathValidator implements Validator { + + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + if (PropertyNameUtil.hasFieldType(subject) && !PropertyNameUtil.isFieldTypeValid(subject)) { + final String fieldType = PropertyNameUtil.getFieldTypeName(subject).get(); + + return new ValidationResult.Builder() + .subject(subject) + .input(input) + .valid(false) + .explanation("Invalid field type. If property name contains a colon (:) it must use syntax of " + + ": but the specified field type ('" + fieldType + "') is not a valid field type") + .build(); + } + + try { + JsonPath.compile(input); + } catch (final Exception e) { + return new ValidationResult.Builder() + .subject(subject) + .input(input) + .valid(false) + .explanation("Invalid JSON Path Expression: " + e.getMessage()) + .build(); + } + + return new ValidationResult.Builder() + .subject(subject) + .input(input) + .valid(true) + .build(); + } + +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/JsonRecordSetWriter.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/JsonRecordSetWriter.java new file mode 100644 index 000000000..dc75a5168 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/JsonRecordSetWriter.java @@ -0,0 +1,66 @@ +/* + * 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.json; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.annotation.lifecycle.OnEnabled; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.controller.ConfigurationContext; +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.serialization.AbstractRecordSetWriter; +import org.apache.nifi.serialization.RecordSetWriter; +import org.apache.nifi.serialization.RecordSetWriterFactory; + +@Tags({"json", "resultset", "writer", "serialize", "record", "row"}) +@CapabilityDescription("Writes the results of a Database ResultSet as a JSON Array. Even if the ResultSet " + + "consists of a single row, it will be written as an array with a single element.") +public class JsonRecordSetWriter extends AbstractRecordSetWriter implements RecordSetWriterFactory { + + static final PropertyDescriptor PRETTY_PRINT_JSON = new PropertyDescriptor.Builder() + .name("Pretty Print JSON") + .description("Specifies whether or not the JSON should be pretty printed") + .expressionLanguageSupported(false) + .allowableValues("true", "false") + .defaultValue("false") + .required(true) + .build(); + + private boolean prettyPrint; + + @Override + protected List getSupportedPropertyDescriptors() { + final List properties = new ArrayList<>(super.getSupportedPropertyDescriptors()); + properties.add(PRETTY_PRINT_JSON); + return properties; + } + + @OnEnabled + public void onEnabled(final ConfigurationContext context) { + prettyPrint = context.getProperty(PRETTY_PRINT_JSON).asBoolean(); + } + + @Override + public RecordSetWriter createWriter(final ComponentLog logger) { + return new WriteJsonResult(logger, prettyPrint, getDateFormat(), getTimeFormat(), getTimestampFormat()); + } + +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/JsonTreeReader.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/JsonTreeReader.java new file mode 100644 index 000000000..2d7072a76 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/JsonTreeReader.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.json; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.nifi.annotation.behavior.DynamicProperty; +import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.SeeAlso; +import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.serialization.MalformedRecordException; +import org.apache.nifi.serialization.RecordReader; +import org.apache.nifi.serialization.RowRecordReaderFactory; +import org.apache.nifi.serialization.UserTypeOverrideRowReader; + +@Tags({"json", "tree", "record", "reader", "parser"}) +@CapabilityDescription("Parses JSON into individual Record objects. The Record that is produced will contain all top-level " + + "elements of the corresponding JSON Object. If the JSON has nested arrays, those values will be represented as an Object array for that field. " + + "Nested JSON objects will be represented as a Map. " + + "The root JSON element can be either a single element or an array of JSON elements, and each " + + "element in that array will be treated as a separate record. If any of the elements has a nested array or a nested " + + "element, they will be returned as OBJECT or ARRAY types (respectively), not flattened out into individual fields. " + + "The schema for the record is determined by the first JSON element in the array, if the incoming FlowFile is a JSON array. " + + "This means that if a field does not exist in the first JSON object, then it will be skipped in all subsequent JSON objects. " + + "The data type of a field can be overridden by adding a property to " + + "the controller service where the name of the property matches the JSON field name and the value of the property is " + + "the data type to use. If that field does not exist in a JSON element, the field will be assumed to be null. " + + "See the Usage of the Controller Service for more information.") +@SeeAlso(JsonPathReader.class) +@DynamicProperty(name = "", value = "", + description = "User-defined properties are used to indicate that the values of a specific field should be interpreted as a " + + "user-defined data type (e.g., int, double, float, date, etc.)", supportsExpressionLanguage = false) +public class JsonTreeReader extends UserTypeOverrideRowReader implements RowRecordReaderFactory { + + @Override + public RecordReader createRecordReader(final InputStream in, final ComponentLog logger) throws IOException, MalformedRecordException { + return new JsonTreeRowRecordReader(in, logger, getFieldTypeOverrides()); + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/JsonTreeRowRecordReader.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/JsonTreeRowRecordReader.java new file mode 100644 index 000000000..4a2d212af --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/JsonTreeRowRecordReader.java @@ -0,0 +1,115 @@ +/* + * 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.json; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.serialization.MalformedRecordException; +import org.apache.nifi.serialization.SimpleRecordSchema; +import org.apache.nifi.serialization.record.DataType; +import org.apache.nifi.serialization.record.MapRecord; +import org.apache.nifi.serialization.record.Record; +import org.apache.nifi.serialization.record.RecordField; +import org.apache.nifi.serialization.record.RecordSchema; +import org.codehaus.jackson.JsonNode; + + +public class JsonTreeRowRecordReader extends AbstractJsonRowRecordReader { + private final Map fieldTypeOverrides; + private RecordSchema schema; + + public JsonTreeRowRecordReader(final InputStream in, final ComponentLog logger, final Map fieldTypeOverrides) throws IOException, MalformedRecordException { + super(in, logger); + this.fieldTypeOverrides = fieldTypeOverrides; + } + + @Override + protected Record convertJsonNodeToRecord(final JsonNode jsonNode, final RecordSchema schema) throws IOException, MalformedRecordException { + if (jsonNode == null) { + return null; + } + + final Map values = new HashMap<>(schema.getFieldCount()); + for (int i = 0; i < schema.getFieldCount(); i++) { + final RecordField field = schema.getField(i); + final String fieldName = field.getFieldName(); + final JsonNode fieldNode = jsonNode.get(fieldName); + + final DataType desiredType = field.getDataType(); + final Object value = convertField(fieldNode, fieldName, desiredType); + values.put(fieldName, value); + } + + return new MapRecord(schema, values); + } + + + @Override + public RecordSchema getSchema() { + if (schema != null) { + return schema; + } + + final List recordFields = new ArrayList<>(); + final Optional firstNodeOption = getFirstJsonNode(); + + if (firstNodeOption.isPresent()) { + final Iterator> itr = firstNodeOption.get().getFields(); + while (itr.hasNext()) { + final Map.Entry entry = itr.next(); + final String elementName = entry.getKey(); + final JsonNode node = entry.getValue(); + + DataType dataType; + final DataType overriddenDataType = fieldTypeOverrides.get(elementName); + if (overriddenDataType == null) { + dataType = determineFieldType(node); + } else { + dataType = overriddenDataType; + } + + recordFields.add(new RecordField(elementName, dataType)); + } + } + + // If there are any overridden field types that we didn't find, add as the last fields. + final Set knownFieldNames = recordFields.stream() + .map(f -> f.getFieldName()) + .collect(Collectors.toSet()); + + for (final Map.Entry entry : fieldTypeOverrides.entrySet()) { + if (!knownFieldNames.contains(entry.getKey())) { + recordFields.add(new RecordField(entry.getKey(), entry.getValue())); + } + } + + schema = new SimpleRecordSchema(recordFields); + return schema; + } + +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/PropertyNameUtil.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/PropertyNameUtil.java new file mode 100644 index 000000000..3b7dcf96d --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/PropertyNameUtil.java @@ -0,0 +1,88 @@ +/* + * 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.json; + +import java.util.Optional; + +import org.apache.nifi.serialization.record.DataType; +import org.apache.nifi.serialization.record.RecordFieldType; + +public class PropertyNameUtil { + + public static String getFieldName(final String propertyName) { + final int colonIndex = propertyName.indexOf(":"); + if (colonIndex > -1 && colonIndex < propertyName.length() - 1) { + return propertyName.substring(0, colonIndex); + } + + return propertyName; + } + + public static boolean hasFieldType(final String propertyName) { + final int colonIndex = propertyName.indexOf(":"); + return (colonIndex > -1 && colonIndex < propertyName.length() - 1); + } + + public static Optional getFieldTypeName(final String propertyName) { + if (hasFieldType(propertyName)) { + final String[] splits = propertyName.split("\\:"); + if (splits.length > 1) { + return Optional.of(splits[1]); + } + return Optional.empty(); + } + + return Optional.empty(); + } + + public static Optional getFieldFormat(final String propertyName) { + final String[] splits = propertyName.split("\\:"); + if (splits.length != 3) { + return Optional.empty(); + } + + return Optional.of(splits[2]); + } + + public static boolean isFieldTypeValid(final String propertyName) { + final Optional fieldType = getFieldTypeName(propertyName); + if (!fieldType.isPresent()) { + return false; + } + + final String typeName = fieldType.get(); + final RecordFieldType recordFieldType = RecordFieldType.of(typeName); + return recordFieldType != null; + } + + public static Optional getDataType(final String propertyName) { + if (isFieldTypeValid(propertyName)) { + final String typeName = getFieldTypeName(propertyName).get(); + final RecordFieldType fieldType = RecordFieldType.of(typeName); + + final Optional format = getFieldFormat(propertyName); + if (format.isPresent()) { + return Optional.of(fieldType.getDataType(format.get())); + } else { + return Optional.of(fieldType.getDataType()); + } + } + + return Optional.empty(); + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/WriteJsonResult.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/WriteJsonResult.java new file mode 100644 index 000000000..cf72b199c --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/WriteJsonResult.java @@ -0,0 +1,309 @@ +/* + * 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.json; + +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigInteger; +import java.sql.Array; +import java.sql.SQLException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.serialization.DataTypeUtils; +import org.apache.nifi.serialization.RecordSetWriter; +import org.apache.nifi.serialization.WriteResult; +import org.apache.nifi.serialization.record.DataType; +import org.apache.nifi.serialization.record.Record; +import org.apache.nifi.serialization.record.RecordFieldType; +import org.apache.nifi.serialization.record.RecordSchema; +import org.apache.nifi.serialization.record.RecordSet; +import org.apache.nifi.stream.io.NonCloseableOutputStream; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.JsonGenerator; + +public class WriteJsonResult implements RecordSetWriter { + private final boolean prettyPrint; + + private final ComponentLog logger; + private final JsonFactory factory = new JsonFactory(); + private final DateFormat dateFormat; + private final DateFormat timeFormat; + private final DateFormat timestampFormat; + + public WriteJsonResult(final ComponentLog logger, final boolean prettyPrint, final String dateFormat, final String timeFormat, final String timestampFormat) { + this.prettyPrint = prettyPrint; + this.dateFormat = new SimpleDateFormat(dateFormat); + this.timeFormat = new SimpleDateFormat(timeFormat); + this.timestampFormat = new SimpleDateFormat(timestampFormat); + this.logger = logger; + } + + @Override + public WriteResult write(final RecordSet rs, final OutputStream rawOut) throws IOException { + int count = 0; + + try (final JsonGenerator generator = factory.createJsonGenerator(new NonCloseableOutputStream(rawOut))) { + if (prettyPrint) { + generator.useDefaultPrettyPrinter(); + } + + generator.writeStartArray(); + + Record record; + while ((record = rs.next()) != null) { + count++; + writeRecord(record, generator, g -> g.writeStartObject(), g -> g.writeEndObject()); + } + + generator.writeEndArray(); + } catch (final SQLException e) { + throw new IOException("Failed to serialize Result Set to stream", e); + } + + return WriteResult.of(count, Collections.emptyMap()); + } + + @Override + public WriteResult write(final Record record, final OutputStream rawOut) throws IOException { + try (final JsonGenerator generator = factory.createJsonGenerator(new NonCloseableOutputStream(rawOut))) { + if (prettyPrint) { + generator.useDefaultPrettyPrinter(); + } + + writeRecord(record, generator, g -> g.writeStartObject(), g -> g.writeEndObject()); + } catch (final SQLException e) { + throw new IOException("Failed to write records to stream", e); + } + + return WriteResult.of(1, Collections.emptyMap()); + } + + private void writeRecord(final Record record, final JsonGenerator generator, final GeneratorTask startTask, final GeneratorTask endTask) + throws JsonGenerationException, IOException, SQLException { + + try { + final RecordSchema schema = record.getSchema(); + startTask.apply(generator); + for (int i = 0; i < schema.getFieldCount(); i++) { + final String fieldName = schema.getField(i).getFieldName(); + final Object value = record.getValue(fieldName); + if (value == null) { + generator.writeNullField(fieldName); + continue; + } + + generator.writeFieldName(fieldName); + final DataType dataType = schema.getDataType(fieldName).get(); + + writeValue(generator, value, dataType, i < schema.getFieldCount() - 1); + } + + endTask.apply(generator); + } catch (final Exception e) { + logger.error("Failed to write {} with schema {} as a JSON Object due to {}", new Object[] {record, record.getSchema(), e.toString(), e}); + throw e; + } + } + + private String createDate(final Object value, final DateFormat format) { + if (value == null) { + return null; + } + + if (value instanceof Date) { + return format.format((Date) value); + } + if (value instanceof java.sql.Date) { + return format.format(new Date(((java.sql.Date) value).getTime())); + } + if (value instanceof java.sql.Time) { + return format.format(new Date(((java.sql.Time) value).getTime())); + } + if (value instanceof java.sql.Timestamp) { + return format.format(new Date(((java.sql.Timestamp) value).getTime())); + } + + return null; + } + + private void writeValue(final JsonGenerator generator, final Object value, final DataType dataType, final boolean moreCols) + throws JsonGenerationException, IOException, SQLException { + if (value == null) { + generator.writeNull(); + return; + } + + final DataType resolvedDataType; + if (dataType.getFieldType() == RecordFieldType.CHOICE) { + resolvedDataType = DataTypeUtils.inferDataType(value); + } else { + resolvedDataType = dataType; + } + + switch (resolvedDataType.getFieldType()) { + case DATE: + generator.writeString(createDate(value, dateFormat)); + break; + case TIME: + generator.writeString(createDate(value, timeFormat)); + break; + case TIMESTAMP: + generator.writeString(createDate(value, timestampFormat)); + break; + case DOUBLE: + generator.writeNumber(DataTypeUtils.toDouble(value, 0D)); + break; + case FLOAT: + generator.writeNumber(DataTypeUtils.toFloat(value, 0F)); + break; + case LONG: + generator.writeNumber(DataTypeUtils.toLong(value, 0L)); + break; + case INT: + case BYTE: + case SHORT: + generator.writeNumber(DataTypeUtils.toInteger(value, 0)); + break; + case CHAR: + case STRING: + generator.writeString(value.toString()); + break; + case BIGINT: + if (value instanceof Long) { + generator.writeNumber(((Long) value).longValue()); + } else { + generator.writeNumber((BigInteger) value); + } + break; + case BOOLEAN: + final String stringValue = value.toString(); + if ("true".equalsIgnoreCase(stringValue)) { + generator.writeBoolean(true); + } else if ("false".equalsIgnoreCase(stringValue)) { + generator.writeBoolean(false); + } else { + generator.writeString(stringValue); + } + break; + case RECORD: { + final Record record = (Record) value; + writeRecord(record, generator, gen -> gen.writeStartObject(), gen -> gen.writeEndObject()); + break; + } + case ARRAY: + default: + if ("null".equals(value.toString())) { + generator.writeNull(); + } else if (value instanceof Map) { + final Map map = (Map) value; + generator.writeStartObject(); + + int i = 0; + for (final Map.Entry entry : map.entrySet()) { + generator.writeFieldName(entry.getKey().toString()); + final boolean moreEntries = ++i < map.size(); + writeValue(generator, entry.getValue(), getColType(entry.getValue()), moreEntries); + } + generator.writeEndObject(); + } else if (value instanceof List) { + final List list = (List) value; + writeArray(list.toArray(), generator); + } else if (value instanceof Array) { + final Array array = (Array) value; + final Object[] values = (Object[]) array.getArray(); + writeArray(values, generator); + } else if (value instanceof Object[]) { + final Object[] values = (Object[]) value; + writeArray(values, generator); + } else { + generator.writeString(value.toString()); + } + break; + } + } + + private void writeArray(final Object[] values, final JsonGenerator generator) throws JsonGenerationException, IOException, SQLException { + generator.writeStartArray(); + for (int i = 0; i < values.length; i++) { + final boolean moreEntries = i < values.length - 1; + final Object element = values[i]; + writeValue(generator, element, getColType(element), moreEntries); + } + generator.writeEndArray(); + } + + private DataType getColType(final Object value) { + if (value instanceof String) { + return RecordFieldType.STRING.getDataType(); + } + if (value instanceof Double) { + return RecordFieldType.DOUBLE.getDataType(); + } + if (value instanceof Float) { + return RecordFieldType.FLOAT.getDataType(); + } + if (value instanceof Integer) { + return RecordFieldType.INT.getDataType(); + } + if (value instanceof Long) { + return RecordFieldType.LONG.getDataType(); + } + if (value instanceof BigInteger) { + return RecordFieldType.BIGINT.getDataType(); + } + if (value instanceof Boolean) { + return RecordFieldType.BOOLEAN.getDataType(); + } + if (value instanceof Byte || value instanceof Short) { + return RecordFieldType.INT.getDataType(); + } + if (value instanceof Character) { + return RecordFieldType.STRING.getDataType(); + } + if (value instanceof java.util.Date || value instanceof java.sql.Date) { + return RecordFieldType.DATE.getDataType(); + } + if (value instanceof java.sql.Time) { + return RecordFieldType.TIME.getDataType(); + } + if (value instanceof java.sql.Timestamp) { + return RecordFieldType.TIMESTAMP.getDataType(); + } + if (value instanceof Object[] || value instanceof List || value instanceof Array) { + return RecordFieldType.ARRAY.getDataType(); + } + + return RecordFieldType.RECORD.getDataType(); + } + + @Override + public String getMimeType() { + return "application/json"; + } + + private static interface GeneratorTask { + void apply(JsonGenerator generator) throws JsonGenerationException, IOException; + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/serialization/AbstractRecordSetWriter.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/serialization/AbstractRecordSetWriter.java new file mode 100644 index 000000000..b58a22eac --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/serialization/AbstractRecordSetWriter.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.serialization; + +import java.util.Arrays; +import java.util.List; + +import org.apache.nifi.annotation.lifecycle.OnEnabled; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.controller.AbstractControllerService; +import org.apache.nifi.controller.ConfigurationContext; +import org.apache.nifi.serialization.record.RecordFieldType; + +public abstract class AbstractRecordSetWriter extends AbstractControllerService { + static final PropertyDescriptor DATE_FORMAT = new PropertyDescriptor.Builder() + .name("Date Format") + .description("Specifies the format to use when writing out Date fields") + .expressionLanguageSupported(false) + .defaultValue(RecordFieldType.DATE.getDefaultFormat()) + .addValidator(new SimpleDateFormatValidator()) + .required(true) + .build(); + + static final PropertyDescriptor TIME_FORMAT = new PropertyDescriptor.Builder() + .name("Time Format") + .description("Specifies the format to use when writing out Time fields") + .expressionLanguageSupported(false) + .defaultValue(RecordFieldType.TIME.getDefaultFormat()) + .addValidator(new SimpleDateFormatValidator()) + .required(true) + .build(); + + static final PropertyDescriptor TIMESTAMP_FORMAT = new PropertyDescriptor.Builder() + .name("Timestamp Format") + .description("Specifies the format to use when writing out Timestamp (date/time) fields") + .expressionLanguageSupported(false) + .defaultValue(RecordFieldType.TIMESTAMP.getDefaultFormat()) + .addValidator(new SimpleDateFormatValidator()) + .required(true) + .build(); + + private volatile String dateFormat; + private volatile String timeFormat; + private volatile String timestampFormat; + + @Override + protected List getSupportedPropertyDescriptors() { + return Arrays.asList(DATE_FORMAT, TIME_FORMAT, TIMESTAMP_FORMAT); + } + + @OnEnabled + public void captureValues(final ConfigurationContext context) { + this.dateFormat = context.getProperty(DATE_FORMAT).getValue(); + this.timeFormat = context.getProperty(TIME_FORMAT).getValue(); + this.timestampFormat = context.getProperty(TIMESTAMP_FORMAT).getValue(); + } + + protected String getDateFormat() { + return dateFormat; + } + + protected String getTimeFormat() { + return timeFormat; + } + + protected String getTimestampFormat() { + return timestampFormat; + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/serialization/DataTypeUtils.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/serialization/DataTypeUtils.java new file mode 100644 index 000000000..de207f498 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/serialization/DataTypeUtils.java @@ -0,0 +1,165 @@ +/* + * 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.serialization; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.apache.nifi.serialization.record.DataType; +import org.apache.nifi.serialization.record.RecordField; +import org.apache.nifi.serialization.record.RecordFieldType; +import org.apache.nifi.serialization.record.RecordSchema; + +public class DataTypeUtils { + + public static Double toDouble(final Object value, final Double defaultValue) { + if (value == null) { + return null; + } + + if (value instanceof Number) { + return ((Number) value).doubleValue(); + } + + if (value instanceof String) { + return Double.parseDouble((String) value); + } + + return defaultValue; + } + + public static Float toFloat(final Object value, final Float defaultValue) { + if (value == null) { + return null; + } + + if (value instanceof Number) { + return ((Number) value).floatValue(); + } + + if (value instanceof String) { + return Float.parseFloat((String) value); + } + + return defaultValue; + } + + public static Long toLong(final Object value, final Long defaultValue) { + if (value == null) { + return null; + } + + if (value instanceof Number) { + return ((Number) value).longValue(); + } + + if (value instanceof String) { + return Long.parseLong((String) value); + } + + return defaultValue; + } + + + + public static Integer toInteger(final Object value, final Integer defaultValue) { + if (value == null) { + return null; + } + + if (value instanceof Number) { + return ((Number) value).intValue(); + } + + if (value instanceof String) { + return Integer.parseInt((String) value); + } + + return defaultValue; + } + + + /** + * Deduces the type of RecordFieldType that should be used for a value of the given type, + * or returns null if the value is null + * + * @param value the value whose type should be deduced + * @return the type of RecordFieldType that should be used for a value of the given type, + * or null if the value is null + */ + public static DataType inferDataType(final Object value) { + if (value == null) { + return null; + } + + if (value instanceof String) { + return RecordFieldType.STRING.getDataType(); + } + if (value instanceof Long) { + return RecordFieldType.LONG.getDataType(); + } + if (value instanceof Integer) { + return RecordFieldType.INT.getDataType(); + } + if (value instanceof Double) { + return RecordFieldType.DOUBLE.getDataType(); + } + if (value instanceof Float) { + return RecordFieldType.FLOAT.getDataType(); + } + if (value instanceof Boolean) { + return RecordFieldType.BOOLEAN.getDataType(); + } + if (value instanceof Byte) { + return RecordFieldType.BYTE.getDataType(); + } + if (value instanceof Character) { + return RecordFieldType.CHAR.getDataType(); + } + if (value instanceof Short) { + return RecordFieldType.SHORT.getDataType(); + } + if (value instanceof Date) { + return RecordFieldType.DATE.getDataType(); + } + if (value instanceof Object[] || value instanceof List) { + return RecordFieldType.ARRAY.getDataType(); + } + if (value instanceof Map) { + @SuppressWarnings("unchecked") + final Map map = (Map) value; + final RecordSchema childSchema = determineSchema(map); + return RecordFieldType.RECORD.getDataType(childSchema); + } + + return RecordFieldType.RECORD.getDataType(); + } + + public static RecordSchema determineSchema(final Map valueMap) { + final List fields = new ArrayList<>(valueMap.size()); + for (final Map.Entry entry : valueMap.entrySet()) { + final DataType valueType = inferDataType(entry.getValue()); + final String fieldName = entry.getKey(); + final RecordField field = new RecordField(fieldName, valueType); + fields.add(field); + } + return new SimpleRecordSchema(fields); + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/serialization/SimpleDateFormatValidator.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/serialization/SimpleDateFormatValidator.java new file mode 100644 index 000000000..f25749bfe --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/serialization/SimpleDateFormatValidator.java @@ -0,0 +1,48 @@ +/* + * 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.serialization; + +import java.text.SimpleDateFormat; + +import org.apache.nifi.components.ValidationContext; +import org.apache.nifi.components.ValidationResult; +import org.apache.nifi.components.Validator; + +public class SimpleDateFormatValidator implements Validator { + + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + try { + new SimpleDateFormat(input); + } catch (final Exception e) { + return new ValidationResult.Builder() + .input(input) + .subject(subject) + .valid(false) + .explanation("Invalid Date format: " + e.getMessage()) + .build(); + } + + return new ValidationResult.Builder() + .input(input) + .subject(subject) + .valid(true) + .build(); + } + +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/serialization/UserTypeOverrideRowReader.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/serialization/UserTypeOverrideRowReader.java new file mode 100644 index 000000000..be0b8add9 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/serialization/UserTypeOverrideRowReader.java @@ -0,0 +1,78 @@ +/* + * 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.serialization; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.nifi.annotation.lifecycle.OnEnabled; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.controller.AbstractControllerService; +import org.apache.nifi.controller.ConfigurationContext; +import org.apache.nifi.serialization.DataTypeValidator; +import org.apache.nifi.serialization.record.DataType; +import org.apache.nifi.serialization.record.RecordFieldType; + +public abstract class UserTypeOverrideRowReader extends AbstractControllerService { + private volatile Map fieldTypeOverrides; + + @Override + protected PropertyDescriptor getSupportedDynamicPropertyDescriptor(final String propertyDescriptorName) { + return new PropertyDescriptor.Builder() + .name(propertyDescriptorName) + .dynamic(true) + .addValidator(new DataTypeValidator()) + .build(); + } + + @OnEnabled + public void createFieldTypeOverrides(final ConfigurationContext context) { + final Map overrides = new HashMap<>(context.getProperties().size()); + for (final Map.Entry entry : context.getProperties().entrySet()) { + if (!entry.getKey().isDynamic()) { + continue; + } + + final String fieldName = entry.getKey().getName(); + final String dataTypeName = entry.getValue(); + if (dataTypeName == null) { + continue; + } + + final DataType dataType; + final String[] splits = dataTypeName.split("\\:"); + if (splits.length == 2) { + final RecordFieldType fieldType = RecordFieldType.of(splits[0]); + final String format = splits[1]; + dataType = fieldType.getDataType(format); + } else { + final RecordFieldType fieldType = RecordFieldType.of(dataTypeName); + dataType = fieldType.getDataType(); + } + + overrides.put(fieldName, dataType); + } + + this.fieldTypeOverrides = Collections.unmodifiableMap(overrides); + } + + protected Map getFieldTypeOverrides() { + return fieldTypeOverrides; + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/text/FreeFormTextRecordSetWriter.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/text/FreeFormTextRecordSetWriter.java new file mode 100644 index 000000000..07da00ebd --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/text/FreeFormTextRecordSetWriter.java @@ -0,0 +1,80 @@ +/* + * 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.text; + +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.annotation.lifecycle.OnEnabled; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.PropertyValue; +import org.apache.nifi.controller.AbstractControllerService; +import org.apache.nifi.controller.ConfigurationContext; +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.processor.util.StandardValidators; +import org.apache.nifi.serialization.RecordSetWriter; +import org.apache.nifi.serialization.RecordSetWriterFactory; + +@Tags({"text", "freeform", "expression", "language", "el", "resultset", "writer", "serialize"}) +@CapabilityDescription("Writes the contents of a Database ResultSet as free-form text. The configured " + + "text is able to make use of the Expression Language to reference each of the columns that are available " + + "in the ResultSet. Each record in the ResultSet will be separated by a single newline character.") +public class FreeFormTextRecordSetWriter extends AbstractControllerService implements RecordSetWriterFactory { + static final PropertyDescriptor TEXT = new PropertyDescriptor.Builder() + .name("Text") + .description("The text to use when writing the results. This property will evaluate the Expression Language using any of the columns available to the Result Set. For example, if the " + + "following SQL Query is used: \"SELECT Name, COUNT(*) AS Count\" then the Expression can reference \"Name\" and \"Count\", such as \"${Name:toUpper()} ${Count:minus(1)}\"") + .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .expressionLanguageSupported(true) + .required(true) + .build(); + static final PropertyDescriptor CHARACTER_SET = new PropertyDescriptor.Builder() + .name("Character Set") + .description("The Character set to use when writing the data to the FlowFile") + .addValidator(StandardValidators.CHARACTER_SET_VALIDATOR) + .defaultValue("UTF-8") + .expressionLanguageSupported(false) + .required(true) + .build(); + + private volatile PropertyValue textValue; + private volatile Charset characterSet; + + @Override + protected List getSupportedPropertyDescriptors() { + final List properties = new ArrayList<>(); + properties.add(TEXT); + properties.add(CHARACTER_SET); + return properties; + } + + @OnEnabled + public void onEnabled(final ConfigurationContext context) { + textValue = context.getProperty(TEXT); + characterSet = Charset.forName(context.getProperty(CHARACTER_SET).getValue()); + } + + @Override + public RecordSetWriter createWriter(final ComponentLog logger) { + return new FreeFormTextWriter(textValue, characterSet); + } + +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/text/FreeFormTextWriter.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/text/FreeFormTextWriter.java new file mode 100644 index 000000000..781f41fc7 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/text/FreeFormTextWriter.java @@ -0,0 +1,99 @@ +/* + * 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.text; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.nifi.components.PropertyValue; +import org.apache.nifi.processor.exception.ProcessException; +import org.apache.nifi.serialization.RecordSetWriter; +import org.apache.nifi.serialization.WriteResult; +import org.apache.nifi.serialization.record.Record; +import org.apache.nifi.serialization.record.RecordSchema; +import org.apache.nifi.serialization.record.RecordSet; + +public class FreeFormTextWriter implements RecordSetWriter { + private static final byte NEW_LINE = (byte) '\n'; + private final PropertyValue propertyValue; + private final Charset charset; + + public FreeFormTextWriter(final PropertyValue textPropertyValue, final Charset characterSet) { + propertyValue = textPropertyValue; + charset = characterSet; + } + + @Override + public WriteResult write(final RecordSet recordSet, final OutputStream out) throws IOException { + int count = 0; + + try { + final RecordSchema schema = recordSet.getSchema(); + final String[] colNames = getColumnNames(schema); + + Record record; + while ((record = recordSet.next()) != null) { + count++; + write(record, out, colNames); + } + } catch (final Exception e) { + throw new ProcessException(e); + } + + return WriteResult.of(count, Collections.emptyMap()); + } + + private String[] getColumnNames(final RecordSchema schema) { + final int numCols = schema.getFieldCount(); + final String[] columnNames = new String[numCols]; + for (int i = 0; i < numCols; i++) { + columnNames[i] = schema.getField(i).getFieldName(); + } + + return columnNames; + } + + @Override + public WriteResult write(final Record record, final OutputStream out) throws IOException { + write(record, out, getColumnNames(record.getSchema())); + return WriteResult.of(1, Collections.emptyMap()); + } + + private void write(final Record record, final OutputStream out, final String[] columnNames) throws IOException { + final int numCols = columnNames.length; + final Map values = new HashMap<>(numCols); + for (int i = 0; i < numCols; i++) { + final String columnName = columnNames[i]; + final String columnValue = record.getAsString(columnName); + values.put(columnName, columnValue); + } + + final String evaluated = propertyValue.evaluateAttributeExpressions(values).getValue(); + out.write(evaluated.getBytes(charset)); + out.write(NEW_LINE); + } + + @Override + public String getMimeType() { + return "text/plain"; + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService new file mode 100644 index 000000000..628dbe5c0 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService @@ -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. + +org.apache.nifi.avro.AvroReader +org.apache.nifi.avro.AvroRecordSetWriter + +org.apache.nifi.json.JsonTreeReader +org.apache.nifi.json.JsonPathReader +org.apache.nifi.json.JsonRecordSetWriter + +org.apache.nifi.csv.CSVReader +org.apache.nifi.csv.CSVRecordSetWriter + +org.apache.nifi.grok.GrokReader + +org.apache.nifi.text.FreeFormTextRecordSetWriter \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/resources/default-grok-patterns.txt b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/resources/default-grok-patterns.txt new file mode 100644 index 000000000..4b110e87f --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/resources/default-grok-patterns.txt @@ -0,0 +1,115 @@ +# 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. + + + +# Log Levels +LOGLEVEL ([Aa]lert|ALERT|[Tt]race|TRACE|[Dd]ebug|DEBUG|[Nn]otice|NOTICE|[Ii]nfo|INFO|[Ww]arn?(?:ing)?|WARN?(?:ING)?|[Ee]rr?(?:or)?|ERR?(?:OR)?|[Cc]rit?(?:ical)?|CRIT?(?:ICAL)?|[Ff]atal|FATAL|[Ss]evere|SEVERE|EMERG(?:ENCY)?|[Ee]merg(?:ency)?)|FINE|FINER|FINEST|CONFIG + +# Syslog Dates: Month Day HH:MM:SS +SYSLOGTIMESTAMP %{MONTH} +%{MONTHDAY} %{TIME} +PROG (?:[\w._/%-]+) +SYSLOGPROG %{PROG:program}(?:\[%{POSINT:pid}\])? +SYSLOGHOST %{IPORHOST} +SYSLOGFACILITY <%{NONNEGINT:facility}.%{NONNEGINT:priority}> +HTTPDATE %{MONTHDAY}/%{MONTH}/%{YEAR}:%{TIME} %{INT} + +# Months: January, Feb, 3, 03, 12, December +MONTH \b(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\b +MONTHNUM (?:0?[1-9]|1[0-2]) +MONTHNUM2 (?:0[1-9]|1[0-2]) +MONTHDAY (?:(?:0[1-9])|(?:[12][0-9])|(?:3[01])|[1-9]) + +# Days: Monday, Tue, Thu, etc... +DAY (?:Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:rsday)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?) + +# Years? +YEAR (?>\d\d){1,2} +HOUR (?:2[0123]|[01]?[0-9]) +MINUTE (?:[0-5][0-9]) +# '60' is a leap second in most time standards and thus is valid. +SECOND (?:(?:[0-5]?[0-9]|60)(?:[:.,][0-9]+)?) +TIME (?!<[0-9])%{HOUR}:%{MINUTE}(?::%{SECOND})(?![0-9]) + +# datestamp is YYYY/MM/DD-HH:MM:SS.UUUU (or something like it) +DATE_US_MONTH_DAY_YEAR %{MONTHNUM}[/-]%{MONTHDAY}[/-]%{YEAR} +DATE_US_YEAR_MONTH_DAY %{YEAR}[/-]%{MONTHNUM}[/-]%{MONTHDAY} +DATE_US %{DATE_US_MONTH_DAY_YEAR}|%{DATE_US_YEAR_MONTH_DAY} +DATE_EU %{MONTHDAY}[./-]%{MONTHNUM}[./-]%{YEAR} +ISO8601_TIMEZONE (?:Z|[+-]%{HOUR}(?::?%{MINUTE})) +ISO8601_SECOND (?:%{SECOND}|60) +TIMESTAMP_ISO8601 %{YEAR}-%{MONTHNUM}-%{MONTHDAY}[T ]%{HOUR}:?%{MINUTE}(?::?%{SECOND})?%{ISO8601_TIMEZONE}? +DATE %{DATE_US}|%{DATE_EU} +DATESTAMP %{DATE}[- ]%{TIME} +TZ (?:[PMCE][SD]T|UTC) +DATESTAMP_RFC822 %{DAY} %{MONTH} %{MONTHDAY} %{YEAR} %{TIME} %{TZ} +DATESTAMP_RFC2822 %{DAY}, %{MONTHDAY} %{MONTH} %{YEAR} %{TIME} %{ISO8601_TIMEZONE} +DATESTAMP_OTHER %{DAY} %{MONTH} %{MONTHDAY} %{TIME} %{TZ} %{YEAR} +DATESTAMP_EVENTLOG %{YEAR}%{MONTHNUM2}%{MONTHDAY}%{HOUR}%{MINUTE}%{SECOND} + + +POSINT \b(?:[1-9][0-9]*)\b +NONNEGINT \b(?:[0-9]+)\b +WORD \b\w+\b +NOTSPACE \S+ +SPACE \s* +DATA .*? +GREEDYDATA .* +QUOTEDSTRING (?>(?"(?>\\.|[^\\"]+)+"|""|(?>'(?>\\.|[^\\']+)+')|''|(?>`(?>\\.|[^\\`]+)+`)|``)) +UUID [A-Fa-f0-9]{8}-(?:[A-Fa-f0-9]{4}-){3}[A-Fa-f0-9]{12} + +USERNAME [a-zA-Z0-9._-]+ +USER %{USERNAME} +INT (?:[+-]?(?:[0-9]+)) +BASE10NUM (?[+-]?(?:(?:[0-9]+(?:\.[0-9]+)?)|(?:\.[0-9]+))) +NUMBER (?:%{BASE10NUM}) +BASE16NUM (?/(?>[\w_%!$@:.,-]+|\\.)*)+ +TTY (?:/dev/(pts|tty([pq])?)(\w+)?/?(?:[0-9]+)) +WINPATH (?>[A-Za-z]+:|\\)(?:\\[^\\?*]*)+ +URIPROTO [A-Za-z]+(\+[A-Za-z+]+)? +URIHOST %{IPORHOST}(?::%{POSINT:port})? +# uripath comes loosely from RFC1738, but mostly from what Firefox +# doesn't turn into %XX +URIPATH (?:/[A-Za-z0-9$.+!*'(){},~:;=@#%_\-]*)+ +#URIPARAM \?(?:[A-Za-z0-9]+(?:=(?:[^&]*))?(?:&(?:[A-Za-z0-9]+(?:=(?:[^&]*))?)?)*)? +URIPARAM \?[A-Za-z0-9$.+!*'|(){},~@#%&/=:;_?\-\[\]]* +URIPATHPARAM %{URIPATH}(?:%{URIPARAM})? +URI %{URIPROTO}://(?:%{USER}(?::[^@]*)?@)?(?:%{URIHOST})?(?:%{URIPATHPARAM})? + +# Shortcuts +QS %{QUOTEDSTRING} + +# Log formats +SYSLOGBASE %{SYSLOGTIMESTAMP:timestamp} (?:%{SYSLOGFACILITY} )?%{SYSLOGHOST:logsource} %{SYSLOGPROG}: +COMMONAPACHELOG %{IPORHOST:clientip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] "(?:%{WORD:verb} %{NOTSPACE:request}(?: HTTP/%{NUMBER:httpversion})?|%{DATA:rawrequest})" %{NUMBER:response} (?:%{NUMBER:bytes}|-) +COMBINEDAPACHELOG %{COMMONAPACHELOG} %{QS:referrer} %{QS:agent} \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/resources/docs/org.apache.nifi.csv.CSVReader/additionalDetails.html b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/resources/docs/org.apache.nifi.csv.CSVReader/additionalDetails.html new file mode 100644 index 000000000..e6dfd0c2d --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/resources/docs/org.apache.nifi.csv.CSVReader/additionalDetails.html @@ -0,0 +1,185 @@ + + + + + + CSVReader + + + + +

+ The CSVReader Controller Service, expects input in such a way that the first line of a FlowFile specifies the name of + each column in the data. Following the first line, the rest of the FlowFile is expected to be valid CSV data from which + to form appropriate Records. By default, the schema for a FlowFile is inferred by extracting the name of each column from + the first line of the CSV and assumes that all columns are of type string. Of course, we may want to treat some + columns as a data type other than string. This can be accomplished by adding a user-defined property where the + name of the property is the same as the name of a CSV column and the value of the property is the data type to use. +

+ +

+ When specifying a data type for a field, the following values are valid: +

+ +
    +
  • string
  • +
  • boolean
  • +
  • byte
  • +
  • char
  • +
  • short
  • +
  • int
  • +
  • bigint
  • +
  • long
  • +
  • float
  • +
  • double
  • +
  • date - A date with no time field. By default, the format used is yyyy-MM-dd. This can be overridden + by adding a colon (:) followed by the desired format. For example: date:MM/dd/yyyy. The format to use is + that of Java's SimpleDateFormat (see + SimpleDateFormat Patterns for more information). +
  • +
  • time - A time with no date field. By default, the format used is HH:mm:ss. This can be overridden + by adding a colon (:) followed by the desired format. For example: time:hh:mm:ss a. The format to use is + that of Java's SimpleDateFormat (see + SimpleDateFormat Patterns for more information). +
  • +
  • timestamp - A field that represents both a date and time. By default, the format used is + yyyy-MM-dd HH:mm:ss. This can be overridden by adding a colon (:) followed by the desired format. For example: + MM/dd/yyyy hh:mm:ss a. The format to use is + that of Java's SimpleDateFormat (see + SimpleDateFormat Patterns for more information).
  • +
  • object - This data type does not apply to CSV data.
  • +
  • array - This data type does not apply to CSV data.
  • +
+ +

+ As an example, consider a FlowFile whose contents consists of the following: +

+ + + id, name, balance, notes
+ 1, John, 48.23, "Our very
+first customer!"
+ 2, Jane, 1245.89,
+ 3, Frank Franklin, "48481.29",
+
+ +

+ Additionally, let's consider that this Controller Service is configured with the following user-defined properties: +

+ + + + + + + + + + + + +
Property NameProperty Value
balancefloat
+ +

+ In this case, the result will be that this FlowFile consists of 3 different records. The first record will contain the following values: +

+ + + + + + + + + + + + + + + + + + + + + + + + +
Field NameField Value
id1
nameJohn
balance48.23
notesOur very
first customer!
+ +

+ The second record will contain the following values: +

+ + + + + + + + + + + + + + + + + + + + + + + + +
Field NameField Value
id2
nameJane
balance1245.89
notes
+ +

+ The third record will contain the following values: +

+ + + + + + + + + + + + + + + + + + + + + + + + +
Field NameField Value
id3
nameFrank Franklin
balance48481.29
notes
+ + + + diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/resources/docs/org.apache.nifi.grok.GrokReader/additionalDetails.html b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/resources/docs/org.apache.nifi.grok.GrokReader/additionalDetails.html new file mode 100644 index 000000000..3a41f478e --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/resources/docs/org.apache.nifi.grok.GrokReader/additionalDetails.html @@ -0,0 +1,396 @@ + + + + + + GrokReader + + + + +

+ The GrokReader Controller Service, provides a means for parsing and structuring input that is + made up of unstructured text, such as log files. Grok allows users to add a naming construct to + Regular Expressions such that they can be composed in order to create expressions that are easier + to manage and work with. This Controller Service consists of one Required Property and one Optional + Property. The Optional Property is named Grok Pattern File and specifies the filename of + a file that contains Grok Patterns that can be used for parsing log data. If not specified, a default + patterns file will be used. Its contains are provided below. +

+ +

+ The Required Property is named Grok Expression and specifies how to parse each + incoming record. This is done by providing a Grok Expression such as: + %{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} \[%{DATA:thread}\] %{DATA:class} %{GREEDYDATA:message}. + This Expression will parse Apache NiFi log messages. This is accomplished by specifying that a line begins + with the TIMESTAMP_ISO8601 pattern (which is a Regular Expression defined in the default + Grok Patterns File). The value that matches this pattern is then given the name timestamp. As a result, + the value that matches this pattern will be assigned to a field named timestamp in the Record that + produced by this Controller Service. +

+ +

+ If a line is encountered in the FlowFile that does not match the configured Grok Expression, it is assumed that the line + is part of the previous message. If the line is the start of a stack trace, then the entire stack trace is read in and assigned + to a field named STACK_TRACE. Otherwise, the line is appended to the last field defined in the Grok Expression. This + is done because typically the last field is a 'message' type of field, which can consist of new-lines. +

+ +

+ By default, all fields that are extracted are considered to be of type string. This can be overridden + by adding a user-defined property where the name of the property matches the name of the field that is present in the + configured Grok Expression. The value of the user-defined property is the data type to use. + When specifying a data type for a field, the following values are valid: +

+ +
    +
  • string
  • +
  • boolean
  • +
  • byte
  • +
  • char
  • +
  • short
  • +
  • int
  • +
  • bigint
  • +
  • long
  • +
  • float
  • +
  • double
  • +
  • date - A date with no time field. By default, the format used is yyyy-MM-dd. This can be overridden + by adding a colon (:) followed by the desired format. For example: date:MM/dd/yyyy. The format to use is + that of Java's SimpleDateFormat (see + SimpleDateFormat Patterns for more information). +
  • +
  • time - A time with no date field. By default, the format used is HH:mm:ss. This can be overridden + by adding a colon (:) followed by the desired format. For example: time:hh:mm:ss a. The format to use is + that of Java's SimpleDateFormat (see + SimpleDateFormat Patterns for more information). +
  • +
  • timestamp - A field that represents both a date and time. By default, the format used is + yyyy-MM-dd HH:mm:ss. This can be overridden by adding a colon (:) followed by the desired format. For example: + MM/dd/yyyy hh:mm:ss a. The format to use is + that of Java's SimpleDateFormat (see + SimpleDateFormat Patterns for more information).
  • +
  • object - This data type does not apply to CSV data.
  • +
  • array - This data type does not apply to CSV data.
  • +
+ + +

+ Examples +

+ +

+ As an example, consider that this Controller Service is configured with the following properties: +

+ + + + + + + + + + + + +
Property NameProperty Value
Grok Expression%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} \[%{DATA:thread}\] %{DATA:class} %{GREEDYDATA:message}
+ +

+ Additionally, let's consider a FlowFile whose contents consists of the following: +

+ +
+2016-08-04 13:26:32,473 INFO [Leader Election Notification Thread-1] o.a.n.c.l.e.CuratorLeaderElectionManager org.apache.nifi.controller.leader.election.CuratorLeaderElectionManager$ElectionListener@1fa27ea5 has been interrupted; no longer leader for role 'Cluster Coordinator'
+2016-08-04 13:26:32,474 ERROR [Leader Election Notification Thread-2] o.apache.nifi.controller.FlowController One
+Two
+Three
+org.apache.nifi.exception.UnitTestException: Testing to ensure we are able to capture stack traces
+        at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185)
+        at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185)
+        at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185)
+        at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185)
+        at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185)
+        at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185)
+        at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185)
+        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_45]
+	at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308) [na:1.8.0_45]
+        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180) [na:1.8.0_45]
+        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294) [na:1.8.0_45]
+        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_45]
+        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_45]
+        at java.lang.Thread.run(Thread.java:745) [na:1.8.0_45]
+Caused by: org.apache.nifi.exception.UnitTestException: Testing to ensure we are able to capture stack traces
+    at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185)
+    at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185)
+    at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185)
+    at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185)
+    at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185)
+    ... 12 common frames omitted
+2016-08-04 13:26:35,475 WARN [Curator-Framework-0] org.apache.curator.ConnectionState Connection attempt unsuccessful after 3008 (greater than max timeout of 3000). Resetting connection and trying again with a new connection.
+        
+ +

+ In this case, the result will be that this FlowFile consists of 3 different records. The first record will contain the following values: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameField Value
timestamp2016-08-04 13:26:32,473
levelINFO
threadLeader Election Notification Thread-1
classo.a.n.c.l.e.CuratorLeaderElectionManager
messageorg.apache.nifi.controller.leader.election.CuratorLeaderElectionManager$ElectionListener@1fa27ea5 has been interrupted; no longer leader for role 'Cluster Coordinator'
STACK_TRACEnull
+ +

+ The second record will contain the following values: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameField Value
timestamp2016-08-04 13:26:32,474
levelERROR
threadLeader Election Notification Thread-2
classo.apache.nifi.controller.FlowController
messageOne
+Two
+Three
STACK_TRACE +
+org.apache.nifi.exception.UnitTestException: Testing to ensure we are able to capture stack traces
+        at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185)
+        at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185)
+        at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185)
+        at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185)
+        at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185)
+        at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185)
+        at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185)
+        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_45]
+	at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308) [na:1.8.0_45]
+        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180) [na:1.8.0_45]
+        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294) [na:1.8.0_45]
+        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_45]
+        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_45]
+        at java.lang.Thread.run(Thread.java:745) [na:1.8.0_45]
+Caused by: org.apache.nifi.exception.UnitTestException: Testing to ensure we are able to capture stack traces
+    at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185)
+    at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185)
+    at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185)
+    at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185)
+    at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185)
+    ... 12 common frames omitted
+
+ +

+ The third record will contain the following values: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameField Value
timestamp2016-08-04 13:26:35,475
levelWARN
threadCurator-Framework-0
classorg.apache.curator.ConnectionState
messageConnection attempt unsuccessful after 3008 (greater than max timeout of 3000). Resetting connection and trying again with a new connection.
STACK_TRACEnull
+ + +

+

+ +

Default Patterns

+ +

+ The following patterns are available in the default Grok Pattern File: +

+ + +
+# Log Levels
+LOGLEVEL ([Aa]lert|ALERT|[Tt]race|TRACE|[Dd]ebug|DEBUG|[Nn]otice|NOTICE|[Ii]nfo|INFO|[Ww]arn?(?:ing)?|WARN?(?:ING)?|[Ee]rr?(?:or)?|ERR?(?:OR)?|[Cc]rit?(?:ical)?|CRIT?(?:ICAL)?|[Ff]atal|FATAL|[Ss]evere|SEVERE|EMERG(?:ENCY)?|[Ee]merg(?:ency)?)|FINE|FINER|FINEST|CONFIG
+
+# Syslog Dates: Month Day HH:MM:SS
+SYSLOGTIMESTAMP %{MONTH} +%{MONTHDAY} %{TIME}
+PROG (?:[\w._/%-]+)
+SYSLOGPROG %{PROG:program}(?:\[%{POSINT:pid}\])?
+SYSLOGHOST %{IPORHOST}
+SYSLOGFACILITY <%{NONNEGINT:facility}.%{NONNEGINT:priority}>
+HTTPDATE %{MONTHDAY}/%{MONTH}/%{YEAR}:%{TIME} %{INT}
+
+# Months: January, Feb, 3, 03, 12, December
+MONTH \b(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\b
+MONTHNUM (?:0?[1-9]|1[0-2])
+MONTHNUM2 (?:0[1-9]|1[0-2])
+MONTHDAY (?:(?:0[1-9])|(?:[12][0-9])|(?:3[01])|[1-9])
+
+# Days: Monday, Tue, Thu, etc...
+DAY (?:Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:rsday)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)
+
+# Years?
+YEAR (?>\d\d){1,2}
+HOUR (?:2[0123]|[01]?[0-9])
+MINUTE (?:[0-5][0-9])
+# '60' is a leap second in most time standards and thus is valid.
+SECOND (?:(?:[0-5]?[0-9]|60)(?:[:.,][0-9]+)?)
+TIME (?!<[0-9])%{HOUR}:%{MINUTE}(?::%{SECOND})(?![0-9])
+
+# datestamp is YYYY/MM/DD-HH:MM:SS.UUUU (or something like it)
+DATE_US_MONTH_DAY_YEAR %{MONTHNUM}[/-]%{MONTHDAY}[/-]%{YEAR}
+DATE_US_YEAR_MONTH_DAY %{YEAR}[/-]%{MONTHNUM}[/-]%{MONTHDAY}
+DATE_US %{DATE_US_MONTH_DAY_YEAR}|%{DATE_US_YEAR_MONTH_DAY}
+DATE_EU %{MONTHDAY}[./-]%{MONTHNUM}[./-]%{YEAR}
+ISO8601_TIMEZONE (?:Z|[+-]%{HOUR}(?::?%{MINUTE}))
+ISO8601_SECOND (?:%{SECOND}|60)
+TIMESTAMP_ISO8601 %{YEAR}-%{MONTHNUM}-%{MONTHDAY}[T ]%{HOUR}:?%{MINUTE}(?::?%{SECOND})?%{ISO8601_TIMEZONE}?
+DATE %{DATE_US}|%{DATE_EU}
+DATESTAMP %{DATE}[- ]%{TIME}
+TZ (?:[PMCE][SD]T|UTC)
+DATESTAMP_RFC822 %{DAY} %{MONTH} %{MONTHDAY} %{YEAR} %{TIME} %{TZ}
+DATESTAMP_RFC2822 %{DAY}, %{MONTHDAY} %{MONTH} %{YEAR} %{TIME} %{ISO8601_TIMEZONE}
+DATESTAMP_OTHER %{DAY} %{MONTH} %{MONTHDAY} %{TIME} %{TZ} %{YEAR}
+DATESTAMP_EVENTLOG %{YEAR}%{MONTHNUM2}%{MONTHDAY}%{HOUR}%{MINUTE}%{SECOND}
+
+
+POSINT \b(?:[1-9][0-9]*)\b
+NONNEGINT \b(?:[0-9]+)\b
+WORD \b\w+\b
+NOTSPACE \S+
+SPACE \s*
+DATA .*?
+GREEDYDATA .*
+QUOTEDSTRING (?>(?"(?>\\.|[^\\"]+)+"|""|(?>'(?>\\.|[^\\']+)+')|''|(?>`(?>\\.|[^\\`]+)+`)|``))
+UUID [A-Fa-f0-9]{8}-(?:[A-Fa-f0-9]{4}-){3}[A-Fa-f0-9]{12}
+
+USERNAME [a-zA-Z0-9._-]+
+USER %{USERNAME}
+INT (?:[+-]?(?:[0-9]+))
+BASE10NUM (?[+-]?(?:(?:[0-9]+(?:\.[0-9]+)?)|(?:\.[0-9]+)))
+NUMBER (?:%{BASE10NUM})
+BASE16NUM (?/(?>[\w_%!$@:.,-]+|\\.)*)+
+TTY (?:/dev/(pts|tty([pq])?)(\w+)?/?(?:[0-9]+))
+WINPATH (?>[A-Za-z]+:|\\)(?:\\[^\\?*]*)+
+URIPROTO [A-Za-z]+(\+[A-Za-z+]+)?
+URIHOST %{IPORHOST}(?::%{POSINT:port})?
+# uripath comes loosely from RFC1738, but mostly from what Firefox
+# doesn't turn into %XX
+URIPATH (?:/[A-Za-z0-9$.+!*'(){},~:;=@#%_\-]*)+
+#URIPARAM \?(?:[A-Za-z0-9]+(?:=(?:[^&]*))?(?:&(?:[A-Za-z0-9]+(?:=(?:[^&]*))?)?)*)?
+URIPARAM \?[A-Za-z0-9$.+!*'|(){},~@#%&/=:;_?\-\[\]]*
+URIPATHPARAM %{URIPATH}(?:%{URIPARAM})?
+URI %{URIPROTO}://(?:%{USER}(?::[^@]*)?@)?(?:%{URIHOST})?(?:%{URIPATHPARAM})?
+
+# Shortcuts
+QS %{QUOTEDSTRING}
+
+# Log formats
+SYSLOGBASE %{SYSLOGTIMESTAMP:timestamp} (?:%{SYSLOGFACILITY} )?%{SYSLOGHOST:logsource} %{SYSLOGPROG}:
+COMMONAPACHELOG %{IPORHOST:clientip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] "(?:%{WORD:verb} %{NOTSPACE:request}(?: HTTP/%{NUMBER:httpversion})?|%{DATA:rawrequest})" %{NUMBER:response} (?:%{NUMBER:bytes}|-)
+COMBINEDAPACHELOG %{COMMONAPACHELOG} %{QS:referrer} %{QS:agent}
+		
+
+ + + diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/resources/docs/org.apache.nifi.json.JsonPathReader/additionalDetails.html b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/resources/docs/org.apache.nifi.json.JsonPathReader/additionalDetails.html new file mode 100644 index 000000000..2b69f7e2c --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/resources/docs/org.apache.nifi.json.JsonPathReader/additionalDetails.html @@ -0,0 +1,227 @@ + + + + + + JsonPathReader + + + + +

+ The JsonPathReader Controller Service, parses FlowFiles that are in the JSON format. User-defined properties + specify how to extract all relevant fields from the JSON in order to create a row-oriented record. The Controller + Service will not be valid unless at least one JSON Path is provided. Unlike the + FlatJsonReader Controller Service, this + service will return a record that contains only those fields that have been configured via JSON Path. +

+ +

+ If the root of the FlowFile's JSON is a JSON Array, each JSON Object found in that array will be treated as a separate + Record, not as a single record made up of an array. If the root of the FlowFile's JSON is a JSON Object, it will be + evaluated as a single Record. +

+ +

+ Supplying a JSON Path is accomplished by adding a user-defined property where the name of the property becomes the name + of the field in the Record that is returned. The value of the property must be a valid JSON Path expression. This JSON Path + will be evaluated against each top-level JSON Object in the FlowFile, and the result will be the value of the field whose + name is specified by the property name. By default, the type of each field is inferred automatically based on the values of + the first JSON Object encountered for the FlowFile. This can be overridden by changing the name of the user-defined property + by adding a colon (:) and specifying the data type. For example: balance:double or dob:date:MM/dd/yyyy. + In this case, the data type and option format are not included in the field name. So for the aforementioned examples, we would + end up with field names balance and dob. +

+ +

+ When specifying a data type for a field, the following values are valid: +

+ +
    +
  • string
  • +
  • boolean
  • +
  • byte
  • +
  • char
  • +
  • short
  • +
  • int
  • +
  • bigint
  • +
  • long
  • +
  • float
  • +
  • double
  • +
  • date - A date with no time field. By default, the format used is yyyy-MM-dd. This can be overridden + by adding a colon (:) followed by the desired format. For example: date:MM/dd/yyyy. The format to use is + that of Java's SimpleDateFormat (see + SimpleDateFormat Patterns for more information). +
  • +
  • time - A time with no date field. By default, the format used is HH:mm:ss. This can be overridden + by adding a colon (:) followed by the desired format. For example: time:hh:mm:ss a. The format to use is + that of Java's SimpleDateFormat (see + SimpleDateFormat Patterns for more information). +
  • +
  • timestamp - A field that represents both a date and time. By default, the format used is + yyyy-MM-dd HH:mm:ss. This can be overridden by adding a colon (:) followed by the desired format. For example: + MM/dd/yyyy hh:mm:ss a. The format to use is + that of Java's SimpleDateFormat (see + SimpleDateFormat Patterns for more information).
  • +
  • object - The value will be returned as a Map<String, Object>. The types of the values in the Map + are always inferred. The type used for the values may not be the same for each record. For example, consider the following + JSON array: +

    + + [{ + id: 17, + name: "John", + child: { + id: "1" + }, + siblingIds: [4, "8"] + }, +
    { + id: 98, + name: "Jane", + child: { + id: 2 + }, + siblingIds: [] + }] +
    +

    + In this case, the child element would be inferred to be of type object. Since nested types + are inferred on a per-record basis, for the first record, the child field would return a Map + where the value of the id entry is a string. However, for the second record, the child + field would return a Map where the value of the id entry is an int. +
    + Moreover, the siblingIds of the John will be an array where the first element is an int + and the second element is a string. The siblingIds of Jane will be an empty array. +
  • +
  • array - An array of values. The types of the values are always inferred and may not be the same for each element + in the array, or for two arrays from different JSON objects.
  • +
+ + +

+ As an example, consider a FlowFile whose content contains the following JSON: +

+ + + [{ + id: 17, + name: "John", + child: { + id: "1" + }, + siblingIds: [4, "8"] + }, +
{ + id: 98, + name: "Jane", + child: { + id: 2 + }, + gender: "F", + siblingIds: [] + }] +
+ +

+ If we configure this Controller Service with the following user-defined properties: + + + + + + + + + + + + + + + + + + + + + + + + +
Property NameProperty Value
id$.id
name$.name
childId:long$.child.id
gender:string$.gender
+

+ +

+ In this case, the FlowFile will generate two Records. The first record will consist of the following key/value pairs: + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameField Value
id17
nameJohn
childId1
gendernull
+

+ +

+ The second record will consist of the following key/value pairs: + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameField Value
id98
nameJane
childId2
genderF
+

+ + + diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/resources/docs/org.apache.nifi.json.JsonTreeReader/additionalDetails.html b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/resources/docs/org.apache.nifi.json.JsonTreeReader/additionalDetails.html new file mode 100644 index 000000000..7d6be7a84 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/resources/docs/org.apache.nifi.json.JsonTreeReader/additionalDetails.html @@ -0,0 +1,102 @@ + + + + + + JsonTreeReader + + + + +

+ The JsonTreeReader Controller Service, by default, derives the schema for a FlowFile + based on the first JSON Object in the FlowFile. For each field found, the data type + is inferred. However, the type of a field can be overridden by adding a user-defined property to + the Controller Service. The name of the property should be the same as the name of the + JSON field. The value of the property denotes the data type of the corresponding field. + If no JSON field is found with a matching name, then a field will be added to the schema, + and a null value will be used for any record for which the JSON field + is not present. If a field is found with a matching name, but the type is different, + the Controller Service will attempt to coerce the value into the user-defined type. If unable + to do so, an Exception will be thrown. +

+ +

+ When specifying a data type for a field, the following values are valid: +

+ +
    +
  • string
  • +
  • boolean
  • +
  • byte
  • +
  • char
  • +
  • short
  • +
  • int
  • +
  • bigint
  • +
  • long
  • +
  • float
  • +
  • double
  • +
  • date - A date with no time field. By default, the format used is yyyy-MM-dd. This can be overridden + by adding a colon (:) followed by the desired format. For example: date:MM/dd/yyyy. The format to use is + that of Java's SimpleDateFormat (see + SimpleDateFormat Patterns for more information). +
  • +
  • time - A time with no date field. By default, the format used is HH:mm:ss. This can be overridden + by adding a colon (:) followed by the desired format. For example: time:hh:mm:ss a. The format to use is + that of Java's SimpleDateFormat (see + SimpleDateFormat Patterns for more information). +
  • +
  • timestamp - A field that represents both a date and time. By default, the format used is + yyyy-MM-dd HH:mm:ss. This can be overridden by adding a colon (:) followed by the desired format. For example: + MM/dd/yyyy hh:mm:ss a. The format to use is + that of Java's SimpleDateFormat (see + SimpleDateFormat Patterns for more information).
  • +
  • object - The value will be returned as a Map<String, Object>. The types of the values in the Map + are always inferred. The type used for the values may not be the same for each record. For example, consider the following + JSON array: +

    + + [{ + id: 17, + name: "John", + child: { + id: "1" + }, + siblingIds: [4, "8"] + }, +
    { + id: 98, + name: "Jane", + child: { + id: 2 + }, + siblingIds: [] + }] +
    +

    + In this case, the child element would be inferred to be of type object. Since nested types + are inferred on a per-record basis, for the first record, the child field would return a Map + where the value of the id entry is a string. However, for the second record, the child + field would return a Map where the value of the id entry is an int. +
    + Moreover, the siblingIds of the John will be an array where the first element is an int + and the second element is a string. The siblingIds of Jane will be an empty array. +
  • +
  • array - An array of values. The types of the values are always inferred and may not be the same for each element + in the array, or for two arrays from different JSON objects.
  • +
+ + diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/avro/TestAvroRecordReader.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/avro/TestAvroRecordReader.java new file mode 100644 index 000000000..2ec3441b1 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/avro/TestAvroRecordReader.java @@ -0,0 +1,221 @@ +/* + * 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.avro; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.avro.Schema; +import org.apache.avro.Schema.Field; +import org.apache.avro.Schema.Type; +import org.apache.avro.file.DataFileWriter; +import org.apache.avro.generic.GenericData; +import org.apache.avro.generic.GenericDatumWriter; +import org.apache.avro.generic.GenericRecord; +import org.apache.avro.io.DatumWriter; +import org.apache.nifi.serialization.MalformedRecordException; +import org.apache.nifi.serialization.SimpleRecordSchema; +import org.apache.nifi.serialization.record.MapRecord; +import org.apache.nifi.serialization.record.Record; +import org.apache.nifi.serialization.record.RecordField; +import org.apache.nifi.serialization.record.RecordFieldType; +import org.apache.nifi.serialization.record.RecordSchema; +import org.junit.Test; + +public class TestAvroRecordReader { + + @Test + public void testDataTypes() throws IOException, MalformedRecordException { + final List accountFields = new ArrayList<>(); + accountFields.add(new Field("accountId", Schema.create(Type.LONG), null, null)); + accountFields.add(new Field("accountName", Schema.create(Type.STRING), null, null)); + final Schema accountSchema = Schema.createRecord("account", null, null, false); + accountSchema.setFields(accountFields); + + final List catFields = new ArrayList<>(); + catFields.add(new Field("catTailLength", Schema.create(Type.INT), null, null)); + catFields.add(new Field("catName", Schema.create(Type.STRING), null, null)); + final Schema catSchema = Schema.createRecord("cat", null, null, false); + catSchema.setFields(catFields); + + final List dogFields = new ArrayList<>(); + dogFields.add(new Field("dogTailLength", Schema.create(Type.INT), null, null)); + dogFields.add(new Field("dogName", Schema.create(Type.STRING), null, null)); + final Schema dogSchema = Schema.createRecord("dog", null, null, false); + dogSchema.setFields(dogFields); + + final List fields = new ArrayList<>(); + fields.add(new Field("name", Schema.create(Type.STRING), null, null)); + fields.add(new Field("age", Schema.create(Type.INT), null, null)); + fields.add(new Field("balance", Schema.create(Type.DOUBLE), null, null)); + fields.add(new Field("rate", Schema.create(Type.FLOAT), null, null)); + fields.add(new Field("debt", Schema.create(Type.BOOLEAN), null, null)); + fields.add(new Field("nickname", Schema.create(Type.NULL), null, null)); + fields.add(new Field("binary", Schema.create(Type.BYTES), null, null)); + fields.add(new Field("fixed", Schema.createFixed("fixed", null, null, 5), null, null)); + fields.add(new Field("map", Schema.createMap(Schema.create(Type.STRING)), null, null)); + fields.add(new Field("array", Schema.createArray(Schema.create(Type.LONG)), null, null)); + fields.add(new Field("account", accountSchema, null, null)); + fields.add(new Field("desiredbalance", Schema.createUnion( // test union of NULL and other type with no value + Arrays.asList(Schema.create(Type.NULL), Schema.create(Type.DOUBLE))), + null, null)); + fields.add(new Field("dreambalance", Schema.createUnion( // test union of NULL and other type with a value + Arrays.asList(Schema.create(Type.NULL), Schema.create(Type.DOUBLE))), + null, null)); + fields.add(new Field("favAnimal", Schema.createUnion(Arrays.asList(catSchema, dogSchema)), null, null)); + fields.add(new Field("otherFavAnimal", Schema.createUnion(Arrays.asList(catSchema, dogSchema)), null, null)); + + final Schema schema = Schema.createRecord("record", null, null, false); + schema.setFields(fields); + + final byte[] source; + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + final Map map = new HashMap<>(); + map.put("greeting", "hello"); + map.put("salutation", "good-bye"); + + final DatumWriter datumWriter = new GenericDatumWriter<>(schema); + try (final DataFileWriter dataFileWriter = new DataFileWriter<>(datumWriter); + final DataFileWriter writer = dataFileWriter.create(schema, baos)) { + + final GenericRecord record = new GenericData.Record(schema); + record.put("name", "John"); + record.put("age", 33); + record.put("balance", 1234.56D); + record.put("rate", 0.045F); + record.put("debt", false); + record.put("binary", ByteBuffer.wrap("binary".getBytes(StandardCharsets.UTF_8))); + record.put("fixed", new GenericData.Fixed(Schema.create(Type.BYTES), "fixed".getBytes(StandardCharsets.UTF_8))); + record.put("map", map); + record.put("array", Arrays.asList(1L, 2L)); + record.put("dreambalance", 10_000_000.00D); + + final GenericRecord accountRecord = new GenericData.Record(accountSchema); + accountRecord.put("accountId", 83L); + accountRecord.put("accountName", "Checking"); + record.put("account", accountRecord); + + final GenericRecord catRecord = new GenericData.Record(catSchema); + catRecord.put("catTailLength", 1); + catRecord.put("catName", "Meow"); + record.put("otherFavAnimal", catRecord); + + final GenericRecord dogRecord = new GenericData.Record(dogSchema); + dogRecord.put("dogTailLength", 14); + dogRecord.put("dogName", "Fido"); + record.put("favAnimal", dogRecord); + + writer.append(record); + } + + source = baos.toByteArray(); + + try (final InputStream in = new ByteArrayInputStream(source)) { + final AvroRecordReader reader = new AvroRecordReader(in); + final RecordSchema recordSchema = reader.getSchema(); + assertEquals(15, recordSchema.getFieldCount()); + + assertEquals(RecordFieldType.STRING, recordSchema.getDataType("name").get().getFieldType()); + assertEquals(RecordFieldType.INT, recordSchema.getDataType("age").get().getFieldType()); + assertEquals(RecordFieldType.DOUBLE, recordSchema.getDataType("balance").get().getFieldType()); + assertEquals(RecordFieldType.FLOAT, recordSchema.getDataType("rate").get().getFieldType()); + assertEquals(RecordFieldType.BOOLEAN, recordSchema.getDataType("debt").get().getFieldType()); + assertEquals(RecordFieldType.RECORD, recordSchema.getDataType("nickname").get().getFieldType()); + assertEquals(RecordFieldType.ARRAY, recordSchema.getDataType("binary").get().getFieldType()); + assertEquals(RecordFieldType.ARRAY, recordSchema.getDataType("fixed").get().getFieldType()); + assertEquals(RecordFieldType.RECORD, recordSchema.getDataType("map").get().getFieldType()); + assertEquals(RecordFieldType.ARRAY, recordSchema.getDataType("array").get().getFieldType()); + assertEquals(RecordFieldType.RECORD, recordSchema.getDataType("account").get().getFieldType()); + assertEquals(RecordFieldType.DOUBLE, recordSchema.getDataType("desiredbalance").get().getFieldType()); + assertEquals(RecordFieldType.DOUBLE, recordSchema.getDataType("dreambalance").get().getFieldType()); + assertEquals(RecordFieldType.CHOICE, recordSchema.getDataType("favAnimal").get().getFieldType()); + assertEquals(RecordFieldType.CHOICE, recordSchema.getDataType("otherFavAnimal").get().getFieldType()); + + final Object[] values = reader.nextRecord().getValues(); + assertEquals(15, values.length); + assertEquals("John", values[0]); + assertEquals(33, values[1]); + assertEquals(1234.56D, values[2]); + assertEquals(0.045F, values[3]); + assertEquals(false, values[4]); + assertEquals(null, values[5]); + assertArrayEquals("binary".getBytes(StandardCharsets.UTF_8), (byte[]) values[6]); + assertArrayEquals("fixed".getBytes(StandardCharsets.UTF_8), (byte[]) values[7]); + assertEquals(map, values[8]); + assertArrayEquals(new Object[] {1L, 2L}, (Object[]) values[9]); + + final Map accountValues = new HashMap<>(); + accountValues.put("accountName", "Checking"); + accountValues.put("accountId", 83L); + + final List accountRecordFields = new ArrayList<>(); + accountRecordFields.add(new RecordField("accountId", RecordFieldType.LONG.getDataType())); + accountRecordFields.add(new RecordField("accountName", RecordFieldType.STRING.getDataType())); + + final RecordSchema accountRecordSchema = new SimpleRecordSchema(accountRecordFields); + final Record mapRecord = new MapRecord(accountRecordSchema, accountValues); + + assertEquals(mapRecord, values[10]); + + assertNull(values[11]); + assertEquals(10_000_000.0D, values[12]); + + final Map dogMap = new HashMap<>(); + dogMap.put("dogName", "Fido"); + dogMap.put("dogTailLength", 14); + + final List dogRecordFields = new ArrayList<>(); + dogRecordFields.add(new RecordField("dogTailLength", RecordFieldType.INT.getDataType())); + dogRecordFields.add(new RecordField("dogName", RecordFieldType.STRING.getDataType())); + final RecordSchema dogRecordSchema = new SimpleRecordSchema(dogRecordFields); + final Record dogRecord = new MapRecord(dogRecordSchema, dogMap); + + assertEquals(dogRecord, values[13]); + + final Map catMap = new HashMap<>(); + catMap.put("catName", "Meow"); + catMap.put("catTailLength", 1); + + final List catRecordFields = new ArrayList<>(); + catRecordFields.add(new RecordField("catTailLength", RecordFieldType.INT.getDataType())); + catRecordFields.add(new RecordField("catName", RecordFieldType.STRING.getDataType())); + final RecordSchema catRecordSchema = new SimpleRecordSchema(catRecordFields); + final Record catRecord = new MapRecord(catRecordSchema, catMap); + + assertEquals(catRecord, values[14]); + } + } + + public static enum Status { + GOOD, BAD; + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/csv/TestCSVRecordReader.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/csv/TestCSVRecordReader.java new file mode 100644 index 000000000..1e53d89de --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/csv/TestCSVRecordReader.java @@ -0,0 +1,122 @@ +/* + * 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.csv; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.serialization.MalformedRecordException; +import org.apache.nifi.serialization.record.DataType; +import org.apache.nifi.serialization.record.RecordFieldType; +import org.apache.nifi.serialization.record.RecordSchema; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +public class TestCSVRecordReader { + private final DataType stringDataType = RecordFieldType.STRING.getDataType(); + private final DataType doubleDataType = RecordFieldType.DOUBLE.getDataType(); + private final DataType timeDataType = RecordFieldType.TIME.getDataType(); + + @Test + public void testSimpleParse() throws IOException, MalformedRecordException { + final Map overrides = new HashMap<>(); + overrides.put("balance", doubleDataType); + overrides.put("other", timeDataType); + + try (final InputStream fis = new FileInputStream(new File("src/test/resources/csv/single-bank-account.csv"))) { + final CSVRecordReader reader = new CSVRecordReader(fis, null, overrides); + + final RecordSchema schema = reader.getSchema(); + verifyFields(schema); + + final Object[] record = reader.nextRecord().getValues(); + final Object[] expectedValues = new Object[] {"1", "John Doe", 4750.89D, "123 My Street", "My City", "MS", "11111", "USA"}; + Assert.assertArrayEquals(expectedValues, record); + + assertNull(reader.nextRecord()); + } + } + + @Test + public void testMultipleRecords() throws IOException, MalformedRecordException { + final Map overrides = new HashMap<>(); + overrides.put("balance", doubleDataType); + + try (final InputStream fis = new FileInputStream(new File("src/test/resources/csv/multi-bank-account.csv"))) { + final CSVRecordReader reader = new CSVRecordReader(fis, null, overrides); + + final RecordSchema schema = reader.getSchema(); + verifyFields(schema); + + final Object[] firstRecord = reader.nextRecord().getValues(); + final Object[] firstExpectedValues = new Object[] {"1", "John Doe", 4750.89D, "123 My Street", "My City", "MS", "11111", "USA"}; + Assert.assertArrayEquals(firstExpectedValues, firstRecord); + + final Object[] secondRecord = reader.nextRecord().getValues(); + final Object[] secondExpectedValues = new Object[] {"2", "Jane Doe", 4820.09D, "321 Your Street", "Your City", "NY", "33333", "USA"}; + Assert.assertArrayEquals(secondExpectedValues, secondRecord); + + assertNull(reader.nextRecord()); + } + } + + @Test + public void testExtraWhiteSpace() throws IOException, MalformedRecordException { + final Map overrides = new HashMap<>(); + overrides.put("balance", doubleDataType); + + try (final InputStream fis = new FileInputStream(new File("src/test/resources/csv/extra-white-space.csv"))) { + final CSVRecordReader reader = new CSVRecordReader(fis, Mockito.mock(ComponentLog.class), overrides); + + final RecordSchema schema = reader.getSchema(); + verifyFields(schema); + + final Object[] firstRecord = reader.nextRecord().getValues(); + final Object[] firstExpectedValues = new Object[] {"1", "John Doe", 4750.89D, "123 My Street", "My City", "MS", "11111", "USA"}; + Assert.assertArrayEquals(firstExpectedValues, firstRecord); + + final Object[] secondRecord = reader.nextRecord().getValues(); + final Object[] secondExpectedValues = new Object[] {"2", "Jane Doe", 4820.09D, "321 Your Street", "Your City", "NY", "33333", "USA"}; + Assert.assertArrayEquals(secondExpectedValues, secondRecord); + + assertNull(reader.nextRecord()); + } + } + + private void verifyFields(final RecordSchema schema) { + final List fieldNames = schema.getFieldNames(); + final List expectedFieldNames = Arrays.asList("id", "name", "balance", "address", "city", "state", "zipCode", "country"); + assertEquals(expectedFieldNames, fieldNames); + + final List dataTypes = schema.getDataTypes(); + final List expectedDataTypes = Arrays.asList(stringDataType, stringDataType, doubleDataType, + stringDataType, stringDataType, stringDataType, stringDataType, stringDataType); + assertEquals(expectedDataTypes, dataTypes); + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/csv/TestWriteCSVResult.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/csv/TestWriteCSVResult.java new file mode 100644 index 000000000..04f84791c --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/csv/TestWriteCSVResult.java @@ -0,0 +1,121 @@ +/* + * 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.csv; + +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.nifi.serialization.SimpleRecordSchema; +import org.apache.nifi.serialization.record.DataType; +import org.apache.nifi.serialization.record.MapRecord; +import org.apache.nifi.serialization.record.Record; +import org.apache.nifi.serialization.record.RecordField; +import org.apache.nifi.serialization.record.RecordFieldType; +import org.apache.nifi.serialization.record.RecordSchema; +import org.apache.nifi.serialization.record.RecordSet; +import org.junit.Test; + + +public class TestWriteCSVResult { + + @Test + public void testDataTypes() throws IOException { + final WriteCSVResult result = new WriteCSVResult(RecordFieldType.DATE.getDefaultFormat(), RecordFieldType.TIME.getDefaultFormat(), RecordFieldType.TIMESTAMP.getDefaultFormat()); + + final StringBuilder headerBuilder = new StringBuilder(); + final List fields = new ArrayList<>(); + for (final RecordFieldType fieldType : RecordFieldType.values()) { + if (fieldType == RecordFieldType.CHOICE) { + final List possibleTypes = new ArrayList<>(); + possibleTypes.add(RecordFieldType.INT.getDataType()); + possibleTypes.add(RecordFieldType.LONG.getDataType()); + + fields.add(new RecordField(fieldType.name().toLowerCase(), fieldType.getDataType(possibleTypes))); + } else { + fields.add(new RecordField(fieldType.name().toLowerCase(), fieldType.getDataType())); + } + + headerBuilder.append('"').append(fieldType.name().toLowerCase()).append('"').append(","); + } + final RecordSchema schema = new SimpleRecordSchema(fields); + + final long now = System.currentTimeMillis(); + final Map valueMap = new HashMap<>(); + valueMap.put("string", "string"); + valueMap.put("boolean", true); + valueMap.put("byte", (byte) 1); + valueMap.put("char", 'c'); + valueMap.put("short", (short) 8); + valueMap.put("int", 9); + valueMap.put("bigint", BigInteger.valueOf(8L)); + valueMap.put("long", 8L); + valueMap.put("float", 8.0F); + valueMap.put("double", 8.0D); + valueMap.put("date", new Date(now)); + valueMap.put("time", new Time(now)); + valueMap.put("timestamp", new Timestamp(now)); + valueMap.put("object", null); + valueMap.put("choice", 48L); + valueMap.put("array", null); + + final Record record = new MapRecord(schema, valueMap); + final RecordSet rs = RecordSet.of(schema, record); + + final String output; + try (final ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + result.write(rs, baos); + output = new String(baos.toByteArray(), StandardCharsets.UTF_8); + } + + headerBuilder.deleteCharAt(headerBuilder.length() - 1); + final String headerLine = headerBuilder.toString(); + + final String[] splits = output.split("\n"); + assertEquals(2, splits.length); + assertEquals(headerLine, splits[0]); + + final String values = splits[1]; + final StringBuilder expectedBuilder = new StringBuilder(); + expectedBuilder.append("\"string\",\"true\",\"1\",\"c\",\"8\",\"9\",\"8\",\"8\",\"8.0\",\"8.0\","); + + final String dateValue = new SimpleDateFormat(RecordFieldType.DATE.getDefaultFormat()).format(now); + final String timeValue = new SimpleDateFormat(RecordFieldType.TIME.getDefaultFormat()).format(now); + final String timestampValue = new SimpleDateFormat(RecordFieldType.TIMESTAMP.getDefaultFormat()).format(now); + + expectedBuilder.append('"').append(dateValue).append('"').append(','); + expectedBuilder.append('"').append(timeValue).append('"').append(','); + expectedBuilder.append('"').append(timestampValue).append('"').append(','); + expectedBuilder.append(",\"48\","); + final String expectedValues = expectedBuilder.toString(); + + assertEquals(expectedValues, values); + } + +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/grok/TestGrokRecordReader.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/grok/TestGrokRecordReader.java new file mode 100644 index 000000000..3757ab185 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/grok/TestGrokRecordReader.java @@ -0,0 +1,190 @@ +/* + * 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.grok; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Collections; + +import org.apache.nifi.serialization.MalformedRecordException; +import org.apache.nifi.serialization.record.Record; +import org.junit.Test; + +import io.thekraken.grok.api.Grok; +import io.thekraken.grok.api.exception.GrokException; + +public class TestGrokRecordReader { + + @Test + public void testParseSingleLineLogMessages() throws GrokException, IOException, MalformedRecordException { + try (final InputStream fis = new FileInputStream(new File("src/test/resources/grok/single-line-log-messages.txt"))) { + final Grok grok = new Grok(); + grok.addPatternFromFile("src/main/resources/default-grok-patterns.txt"); + grok.compile("%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} %{GREEDYDATA:message}"); + + final GrokRecordReader deserializer = new GrokRecordReader(fis, grok, Collections.emptyMap()); + + final String[] logLevels = new String[] {"INFO", "WARN", "ERROR", "FATAL", "FINE"}; + final String[] messages = new String[] {"Test Message 1", "Red", "Green", "Blue", "Yellow"}; + + for (int i = 0; i < logLevels.length; i++) { + final Object[] values = deserializer.nextRecord().getValues(); + + assertNotNull(values); + assertEquals(4, values.length); // values[] contains 4 elements: timestamp, level, message, STACK_TRACE + assertEquals("2016-11-08 21:24:23,029", values[0]); + assertEquals(logLevels[i], values[1]); + assertEquals(messages[i], values[2]); + assertNull(values[3]); + } + + assertNull(deserializer.nextRecord()); + } + } + + + @Test + public void testParseEmptyMessageWithStackTrace() throws GrokException, IOException, MalformedRecordException { + final Grok grok = new Grok(); + grok.addPatternFromFile("src/main/resources/default-grok-patterns.txt"); + grok.compile("%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} \\[%{DATA:thread}\\] %{DATA:class} %{GREEDYDATA:message}"); + + final String msg = "2016-08-04 13:26:32,473 INFO [Leader Election Notification Thread-1] o.a.n.LoggerClass \n" + + "org.apache.nifi.exception.UnitTestException: Testing to ensure we are able to capture stack traces"; + final InputStream bais = new ByteArrayInputStream(msg.getBytes(StandardCharsets.UTF_8)); + final GrokRecordReader deserializer = new GrokRecordReader(bais, grok, Collections.emptyMap()); + + final Object[] values = deserializer.nextRecord().getValues(); + + assertNotNull(values); + assertEquals(6, values.length); // values[] contains 4 elements: timestamp, level, message, STACK_TRACE + assertEquals("2016-08-04 13:26:32,473", values[0]); + assertEquals("INFO", values[1]); + assertEquals("Leader Election Notification Thread-1", values[2]); + assertEquals("o.a.n.LoggerClass", values[3]); + assertEquals("", values[4]); + assertEquals("org.apache.nifi.exception.UnitTestException: Testing to ensure we are able to capture stack traces", values[5]); + } + + + + @Test + public void testParseNiFiSampleLog() throws IOException, GrokException, MalformedRecordException { + try (final InputStream fis = new FileInputStream(new File("src/test/resources/grok/nifi-log-sample.log"))) { + final Grok grok = new Grok(); + grok.addPatternFromFile("src/main/resources/default-grok-patterns.txt"); + grok.compile("%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} \\[%{DATA:thread}\\] %{DATA:class} %{GREEDYDATA:message}"); + + final GrokRecordReader deserializer = new GrokRecordReader(fis, grok, Collections.emptyMap()); + + final String[] logLevels = new String[] {"INFO", "INFO", "INFO", "WARN", "WARN"}; + + for (int i = 0; i < logLevels.length; i++) { + final Object[] values = deserializer.nextRecord().getValues(); + + assertNotNull(values); + assertEquals(6, values.length); // values[] contains 6 elements: timestamp, level, thread, class, message, STACK_TRACE + assertEquals(logLevels[i], values[1]); + assertNull(values[5]); + } + + assertNull(deserializer.nextRecord()); + } + } + + @Test + public void testParseNiFiSampleMultilineWithStackTrace() throws IOException, GrokException, MalformedRecordException { + try (final InputStream fis = new FileInputStream(new File("src/test/resources/grok/nifi-log-sample-multiline-with-stacktrace.log"))) { + final Grok grok = new Grok(); + grok.addPatternFromFile("src/main/resources/default-grok-patterns.txt"); + grok.compile("%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} \\[%{DATA:thread}\\] %{DATA:class} %{GREEDYDATA:message}?"); + + final GrokRecordReader deserializer = new GrokRecordReader(fis, grok, Collections.emptyMap()); + + final String[] logLevels = new String[] {"INFO", "INFO", "ERROR", "WARN", "WARN"}; + + for (int i = 0; i < logLevels.length; i++) { + final Record record = deserializer.nextRecord(); + final Object[] values = record.getValues(); + + assertNotNull(values); + assertEquals(6, values.length); // values[] contains 6 elements: timestamp, level, thread, class, message, STACK_TRACE + assertEquals(logLevels[i], values[1]); + if ("ERROR".equals(values[1])) { + final String msg = (String) values[4]; + assertEquals("One\nTwo\nThree", msg); + assertNotNull(values[5]); + } else { + assertNull(values[5]); + } + } + + assertNull(deserializer.nextRecord()); + } + } + + + @Test + public void testParseStackTrace() throws GrokException, IOException, MalformedRecordException { + try (final InputStream fis = new FileInputStream(new File("src/test/resources/grok/error-with-stack-trace.log"))) { + final Grok grok = new Grok(); + grok.addPatternFromFile("src/main/resources/default-grok-patterns.txt"); + grok.compile("%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} %{GREEDYDATA:message}"); + + final GrokRecordReader deserializer = new GrokRecordReader(fis, grok, Collections.emptyMap()); + + final String[] logLevels = new String[] {"INFO", "ERROR", "INFO"}; + final String[] messages = new String[] {"message without stack trace", + "Log message with stack trace", + "message without stack trace"}; + + for (int i = 0; i < logLevels.length; i++) { + final Object[] values = deserializer.nextRecord().getValues(); + + assertNotNull(values); + assertEquals(4, values.length); // values[] contains 4 elements: timestamp, level, message, STACK_TRACE + assertEquals(logLevels[i], values[1]); + assertEquals(messages[i], values[2]); + + if (values[1].equals("ERROR")) { + final String stackTrace = (String) values[3]; + assertNotNull(stackTrace); + assertTrue(stackTrace.startsWith("org.apache.nifi.exception.UnitTestException: Testing to ensure we are able to capture stack traces")); + assertTrue(stackTrace.contains(" at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(" + + "NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT]")); + assertTrue(stackTrace.contains("Caused by: org.apache.nifi.exception.UnitTestException: Testing to ensure we are able to capture stack traces")); + assertTrue(stackTrace.contains("at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(" + + "NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT]")); + assertTrue(stackTrace.endsWith(" ... 12 common frames omitted")); + } + } + + assertNull(deserializer.nextRecord()); + } + } + +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/json/TestJsonPathRowRecordReader.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/json/TestJsonPathRowRecordReader.java new file mode 100644 index 000000000..fa413962d --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/json/TestJsonPathRowRecordReader.java @@ -0,0 +1,292 @@ +/* + * 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.json; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.serialization.MalformedRecordException; +import org.apache.nifi.serialization.record.DataType; +import org.apache.nifi.serialization.record.Record; +import org.apache.nifi.serialization.record.RecordFieldType; +import org.apache.nifi.serialization.record.RecordSchema; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import com.jayway.jsonpath.JsonPath; + +public class TestJsonPathRowRecordReader { + private final LinkedHashMap allJsonPaths = new LinkedHashMap<>(); + + @Before + public void populateJsonPaths() { + allJsonPaths.clear(); + + allJsonPaths.put("id", JsonPath.compile("$.id")); + allJsonPaths.put("name", JsonPath.compile("$.name")); + allJsonPaths.put("balance", JsonPath.compile("$.balance")); + allJsonPaths.put("address", JsonPath.compile("$.address")); + allJsonPaths.put("city", JsonPath.compile("$.city")); + allJsonPaths.put("state", JsonPath.compile("$.state")); + allJsonPaths.put("zipCode", JsonPath.compile("$.zipCode")); + allJsonPaths.put("country", JsonPath.compile("$.country")); + } + + @Test + public void testReadArray() throws IOException, MalformedRecordException { + try (final InputStream in = new FileInputStream(new File("src/test/resources/json/bank-account-array.json")); + final JsonPathRowRecordReader reader = new JsonPathRowRecordReader(allJsonPaths, Collections.emptyMap(), in, Mockito.mock(ComponentLog.class))) { + + final RecordSchema schema = reader.getSchema(); + + final List fieldNames = schema.getFieldNames(); + final List expectedFieldNames = Arrays.asList(new String[] {"id", "name", "balance", "address", "city", "state", "zipCode", "country"}); + assertEquals(expectedFieldNames, fieldNames); + + final List dataTypes = schema.getDataTypes().stream().map(dt -> dt.getFieldType()).collect(Collectors.toList()); + final List expectedTypes = Arrays.asList(new RecordFieldType[] {RecordFieldType.INT, RecordFieldType.STRING, + RecordFieldType.DOUBLE, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING}); + assertEquals(expectedTypes, dataTypes); + + final Object[] firstRecordValues = reader.nextRecord().getValues(); + Assert.assertArrayEquals(new Object[] {1, "John Doe", 4750.89, "123 My Street", "My City", "MS", "11111", "USA"}, firstRecordValues); + + final Object[] secondRecordValues = reader.nextRecord().getValues(); + Assert.assertArrayEquals(new Object[] {2, "Jane Doe", 4820.09, "321 Your Street", "Your City", "NY", "33333", "USA"}, secondRecordValues); + + assertNull(reader.nextRecord()); + } + } + + @Test + public void testSingleJsonElement() throws IOException, MalformedRecordException { + try (final InputStream in = new FileInputStream(new File("src/test/resources/json/single-bank-account.json")); + final JsonPathRowRecordReader reader = new JsonPathRowRecordReader(allJsonPaths, Collections.emptyMap(), in, Mockito.mock(ComponentLog.class))) { + + final RecordSchema schema = reader.getSchema(); + + final List fieldNames = schema.getFieldNames(); + final List expectedFieldNames = Arrays.asList(new String[] {"id", "name", "balance", "address", "city", "state", "zipCode", "country"}); + assertEquals(expectedFieldNames, fieldNames); + + final List dataTypes = schema.getDataTypes().stream().map(dt -> dt.getFieldType()).collect(Collectors.toList()); + final List expectedTypes = Arrays.asList(new RecordFieldType[] {RecordFieldType.INT, RecordFieldType.STRING, + RecordFieldType.DOUBLE, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING}); + assertEquals(expectedTypes, dataTypes); + + final Object[] firstRecordValues = reader.nextRecord().getValues(); + Assert.assertArrayEquals(new Object[] {1, "John Doe", 4750.89, "123 My Street", "My City", "MS", "11111", "USA"}, firstRecordValues); + + assertNull(reader.nextRecord()); + } + } + + + + @Test + public void testElementWithNestedData() throws IOException, MalformedRecordException { + final LinkedHashMap jsonPaths = new LinkedHashMap<>(allJsonPaths); + jsonPaths.put("account", JsonPath.compile("$.account")); + + try (final InputStream in = new FileInputStream(new File("src/test/resources/json/single-element-nested.json")); + final JsonPathRowRecordReader reader = new JsonPathRowRecordReader(jsonPaths, Collections.emptyMap(), in, Mockito.mock(ComponentLog.class))) { + + final RecordSchema schema = reader.getSchema(); + + final List fieldNames = schema.getFieldNames(); + final List expectedFieldNames = Arrays.asList(new String[] {"id", "name", "balance", "address", "city", "state", "zipCode", "country", "account"}); + assertEquals(expectedFieldNames, fieldNames); + + final List dataTypes = schema.getDataTypes().stream().map(dt -> dt.getFieldType()).collect(Collectors.toList()); + final List expectedTypes = Arrays.asList(new RecordFieldType[] {RecordFieldType.INT, RecordFieldType.STRING, RecordFieldType.STRING, + RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.RECORD}); + assertEquals(expectedTypes, dataTypes); + + final Object[] firstRecordValues = reader.nextRecord().getValues(); + final Object[] simpleElements = Arrays.copyOfRange(firstRecordValues, 0, firstRecordValues.length - 1); + Assert.assertArrayEquals(new Object[] {1, "John Doe", null, "123 My Street", "My City", "MS", "11111", "USA"}, simpleElements); + + final Object lastElement = firstRecordValues[firstRecordValues.length - 1]; + assertTrue(lastElement instanceof Record); + final Record record = (Record) lastElement; + assertEquals(42, record.getValue("id")); + assertEquals(4750.89D, record.getValue("balance")); + + assertNull(reader.nextRecord()); + } + } + + @Test + public void testElementWithNestedArray() throws IOException, MalformedRecordException { + final LinkedHashMap jsonPaths = new LinkedHashMap<>(allJsonPaths); + jsonPaths.put("accounts", JsonPath.compile("$.accounts")); + + try (final InputStream in = new FileInputStream(new File("src/test/resources/json/single-element-nested-array.json")); + final JsonPathRowRecordReader reader = new JsonPathRowRecordReader(jsonPaths, Collections.emptyMap(), in, Mockito.mock(ComponentLog.class))) { + + final RecordSchema schema = reader.getSchema(); + + final List fieldNames = schema.getFieldNames(); + final List expectedFieldNames = Arrays.asList(new String[] { + "id", "name", "balance", "address", "city", "state", "zipCode", "country", "accounts"}); + assertEquals(expectedFieldNames, fieldNames); + + final List dataTypes = schema.getDataTypes().stream().map(dt -> dt.getFieldType()).collect(Collectors.toList()); + final List expectedTypes = Arrays.asList(new RecordFieldType[] {RecordFieldType.INT, RecordFieldType.STRING, RecordFieldType.STRING, + RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.ARRAY}); + assertEquals(expectedTypes, dataTypes); + + final Object[] firstRecordValues = reader.nextRecord().getValues(); + final Object[] nonArrayValues = Arrays.copyOfRange(firstRecordValues, 0, firstRecordValues.length - 1); + Assert.assertArrayEquals(new Object[] {1, "John Doe", null, "123 My Street", "My City", "MS", "11111", "USA"}, nonArrayValues); + + final Object lastRecord = firstRecordValues[firstRecordValues.length - 1]; + assertTrue(Object[].class.isAssignableFrom(lastRecord.getClass())); + + final Object[] array = (Object[]) lastRecord; + assertEquals(2, array.length); + final Object firstElement = array[0]; + assertTrue(firstElement instanceof Map); + + final Map firstMap = (Map) firstElement; + assertEquals(42, firstMap.get("id")); + assertEquals(4750.89D, firstMap.get("balance")); + + final Object secondElement = array[1]; + assertTrue(secondElement instanceof Map); + final Map secondMap = (Map) secondElement; + assertEquals(43, secondMap.get("id")); + assertEquals(48212.38D, secondMap.get("balance")); + + assertNull(reader.nextRecord()); + } + } + + @Test + public void testReadArrayDifferentSchemas() throws IOException, MalformedRecordException { + try (final InputStream in = new FileInputStream(new File("src/test/resources/json/bank-account-array-different-schemas.json")); + final JsonPathRowRecordReader reader = new JsonPathRowRecordReader(allJsonPaths, Collections.emptyMap(), in, Mockito.mock(ComponentLog.class))) { + + final RecordSchema schema = reader.getSchema(); + + final List fieldNames = schema.getFieldNames(); + final List expectedFieldNames = Arrays.asList(new String[] {"id", "name", "balance", "address", "city", "state", "zipCode", "country"}); + assertEquals(expectedFieldNames, fieldNames); + + final List dataTypes = schema.getDataTypes().stream().map(dt -> dt.getFieldType()).collect(Collectors.toList()); + final List expectedTypes = Arrays.asList(new RecordFieldType[] {RecordFieldType.INT, RecordFieldType.STRING, + RecordFieldType.DOUBLE, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING}); + assertEquals(expectedTypes, dataTypes); + + final Object[] firstRecordValues = reader.nextRecord().getValues(); + Assert.assertArrayEquals(new Object[] {1, "John Doe", 4750.89, "123 My Street", "My City", "MS", "11111", "USA"}, firstRecordValues); + + final Object[] secondRecordValues = reader.nextRecord().getValues(); + Assert.assertArrayEquals(new Object[] {2, "Jane Doe", 4820.09, "321 Your Street", "Your City", "NY", "33333", null}, secondRecordValues); + + final Object[] thirdRecordValues = reader.nextRecord().getValues(); + Assert.assertArrayEquals(new Object[] {3, "Jake Doe", 4751.89, "124 My Street", "My City", "MS", "11111", "USA"}, thirdRecordValues); + + assertNull(reader.nextRecord()); + } + } + + @Test + public void testReadArrayDifferentSchemasWithOverride() throws IOException, MalformedRecordException { + final LinkedHashMap jsonPaths = new LinkedHashMap<>(allJsonPaths); + jsonPaths.put("address2", JsonPath.compile("$.address2")); + final Map typeOverrides = Collections.singletonMap("address2", RecordFieldType.STRING.getDataType()); + + try (final InputStream in = new FileInputStream(new File("src/test/resources/json/bank-account-array-different-schemas.json")); + final JsonPathRowRecordReader reader = new JsonPathRowRecordReader(jsonPaths, typeOverrides, in, Mockito.mock(ComponentLog.class))) { + final RecordSchema schema = reader.getSchema(); + + final List fieldNames = schema.getFieldNames(); + final List expectedFieldNames = Arrays.asList(new String[] {"id", "name", "balance", "address", "city", "state", "zipCode", "country", "address2"}); + assertEquals(expectedFieldNames, fieldNames); + + final List dataTypes = schema.getDataTypes().stream().map(dt -> dt.getFieldType()).collect(Collectors.toList()); + final List expectedTypes = Arrays.asList(new RecordFieldType[] {RecordFieldType.INT, RecordFieldType.STRING, RecordFieldType.DOUBLE, RecordFieldType.STRING, + RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING}); + assertEquals(expectedTypes, dataTypes); + + final Object[] firstRecordValues = reader.nextRecord().getValues(); + Assert.assertArrayEquals(new Object[] {1, "John Doe", 4750.89, "123 My Street", "My City", "MS", "11111", "USA", null}, firstRecordValues); + + final Object[] secondRecordValues = reader.nextRecord().getValues(); + Assert.assertArrayEquals(new Object[] {2, "Jane Doe", 4820.09, "321 Your Street", "Your City", "NY", "33333", null, null}, secondRecordValues); + + final Object[] thirdRecordValues = reader.nextRecord().getValues(); + Assert.assertArrayEquals(new Object[] {3, "Jake Doe", 4751.89, "124 My Street", "My City", "MS", "11111", "USA", "Apt. #12"}, thirdRecordValues); + + assertNull(reader.nextRecord()); + } + } + + @Test + public void testPrimitiveTypeArrays() throws IOException, MalformedRecordException { + final LinkedHashMap jsonPaths = new LinkedHashMap<>(allJsonPaths); + jsonPaths.put("accountIds", JsonPath.compile("$.accountIds")); + + try (final InputStream in = new FileInputStream(new File("src/test/resources/json/primitive-type-array.json")); + final JsonPathRowRecordReader reader = new JsonPathRowRecordReader(jsonPaths, Collections.emptyMap(), in, Mockito.mock(ComponentLog.class))) { + + final RecordSchema schema = reader.getSchema(); + + final List fieldNames = schema.getFieldNames(); + final List expectedFieldNames = Arrays.asList(new String[] {"id", "name", "balance", "address", "city", "state", "zipCode", "country", "accountIds"}); + assertEquals(expectedFieldNames, fieldNames); + + final List dataTypes = schema.getDataTypes().stream().map(dt -> dt.getFieldType()).collect(Collectors.toList()); + final List expectedTypes = Arrays.asList(new RecordFieldType[] {RecordFieldType.INT, RecordFieldType.STRING, RecordFieldType.DOUBLE, RecordFieldType.STRING, + RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.ARRAY}); + assertEquals(expectedTypes, dataTypes); + + final Object[] firstRecordValues = reader.nextRecord().getValues(); + + final Object[] nonArrayValues = Arrays.copyOfRange(firstRecordValues, 0, firstRecordValues.length - 1); + Assert.assertArrayEquals(new Object[] {1, "John Doe", 4750.89D, "123 My Street", "My City", "MS", "11111", "USA"}, nonArrayValues); + + final Object lastRecord = firstRecordValues[firstRecordValues.length - 1]; + assertNotNull(lastRecord); + assertTrue(Object[].class.isAssignableFrom(lastRecord.getClass())); + + final Object[] array = (Object[]) lastRecord; + Assert.assertArrayEquals(new Object[] {1, 2, 3}, array); + + assertNull(reader.nextRecord()); + assertNull(reader.nextRecord()); + } + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/json/TestJsonTreeRowRecordReader.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/json/TestJsonTreeRowRecordReader.java new file mode 100644 index 000000000..c5ee0e303 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/json/TestJsonTreeRowRecordReader.java @@ -0,0 +1,266 @@ +/* + * 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.json; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.serialization.MalformedRecordException; +import org.apache.nifi.serialization.record.DataType; +import org.apache.nifi.serialization.record.Record; +import org.apache.nifi.serialization.record.RecordFieldType; +import org.apache.nifi.serialization.record.RecordSchema; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +public class TestJsonTreeRowRecordReader { + + @Test + public void testReadArray() throws IOException, MalformedRecordException { + try (final InputStream in = new FileInputStream(new File("src/test/resources/json/bank-account-array.json")); + final JsonTreeRowRecordReader reader = new JsonTreeRowRecordReader(in, Mockito.mock(ComponentLog.class), Collections.emptyMap())) { + + final RecordSchema schema = reader.getSchema(); + + final List fieldNames = schema.getFieldNames(); + final List expectedFieldNames = Arrays.asList(new String[] {"id", "name", "balance", "address", "city", "state", "zipCode", "country"}); + assertEquals(expectedFieldNames, fieldNames); + + final List dataTypes = schema.getDataTypes().stream().map(dt -> dt.getFieldType()).collect(Collectors.toList()); + final List expectedTypes = Arrays.asList(new RecordFieldType[] {RecordFieldType.INT, RecordFieldType.STRING, + RecordFieldType.DOUBLE, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING}); + assertEquals(expectedTypes, dataTypes); + + final Object[] firstRecordValues = reader.nextRecord().getValues(); + Assert.assertArrayEquals(new Object[] {1, "John Doe", 4750.89, "123 My Street", "My City", "MS", "11111", "USA"}, firstRecordValues); + + final Object[] secondRecordValues = reader.nextRecord().getValues(); + Assert.assertArrayEquals(new Object[] {2, "Jane Doe", 4820.09, "321 Your Street", "Your City", "NY", "33333", "USA"}, secondRecordValues); + + assertNull(reader.nextRecord()); + } + } + + @Test + public void testSingleJsonElement() throws IOException, MalformedRecordException { + try (final InputStream in = new FileInputStream(new File("src/test/resources/json/single-bank-account.json")); + final JsonTreeRowRecordReader reader = new JsonTreeRowRecordReader(in, Mockito.mock(ComponentLog.class), Collections.emptyMap())) { + + final RecordSchema schema = reader.getSchema(); + + final List fieldNames = schema.getFieldNames(); + final List expectedFieldNames = Arrays.asList(new String[] {"id", "name", "balance", "address", "city", "state", "zipCode", "country"}); + assertEquals(expectedFieldNames, fieldNames); + + final List dataTypes = schema.getDataTypes().stream().map(dt -> dt.getFieldType()).collect(Collectors.toList()); + final List expectedTypes = Arrays.asList(new RecordFieldType[] {RecordFieldType.INT, RecordFieldType.STRING, + RecordFieldType.DOUBLE, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING}); + assertEquals(expectedTypes, dataTypes); + + final Object[] firstRecordValues = reader.nextRecord().getValues(); + Assert.assertArrayEquals(new Object[] {1, "John Doe", 4750.89, "123 My Street", "My City", "MS", "11111", "USA"}, firstRecordValues); + + assertNull(reader.nextRecord()); + } + } + + @Test + public void testElementWithNestedData() throws IOException, MalformedRecordException { + try (final InputStream in = new FileInputStream(new File("src/test/resources/json/single-element-nested.json")); + final JsonTreeRowRecordReader reader = new JsonTreeRowRecordReader(in, Mockito.mock(ComponentLog.class), Collections.emptyMap())) { + + final RecordSchema schema = reader.getSchema(); + + final List fieldNames = schema.getFieldNames(); + final List expectedFieldNames = Arrays.asList(new String[] {"id", "name", "address", "city", "state", "zipCode", "country", "account"}); + assertEquals(expectedFieldNames, fieldNames); + + final List dataTypes = schema.getDataTypes().stream().map(dt -> dt.getFieldType()).collect(Collectors.toList()); + final List expectedTypes = Arrays.asList(new RecordFieldType[] {RecordFieldType.INT, RecordFieldType.STRING, + RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.RECORD}); + assertEquals(expectedTypes, dataTypes); + + final Object[] firstRecordValues = reader.nextRecord().getValues(); + final Object[] allButLast = Arrays.copyOfRange(firstRecordValues, 0, firstRecordValues.length - 1); + Assert.assertArrayEquals(new Object[] {1, "John Doe", "123 My Street", "My City", "MS", "11111", "USA"}, allButLast); + + final Object last = firstRecordValues[firstRecordValues.length - 1]; + assertTrue(Record.class.isAssignableFrom(last.getClass())); + final Record record = (Record) last; + assertEquals(42, record.getValue("id")); + assertEquals(4750.89, record.getValue("balance")); + + assertNull(reader.nextRecord()); + } + } + + @Test + public void testElementWithNestedArray() throws IOException, MalformedRecordException { + try (final InputStream in = new FileInputStream(new File("src/test/resources/json/single-element-nested-array.json")); + final JsonTreeRowRecordReader reader = new JsonTreeRowRecordReader(in, Mockito.mock(ComponentLog.class), Collections.emptyMap())) { + + final RecordSchema schema = reader.getSchema(); + + final List fieldNames = schema.getFieldNames(); + final List expectedFieldNames = Arrays.asList(new String[] { + "id", "name", "address", "city", "state", "zipCode", "country", "accounts"}); + assertEquals(expectedFieldNames, fieldNames); + + final List dataTypes = schema.getDataTypes().stream().map(dt -> dt.getFieldType()).collect(Collectors.toList()); + final List expectedTypes = Arrays.asList(new RecordFieldType[] {RecordFieldType.INT, RecordFieldType.STRING, + RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.ARRAY}); + assertEquals(expectedTypes, dataTypes); + + final Object[] firstRecordValues = reader.nextRecord().getValues(); + final Object[] nonArrayValues = Arrays.copyOfRange(firstRecordValues, 0, firstRecordValues.length - 1); + Assert.assertArrayEquals(new Object[] {1, "John Doe", "123 My Street", "My City", "MS", "11111", "USA"}, nonArrayValues); + + final Object lastRecord = firstRecordValues[firstRecordValues.length - 1]; + assertTrue(Object[].class.isAssignableFrom(lastRecord.getClass())); + + assertNull(reader.nextRecord()); + } + } + + @Test + public void testReadArrayDifferentSchemas() throws IOException, MalformedRecordException { + try (final InputStream in = new FileInputStream(new File("src/test/resources/json/bank-account-array-different-schemas.json")); + final JsonTreeRowRecordReader reader = new JsonTreeRowRecordReader(in, Mockito.mock(ComponentLog.class), Collections.emptyMap())) { + + final RecordSchema schema = reader.getSchema(); + + final List fieldNames = schema.getFieldNames(); + final List expectedFieldNames = Arrays.asList(new String[] {"id", "name", "balance", "address", "city", "state", "zipCode", "country"}); + assertEquals(expectedFieldNames, fieldNames); + + final List dataTypes = schema.getDataTypes().stream().map(dt -> dt.getFieldType()).collect(Collectors.toList()); + final List expectedTypes = Arrays.asList(new RecordFieldType[] {RecordFieldType.INT, RecordFieldType.STRING, + RecordFieldType.DOUBLE, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING}); + assertEquals(expectedTypes, dataTypes); + + final Object[] firstRecordValues = reader.nextRecord().getValues(); + Assert.assertArrayEquals(new Object[] {1, "John Doe", 4750.89, "123 My Street", "My City", "MS", "11111", "USA"}, firstRecordValues); + + final Object[] secondRecordValues = reader.nextRecord().getValues(); + Assert.assertArrayEquals(new Object[] {2, "Jane Doe", 4820.09, "321 Your Street", "Your City", "NY", "33333", null}, secondRecordValues); + + final Object[] thirdRecordValues = reader.nextRecord().getValues(); + Assert.assertArrayEquals(new Object[] {3, "Jake Doe", 4751.89, "124 My Street", "My City", "MS", "11111", "USA"}, thirdRecordValues); + + assertNull(reader.nextRecord()); + } + } + + @Test + public void testReadArrayDifferentSchemasWithOverride() throws IOException, MalformedRecordException { + final Map overrides = new HashMap<>(); + overrides.put("address2", RecordFieldType.STRING.getDataType()); + + try (final InputStream in = new FileInputStream(new File("src/test/resources/json/bank-account-array-different-schemas.json")); + final JsonTreeRowRecordReader reader = new JsonTreeRowRecordReader(in, Mockito.mock(ComponentLog.class), overrides)) { + + final RecordSchema schema = reader.getSchema(); + + final List fieldNames = schema.getFieldNames(); + final List expectedFieldNames = Arrays.asList(new String[] {"id", "name", "balance", "address", "city", "state", "zipCode", "country", "address2"}); + assertEquals(expectedFieldNames, fieldNames); + + final List dataTypes = schema.getDataTypes().stream().map(dt -> dt.getFieldType()).collect(Collectors.toList()); + final List expectedTypes = Arrays.asList(new RecordFieldType[] {RecordFieldType.INT, RecordFieldType.STRING, RecordFieldType.DOUBLE, RecordFieldType.STRING, + RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING}); + assertEquals(expectedTypes, dataTypes); + + final Object[] firstRecordValues = reader.nextRecord().getValues(); + Assert.assertArrayEquals(new Object[] {1, "John Doe", 4750.89, "123 My Street", "My City", "MS", "11111", "USA", null}, firstRecordValues); + + final Object[] secondRecordValues = reader.nextRecord().getValues(); + Assert.assertArrayEquals(new Object[] {2, "Jane Doe", 4820.09, "321 Your Street", "Your City", "NY", "33333", null, null}, secondRecordValues); + + final Object[] thirdRecordValues = reader.nextRecord().getValues(); + Assert.assertArrayEquals(new Object[] {3, "Jake Doe", 4751.89, "124 My Street", "My City", "MS", "11111", "USA", "Apt. #12"}, thirdRecordValues); + + assertNull(reader.nextRecord()); + } + } + + @Test + public void testReadArrayDifferentSchemasWithOptionalElementOverridden() throws IOException, MalformedRecordException { + final Map overrides = new HashMap<>(); + overrides.put("balance", RecordFieldType.DOUBLE.getDataType()); + + try (final InputStream in = new FileInputStream(new File("src/test/resources/json/bank-account-array-optional-balance.json")); + final JsonTreeRowRecordReader reader = new JsonTreeRowRecordReader(in, Mockito.mock(ComponentLog.class), overrides)) { + + final RecordSchema schema = reader.getSchema(); + + final List fieldNames = schema.getFieldNames(); + final List expectedFieldNames = Arrays.asList(new String[] {"id", "name", "balance", "address", "city", "state", "zipCode", "country"}); + assertEquals(expectedFieldNames, fieldNames); + + final List dataTypes = schema.getDataTypes().stream().map(dt -> dt.getFieldType()).collect(Collectors.toList()); + final List expectedTypes = Arrays.asList(new RecordFieldType[] {RecordFieldType.INT, RecordFieldType.STRING, RecordFieldType.DOUBLE, RecordFieldType.STRING, + RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING, RecordFieldType.STRING}); + assertEquals(expectedTypes, dataTypes); + + final Object[] firstRecordValues = reader.nextRecord().getValues(); + Assert.assertArrayEquals(new Object[] {1, "John Doe", 4750.89, "123 My Street", "My City", "MS", "11111", "USA"}, firstRecordValues); + + final Object[] secondRecordValues = reader.nextRecord().getValues(); + Assert.assertArrayEquals(new Object[] {2, "Jane Doe", null, "321 Your Street", "Your City", "NY", "33333", "USA"}, secondRecordValues); + + final Object[] thirdRecordValues = reader.nextRecord().getValues(); + Assert.assertArrayEquals(new Object[] {3, "Jimmy Doe", null, "321 Your Street", "Your City", "NY", "33333", "USA"}, thirdRecordValues); + + assertNull(reader.nextRecord()); + } + } + + + @Test + public void testReadUnicodeCharacters() throws IOException, MalformedRecordException { + try (final InputStream in = new FileInputStream(new File("src/test/resources/json/json-with-unicode.json")); + final JsonTreeRowRecordReader reader = new JsonTreeRowRecordReader(in, Mockito.mock(ComponentLog.class), Collections.emptyMap())) { + + final Object[] firstRecordValues = reader.nextRecord().getValues(); + + final Object secondValue = firstRecordValues[1]; + assertTrue(secondValue instanceof Long); + assertEquals(832036744985577473L, secondValue); + + final Object unicodeValue = firstRecordValues[2]; + assertEquals("\u3061\u3083\u6ce3\u304d\u305d\u3046", unicodeValue); + + assertNull(reader.nextRecord()); + } + } + +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/json/TestWriteJsonResult.java b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/json/TestWriteJsonResult.java new file mode 100644 index 000000000..f9849ba6d --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/json/TestWriteJsonResult.java @@ -0,0 +1,102 @@ +/* + * 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.json; + +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.serialization.SimpleRecordSchema; +import org.apache.nifi.serialization.record.DataType; +import org.apache.nifi.serialization.record.MapRecord; +import org.apache.nifi.serialization.record.Record; +import org.apache.nifi.serialization.record.RecordField; +import org.apache.nifi.serialization.record.RecordFieldType; +import org.apache.nifi.serialization.record.RecordSchema; +import org.apache.nifi.serialization.record.RecordSet; +import org.junit.Test; +import org.mockito.Mockito; + +public class TestWriteJsonResult { + + @Test + public void testDataTypes() throws IOException, ParseException { + final WriteJsonResult writer = new WriteJsonResult(Mockito.mock(ComponentLog.class), true, RecordFieldType.DATE.getDefaultFormat(), + RecordFieldType.TIME.getDefaultFormat(), RecordFieldType.TIMESTAMP.getDefaultFormat()); + + final List fields = new ArrayList<>(); + for (final RecordFieldType fieldType : RecordFieldType.values()) { + if (fieldType == RecordFieldType.CHOICE) { + final List possibleTypes = new ArrayList<>(); + possibleTypes.add(RecordFieldType.INT.getDataType()); + possibleTypes.add(RecordFieldType.LONG.getDataType()); + + fields.add(new RecordField(fieldType.name().toLowerCase(), fieldType.getDataType(possibleTypes))); + } else { + fields.add(new RecordField(fieldType.name().toLowerCase(), fieldType.getDataType())); + } + } + final RecordSchema schema = new SimpleRecordSchema(fields); + + final long time = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS").parse("2017/01/01 17:00:00.000").getTime(); + final Map valueMap = new LinkedHashMap<>(); + valueMap.put("string", "string"); + valueMap.put("boolean", true); + valueMap.put("byte", (byte) 1); + valueMap.put("char", 'c'); + valueMap.put("short", (short) 8); + valueMap.put("int", 9); + valueMap.put("bigint", BigInteger.valueOf(8L)); + valueMap.put("long", 8L); + valueMap.put("float", 8.0F); + valueMap.put("double", 8.0D); + valueMap.put("date", new Date(time)); + valueMap.put("time", new Time(time)); + valueMap.put("timestamp", new Timestamp(time)); + valueMap.put("record", null); + valueMap.put("array", null); + valueMap.put("choice", 48L); + + final Record record = new MapRecord(schema, valueMap); + final RecordSet rs = RecordSet.of(schema, record); + + final String output; + try (final ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + writer.write(rs, baos); + output = baos.toString(); + } + + final String expected = new String(Files.readAllBytes(Paths.get("src/test/resources/json/output/dataTypes.json"))); + assertEquals(expected, output); + } + +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/csv/extra-white-space.csv b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/csv/extra-white-space.csv new file mode 100644 index 000000000..453108302 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/csv/extra-white-space.csv @@ -0,0 +1,9 @@ +id, name, balance, address, city, state, zipCode, country +1, John Doe, "4750.89", "123 My Street", My City, MS, 11111, USA + + + + + 2, Jane Doe, 4820.09, 321 Your Street, Your City, NY, 33333, USA + + diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/csv/multi-bank-account.csv b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/csv/multi-bank-account.csv new file mode 100644 index 000000000..9f761c635 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/csv/multi-bank-account.csv @@ -0,0 +1,3 @@ +id, name, balance, address, city, state, zipCode, country +1, John Doe, "4750.89", "123 My Street", My City, MS, 11111, USA +2, Jane Doe, 4820.09, 321 Your Street, Your City, NY, 33333, USA \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/csv/single-bank-account.csv b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/csv/single-bank-account.csv new file mode 100644 index 000000000..11ce6d493 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/csv/single-bank-account.csv @@ -0,0 +1,2 @@ +id, name, balance, address, city, state, zipCode, country +1, John Doe, "4750.89", "123 My Street", My City, MS, 11111, USA \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/grok/error-with-stack-trace.log b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/grok/error-with-stack-trace.log new file mode 100644 index 000000000..43f1b5637 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/grok/error-with-stack-trace.log @@ -0,0 +1,25 @@ +2016-11-23 16:00:00,000 INFO message without stack trace +2016-11-23 16:00:02,689 ERROR Log message with stack trace +org.apache.nifi.exception.UnitTestException: Testing to ensure we are able to capture stack traces + at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT] + at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT] + at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT] + at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT] + at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT] + at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT] + at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT] + at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_45] + at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308) [na:1.8.0_45] + at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180) [na:1.8.0_45] + at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294) [na:1.8.0_45] + at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_45] + at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_45] + at java.lang.Thread.run(Thread.java:745) [na:1.8.0_45] +Caused by: org.apache.nifi.exception.UnitTestException: Testing to ensure we are able to capture stack traces + at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT] + at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT] + at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT] + at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT] + at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT] + ... 12 common frames omitted +2016-11-23 16:05:00,000 INFO message without stack trace diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/grok/nifi-log-sample-multiline-with-stacktrace.log b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/grok/nifi-log-sample-multiline-with-stacktrace.log new file mode 100644 index 000000000..673908f05 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/grok/nifi-log-sample-multiline-with-stacktrace.log @@ -0,0 +1,29 @@ +2016-08-04 13:26:32,473 INFO [Leader Election Notification Thread-1] o.a.n.c.l.e.CuratorLeaderElectionManager org.apache.nifi.controller.leader.election.CuratorLeaderElectionManager$ElectionListener@1fa27ea5 has been interrupted; no longer leader for role 'Cluster Coordinator' +2016-08-04 13:26:32,473 INFO [Leader Election Notification Thread-1] o.a.n.c.l.e.CuratorLeaderElectionManager org.apache.nifi.controller.leader.election.CuratorLeaderElectionManager$ElectionListener@1fa27ea5 This node is no longer leader for role 'Cluster Coordinator' +2016-08-04 13:26:32,474 ERROR [Leader Election Notification Thread-2] o.apache.nifi.controller.FlowController One +Two +Three +org.apache.nifi.exception.UnitTestException: Testing to ensure we are able to capture stack traces + at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT] + at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT] + at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT] + at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT] + at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT] + at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT] + at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT] + at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_45] + at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308) [na:1.8.0_45] + at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180) [na:1.8.0_45] + at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294) [na:1.8.0_45] + at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_45] + at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_45] + at java.lang.Thread.run(Thread.java:745) [na:1.8.0_45] +Caused by: org.apache.nifi.exception.UnitTestException: Testing to ensure we are able to capture stack traces + at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT] + at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT] + at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT] + at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT] + at org.apache.nifi.cluster.coordination.node.NodeClusterCoordinator.getElectedActiveCoordinatorAddress(NodeClusterCoordinator.java:185) [nifi-framework-cluster-1.0.0-SNAPSHOT.jar:1.0.0-SNAPSHOT] + ... 12 common frames omitted +2016-08-04 13:26:35,475 WARN [Curator-Framework-0] org.apache.curator.ConnectionState Connection attempt unsuccessful after 3008 (greater than max timeout of 3000). Resetting connection and trying again with a new connection. +2016-08-04 13:26:35,479 WARN [Curator-Framework-0] org.apache.curator.ConnectionState Connection attempt unsuccessful after 3007 (greater than max timeout of 3000). Resetting connection and trying again with a new connection. \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/grok/nifi-log-sample.log b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/grok/nifi-log-sample.log new file mode 100644 index 000000000..e4c738512 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/grok/nifi-log-sample.log @@ -0,0 +1,5 @@ +2016-08-04 13:26:32,473 INFO [Leader Election Notification Thread-1] o.a.n.c.l.e.CuratorLeaderElectionManager org.apache.nifi.controller.leader.election.CuratorLeaderElectionManager$ElectionListener@1fa27ea5 has been interrupted; no longer leader for role 'Cluster Coordinator' +2016-08-04 13:26:32,473 INFO [Leader Election Notification Thread-1] o.a.n.c.l.e.CuratorLeaderElectionManager org.apache.nifi.controller.leader.election.CuratorLeaderElectionManager$ElectionListener@1fa27ea5 This node is no longer leader for role 'Cluster Coordinator' +2016-08-04 13:26:32,474 INFO [Leader Election Notification Thread-2] o.apache.nifi.controller.FlowController This node is no longer Primary Node +2016-08-04 13:26:35,475 WARN [Curator-Framework-0] org.apache.curator.ConnectionState Connection attempt unsuccessful after 3008 (greater than max timeout of 3000). Resetting connection and trying again with a new connection. +2016-08-04 13:26:35,479 WARN [Curator-Framework-0] org.apache.curator.ConnectionState Connection attempt unsuccessful after 3007 (greater than max timeout of 3000). Resetting connection and trying again with a new connection. \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/grok/single-line-log-messages.txt b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/grok/single-line-log-messages.txt new file mode 100644 index 000000000..737ad95fd --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/grok/single-line-log-messages.txt @@ -0,0 +1,5 @@ +2016-11-08 21:24:23,029 INFO Test Message 1 +2016-11-08 21:24:23,029 WARN Red +2016-11-08 21:24:23,029 ERROR Green +2016-11-08 21:24:23,029 FATAL Blue +2016-11-08 21:24:23,029 FINE Yellow \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/bank-account-array-different-schemas.json b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/bank-account-array-different-schemas.json new file mode 100644 index 000000000..e79a6d791 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/bank-account-array-different-schemas.json @@ -0,0 +1,30 @@ +[ + { + "id": 1, + "name": "John Doe", + "balance": 4750.89, + "address": "123 My Street", + "city": "My City", + "state": "MS", + "zipCode": "11111", + "country": "USA" + }, { + "id": 2, + "name": "Jane Doe", + "balance": 4820.09, + "address": "321 Your Street", + "city": "Your City", + "state": "NY", + "zipCode": "33333" + }, { + "id": 3, + "name": "Jake Doe", + "balance": 4751.89, + "address": "124 My Street", + "address2": "Apt. #12", + "city": "My City", + "state": "MS", + "zipCode": "11111", + "country": "USA" + } +] \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/bank-account-array-optional-balance.json b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/bank-account-array-optional-balance.json new file mode 100644 index 000000000..cb614f14c --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/bank-account-array-optional-balance.json @@ -0,0 +1,29 @@ +[ + { + "id": 1, + "name": "John Doe", + "balance": 4750.89, + "address": "123 My Street", + "city": "My City", + "state": "MS", + "zipCode": "11111", + "country": "USA" + }, { + "id": 2, + "name": "Jane Doe", + "balance": null, + "address": "321 Your Street", + "city": "Your City", + "state": "NY", + "zipCode": "33333", + "country": "USA" + }, { + "id": 3, + "name": "Jimmy Doe", + "address": "321 Your Street", + "city": "Your City", + "state": "NY", + "zipCode": "33333", + "country": "USA" + } +] \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/bank-account-array.json b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/bank-account-array.json new file mode 100644 index 000000000..d821cc19b --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/bank-account-array.json @@ -0,0 +1,21 @@ +[ + { + "id": 1, + "name": "John Doe", + "balance": 4750.89, + "address": "123 My Street", + "city": "My City", + "state": "MS", + "zipCode": "11111", + "country": "USA" + }, { + "id": 2, + "name": "Jane Doe", + "balance": 4820.09, + "address": "321 Your Street", + "city": "Your City", + "state": "NY", + "zipCode": "33333", + "country": "USA" + } +] \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/json-with-unicode.json b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/json-with-unicode.json new file mode 100644 index 000000000..880fabf89 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/json-with-unicode.json @@ -0,0 +1,9 @@ +{ + "created_at":"Thu Feb 16 01:19:42 +0000 2017", + "id":832036744985577473, + "unicode":"\u3061\u3083\u6ce3\u304d\u305d\u3046", + "from":{ + "id":788946702264507903, + "name":"john" + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/output/dataTypes.json b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/output/dataTypes.json new file mode 100644 index 000000000..40c28dd38 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/output/dataTypes.json @@ -0,0 +1,18 @@ +[ { + "string" : "string", + "boolean" : true, + "byte" : 1, + "char" : "c", + "short" : 8, + "int" : 9, + "bigint" : 8, + "long" : 8, + "float" : 8.0, + "double" : 8.0, + "date" : "2017-01-01", + "time" : "17:00:00", + "timestamp" : "2017-01-01 17:00:00", + "record" : null, + "choice" : 48, + "array" : null +} ] \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/primitive-type-array.json b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/primitive-type-array.json new file mode 100644 index 000000000..47e327628 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/primitive-type-array.json @@ -0,0 +1,13 @@ +[ + { + "id": 1, + "name": "John Doe", + "balance": 4750.89, + "address": "123 My Street", + "city": "My City", + "state": "MS", + "zipCode": "11111", + "country": "USA", + "accountIds": [1, 2, 3] + } +] \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/single-bank-account.json b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/single-bank-account.json new file mode 100644 index 000000000..a8d68903c --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/single-bank-account.json @@ -0,0 +1,10 @@ +{ + "id": 1, + "name": "John Doe", + "balance": 4750.89, + "address": "123 My Street", + "city": "My City", + "state": "MS", + "zipCode": "11111", + "country": "USA" +} \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/single-element-nested-array.json b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/single-element-nested-array.json new file mode 100644 index 000000000..0bca3a072 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/single-element-nested-array.json @@ -0,0 +1,16 @@ +{ + "id": 1, + "name": "John Doe", + "address": "123 My Street", + "city": "My City", + "state": "MS", + "zipCode": "11111", + "country": "USA", + "accounts": [{ + "id": 42, + "balance": 4750.89 + }, { + "id": 43, + "balance": 48212.38 + }] +} \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/single-element-nested.json b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/single-element-nested.json new file mode 100644 index 000000000..26a59e1a1 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/resources/json/single-element-nested.json @@ -0,0 +1,13 @@ +{ + "id": 1, + "name": "John Doe", + "address": "123 My Street", + "city": "My City", + "state": "MS", + "zipCode": "11111", + "country": "USA", + "account": { + "id": 42, + "balance": 4750.89 + } +} \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/pom.xml b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/pom.xml new file mode 100644 index 000000000..b1ac470bf --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + org.apache.nifi + nifi-standard-services + 1.2.0-SNAPSHOT + + + nifi-record-serialization-services-bundle + pom + + + nifi-record-serialization-services + nifi-record-serialization-services-nar + + diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-standard-services-api-nar/pom.xml b/nifi-nar-bundles/nifi-standard-services/nifi-standard-services-api-nar/pom.xml index c6e61289a..eae351527 100644 --- a/nifi-nar-bundles/nifi-standard-services/nifi-standard-services-api-nar/pom.xml +++ b/nifi-nar-bundles/nifi-standard-services/nifi-standard-services-api-nar/pom.xml @@ -56,5 +56,10 @@ nifi-hbase-client-service-api compile + + org.apache.nifi + nifi-record-serialization-service-api + compile + diff --git a/nifi-nar-bundles/nifi-standard-services/pom.xml b/nifi-nar-bundles/nifi-standard-services/pom.xml index 7e754be7d..3948a1be5 100644 --- a/nifi-nar-bundles/nifi-standard-services/pom.xml +++ b/nifi-nar-bundles/nifi-standard-services/pom.xml @@ -35,5 +35,7 @@ nifi-dbcp-service-bundle nifi-hbase-client-service-api nifi-hbase_1_1_2-client-service-bundle + nifi-record-serialization-service-api + nifi-record-serialization-services-bundle diff --git a/pom.xml b/pom.xml index 449450dff..057832b41 100644 --- a/pom.xml +++ b/pom.xml @@ -964,6 +964,11 @@ language governing permissions and limitations under the License. --> nifi-ssl-context-service-api 1.2.0-SNAPSHOT + + org.apache.nifi + nifi-record-serialization-service-api + 1.2.0-SNAPSHOT + org.apache.nifi nifi-distributed-cache-services-nar @@ -1292,6 +1297,12 @@ language governing permissions and limitations under the License. --> nifi-site-to-site-reporting-nar 1.2.0-SNAPSHOT nar + + + org.apache.nifi + nifi-record-serialization-services-nar + 1.2.0-SNAPSHOT + nar org.apache.nifi