http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-maven-archetypes/nifi-service-bundle-archetype/src/main/resources/archetype-resources/nifi-__artifactBaseName__/pom.xml
----------------------------------------------------------------------
diff --git 
a/nifi-maven-archetypes/nifi-service-bundle-archetype/src/main/resources/archetype-resources/nifi-__artifactBaseName__/pom.xml
 
b/nifi-maven-archetypes/nifi-service-bundle-archetype/src/main/resources/archetype-resources/nifi-__artifactBaseName__/pom.xml
new file mode 100644
index 0000000..fdbb58b
--- /dev/null
+++ 
b/nifi-maven-archetypes/nifi-service-bundle-archetype/src/main/resources/archetype-resources/nifi-__artifactBaseName__/pom.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements. See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License. You may obtain a copy of the License at
+  http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>${groupId}</groupId>
+        <artifactId>${rootArtifactId}</artifactId>
+        <version>${version}</version>
+    </parent>
+
+    <artifactId>nifi-${artifactBaseName}</artifactId>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>${groupId}</groupId>
+            <artifactId>nifi-${artifactBaseName}-api</artifactId>
+            <version>${version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-processor-utils</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-mock</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-maven-archetypes/nifi-service-bundle-archetype/src/main/resources/archetype-resources/nifi-__artifactBaseName__/src/main/java/StandardMyService.java
----------------------------------------------------------------------
diff --git 
a/nifi-maven-archetypes/nifi-service-bundle-archetype/src/main/resources/archetype-resources/nifi-__artifactBaseName__/src/main/java/StandardMyService.java
 
b/nifi-maven-archetypes/nifi-service-bundle-archetype/src/main/resources/archetype-resources/nifi-__artifactBaseName__/src/main/java/StandardMyService.java
new file mode 100644
index 0000000..193e6b7
--- /dev/null
+++ 
b/nifi-maven-archetypes/nifi-service-bundle-archetype/src/main/resources/archetype-resources/nifi-__artifactBaseName__/src/main/java/StandardMyService.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 ${package};
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.nifi.annotation.documentation.CapabilityDescription;
+import org.apache.nifi.annotation.documentation.Tags;
+import org.apache.nifi.annotation.lifecycle.OnDisabled;
+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.processor.exception.ProcessException;
+import org.apache.nifi.processor.util.StandardValidators;
+import org.apache.nifi.reporting.InitializationException;
+
+@Tags({ "example"})
+@CapabilityDescription("Example ControllerService implementation of 
MyService.")
+public class StandardMyService extends AbstractControllerService implements 
MyService {
+
+    public static final PropertyDescriptor MY_PROPERTY = new PropertyDescriptor
+            .Builder().name("My Property")
+            .description("Example Property")
+            .required(true)
+            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+            .build();
+
+    private static final List<PropertyDescriptor> properties;
+
+    static {
+        final List<PropertyDescriptor> props = new ArrayList<>();
+        props.add(MY_PROPERTY);
+        properties = Collections.unmodifiableList(props);
+    }
+
+    @Override
+    protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+        return properties;
+    }
+
+    /**
+     * @param context
+     *            the configuration context
+     * @throws InitializationException
+     *             if unable to create a database connection
+     */
+    @OnEnabled
+    public void onEnabled(final ConfigurationContext context) throws 
InitializationException {
+
+    }
+
+    @OnDisabled
+    public void shutdown() {
+
+    }
+
+    @Override
+    public void execute() throws ProcessException {
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-maven-archetypes/nifi-service-bundle-archetype/src/main/resources/archetype-resources/nifi-__artifactBaseName__/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService
----------------------------------------------------------------------
diff --git 
a/nifi-maven-archetypes/nifi-service-bundle-archetype/src/main/resources/archetype-resources/nifi-__artifactBaseName__/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService
 
b/nifi-maven-archetypes/nifi-service-bundle-archetype/src/main/resources/archetype-resources/nifi-__artifactBaseName__/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService
new file mode 100644
index 0000000..811fcd3
--- /dev/null
+++ 
b/nifi-maven-archetypes/nifi-service-bundle-archetype/src/main/resources/archetype-resources/nifi-__artifactBaseName__/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService
@@ -0,0 +1,15 @@
+# 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}.StandardMyService
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-maven-archetypes/nifi-service-bundle-archetype/src/main/resources/archetype-resources/nifi-__artifactBaseName__/src/test/java/TestProcessor.java
----------------------------------------------------------------------
diff --git 
a/nifi-maven-archetypes/nifi-service-bundle-archetype/src/main/resources/archetype-resources/nifi-__artifactBaseName__/src/test/java/TestProcessor.java
 
b/nifi-maven-archetypes/nifi-service-bundle-archetype/src/main/resources/archetype-resources/nifi-__artifactBaseName__/src/test/java/TestProcessor.java
new file mode 100644
index 0000000..be00a70
--- /dev/null
+++ 
b/nifi-maven-archetypes/nifi-service-bundle-archetype/src/main/resources/archetype-resources/nifi-__artifactBaseName__/src/test/java/TestProcessor.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 ${package};
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.processor.AbstractProcessor;
+import org.apache.nifi.processor.ProcessContext;
+import org.apache.nifi.processor.ProcessSession;
+import org.apache.nifi.processor.exception.ProcessException;
+
+public class TestProcessor extends AbstractProcessor {
+
+    @Override
+    public void onTrigger(ProcessContext context, ProcessSession session) 
throws ProcessException {
+    }
+
+    @Override
+    protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+        List<PropertyDescriptor> propDescs = new ArrayList<>();
+        propDescs.add(new PropertyDescriptor.Builder()
+                .name("MyService test processor")
+                .description("MyService test processor")
+                .identifiesControllerService(MyService.class)
+                .required(true)
+                .build());
+        return propDescs;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-maven-archetypes/nifi-service-bundle-archetype/src/main/resources/archetype-resources/nifi-__artifactBaseName__/src/test/java/TestStandardMyService.java
----------------------------------------------------------------------
diff --git 
a/nifi-maven-archetypes/nifi-service-bundle-archetype/src/main/resources/archetype-resources/nifi-__artifactBaseName__/src/test/java/TestStandardMyService.java
 
b/nifi-maven-archetypes/nifi-service-bundle-archetype/src/main/resources/archetype-resources/nifi-__artifactBaseName__/src/test/java/TestStandardMyService.java
new file mode 100644
index 0000000..3f430e6
--- /dev/null
+++ 
b/nifi-maven-archetypes/nifi-service-bundle-archetype/src/main/resources/archetype-resources/nifi-__artifactBaseName__/src/test/java/TestStandardMyService.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 ${package};
+
+import org.apache.nifi.reporting.InitializationException;
+import org.apache.nifi.util.TestRunner;
+import org.apache.nifi.util.TestRunners;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestStandardMyService {
+
+    @Before
+    public void init() {
+
+    }
+
+    @Test
+    public void testService() throws InitializationException {
+        final TestRunner runner = 
TestRunners.newTestRunner(TestProcessor.class);
+        final StandardMyService service = new StandardMyService();
+        runner.addControllerService("test-good", service);
+
+        runner.setProperty(service, StandardMyService.MY_PROPERTY, 
"test-value");
+        runner.enableControllerService(service);
+
+        runner.assertValid(service);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-maven-archetypes/nifi-service-bundle-archetype/src/main/resources/archetype-resources/pom.xml
----------------------------------------------------------------------
diff --git 
a/nifi-maven-archetypes/nifi-service-bundle-archetype/src/main/resources/archetype-resources/pom.xml
 
b/nifi-maven-archetypes/nifi-service-bundle-archetype/src/main/resources/archetype-resources/pom.xml
new file mode 100644
index 0000000..0f32431
--- /dev/null
+++ 
b/nifi-maven-archetypes/nifi-service-bundle-archetype/src/main/resources/archetype-resources/pom.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements. See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License. You may obtain a copy of the License at
+  http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.nifi</groupId>
+        <artifactId>nifi-nar-bundles</artifactId>
+        <version>${nifiVersion}</version>
+    </parent>
+
+    <groupId>${groupId}</groupId>
+    <artifactId>${artifactId}</artifactId>
+    <version>${version}</version>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>nifi-${artifactBaseName}-api</module>
+        <module>nifi-${artifactBaseName}-api-nar</module>
+        <module>nifi-${artifactBaseName}</module>
+        <module>nifi-${artifactBaseName}-nar</module>
+    </modules>
+
+</project>

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-maven-archetypes/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-maven-archetypes/pom.xml b/nifi-maven-archetypes/pom.xml
new file mode 100644
index 0000000..b817604
--- /dev/null
+++ b/nifi-maven-archetypes/pom.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements. See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License. You may obtain a copy of the License at
+  http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.nifi</groupId>
+        <artifactId>nifi</artifactId>
+        <version>0.3.0-SNAPSHOT</version>
+    </parent>
+    <groupId>org.apache.nifi</groupId>
+    <artifactId>nifi-maven-archetypes</artifactId>
+    <packaging>pom</packaging>
+    <modules>
+        <module>nifi-processor-bundle-archetype</module>
+        <module>nifi-service-bundle-archetype</module>
+    </modules>
+</project>

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-mock/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-mock/pom.xml b/nifi-mock/pom.xml
new file mode 100644
index 0000000..8db3830
--- /dev/null
+++ b/nifi-mock/pom.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+      http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.nifi</groupId>
+        <artifactId>nifi</artifactId>
+        <version>0.3.0-SNAPSHOT</version>
+    </parent>
+    <artifactId>nifi-mock</artifactId>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-utils</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-expression-language</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-data-provenance-utils</artifactId>
+        </dependency>
+        <dependency>
+            <!-- Dependency marked as provided, not test, because we have 
assertion 
+            methods in our MockSession & MockFlowFile -->
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-mock/src/main/java/org/apache/nifi/provenance/MockProvenanceEventRepository.java
----------------------------------------------------------------------
diff --git 
a/nifi-mock/src/main/java/org/apache/nifi/provenance/MockProvenanceEventRepository.java
 
b/nifi-mock/src/main/java/org/apache/nifi/provenance/MockProvenanceEventRepository.java
new file mode 100644
index 0000000..241041a
--- /dev/null
+++ 
b/nifi-mock/src/main/java/org/apache/nifi/provenance/MockProvenanceEventRepository.java
@@ -0,0 +1,131 @@
+/*
+ * 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.provenance;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.nifi.events.EventReporter;
+import org.apache.nifi.provenance.lineage.ComputeLineageSubmission;
+import org.apache.nifi.provenance.search.Query;
+import org.apache.nifi.provenance.search.QuerySubmission;
+import org.apache.nifi.provenance.search.SearchableField;
+
+public class MockProvenanceEventRepository implements 
ProvenanceEventRepository {
+
+    private final List<ProvenanceEventRecord> records = new ArrayList<>();
+    private final AtomicLong idGenerator = new AtomicLong(0L);
+
+    @Override
+    public void registerEvents(final Iterable<ProvenanceEventRecord> events) {
+        for (final ProvenanceEventRecord event : events) {
+            registerEvent(event);
+        }
+    }
+
+    @Override
+    public void registerEvent(final ProvenanceEventRecord event) {
+        final StandardProvenanceEventRecord newRecord;
+        if (event instanceof StandardProvenanceEventRecord) {
+            newRecord = (StandardProvenanceEventRecord) event;
+        } else {
+            newRecord = new 
StandardProvenanceEventRecord.Builder().fromEvent(event).build();
+        }
+        newRecord.setEventId(idGenerator.getAndIncrement());
+
+        records.add(newRecord);
+    }
+
+    @Override
+    public void initialize(EventReporter reporter) throws IOException {
+    }
+
+    @Override
+    public List<ProvenanceEventRecord> getEvents(long firstRecordId, int 
maxRecords) throws IOException {
+        if (firstRecordId > records.size()) {
+            return Collections.emptyList();
+        }
+
+        return records.subList((int) firstRecordId, Math.min(records.size(), 
(int) (firstRecordId + maxRecords)));
+    }
+
+    @Override
+    public Long getMaxEventId() {
+        return Long.valueOf(records.size() - 1);
+    }
+
+    @Override
+    public QuerySubmission submitQuery(Query query) {
+        throw new UnsupportedOperationException("MockProvenanceEventRepository 
does not support querying");
+    }
+
+    @Override
+    public QuerySubmission retrieveQuerySubmission(String queryIdentifier) {
+        throw new UnsupportedOperationException("MockProvenanceEventRepository 
does not support querying");
+    }
+
+    @Override
+    public ComputeLineageSubmission submitLineageComputation(String 
flowFileUuid) {
+        throw new UnsupportedOperationException("MockProvenanceEventRepository 
does not support Lineage Computation");
+    }
+
+    @Override
+    public ComputeLineageSubmission retrieveLineageSubmission(String 
lineageIdentifier) {
+        throw new UnsupportedOperationException("MockProvenanceEventRepository 
does not support Lineage Computation");
+    }
+
+    @Override
+    public ProvenanceEventRecord getEvent(long id) throws IOException {
+        if (id > records.size()) {
+            return null;
+        }
+
+        return records.get((int) id);
+    }
+
+    @Override
+    public ComputeLineageSubmission submitExpandParents(long eventId) {
+        throw new UnsupportedOperationException("MockProvenanceEventRepository 
does not support Lineage Computation");
+    }
+
+    @Override
+    public ComputeLineageSubmission submitExpandChildren(long eventId) {
+        throw new UnsupportedOperationException("MockProvenanceEventRepository 
does not support Lineage Computation");
+    }
+
+    @Override
+    public void close() throws IOException {
+    }
+
+    @Override
+    public List<SearchableField> getSearchableFields() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public List<SearchableField> getSearchableAttributes() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public ProvenanceEventBuilder eventBuilder() {
+        return new StandardProvenanceEventRecord.Builder();
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-mock/src/main/java/org/apache/nifi/reporting/BulletinFactory.java
----------------------------------------------------------------------
diff --git 
a/nifi-mock/src/main/java/org/apache/nifi/reporting/BulletinFactory.java 
b/nifi-mock/src/main/java/org/apache/nifi/reporting/BulletinFactory.java
new file mode 100644
index 0000000..221bed6
--- /dev/null
+++ b/nifi-mock/src/main/java/org/apache/nifi/reporting/BulletinFactory.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.reporting;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+public class BulletinFactory {
+
+    private static final AtomicLong currentId = new AtomicLong(0);
+
+    public static Bulletin createBulletin(final String groupId, final String 
sourceId, final String sourceName, final String category, final String 
severity, final String message) {
+        final Bulletin bulletin = new 
MockBulletin(currentId.getAndIncrement());
+        bulletin.setGroupId(groupId);
+        bulletin.setSourceId(sourceId);
+        bulletin.setSourceName(sourceName);
+        bulletin.setCategory(category);
+        bulletin.setLevel(severity);
+        bulletin.setMessage(message);
+        return bulletin;
+    }
+
+    public static Bulletin createBulletin(final String category, final String 
severity, final String message) {
+        final Bulletin bulletin = new 
MockBulletin(currentId.getAndIncrement());
+        bulletin.setCategory(category);
+        bulletin.setLevel(severity);
+        bulletin.setMessage(message);
+        return bulletin;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-mock/src/main/java/org/apache/nifi/reporting/MockBulletin.java
----------------------------------------------------------------------
diff --git 
a/nifi-mock/src/main/java/org/apache/nifi/reporting/MockBulletin.java 
b/nifi-mock/src/main/java/org/apache/nifi/reporting/MockBulletin.java
new file mode 100644
index 0000000..652fd51
--- /dev/null
+++ b/nifi-mock/src/main/java/org/apache/nifi/reporting/MockBulletin.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.reporting;
+
+public class MockBulletin extends Bulletin {
+
+    protected MockBulletin(long id) {
+        super(id);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-mock/src/main/java/org/apache/nifi/util/ControllerServiceConfiguration.java
----------------------------------------------------------------------
diff --git 
a/nifi-mock/src/main/java/org/apache/nifi/util/ControllerServiceConfiguration.java
 
b/nifi-mock/src/main/java/org/apache/nifi/util/ControllerServiceConfiguration.java
new file mode 100644
index 0000000..bd623ca
--- /dev/null
+++ 
b/nifi-mock/src/main/java/org/apache/nifi/util/ControllerServiceConfiguration.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.util;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.controller.ControllerService;
+
+public class ControllerServiceConfiguration {
+
+    private final ControllerService service;
+    private final AtomicBoolean enabled = new AtomicBoolean(false);
+    private String annotationData;
+    private Map<PropertyDescriptor, String> properties = new HashMap<>();
+
+    public ControllerServiceConfiguration(final ControllerService service) {
+        this.service = service;
+    }
+
+    public ControllerService getService() {
+        return service;
+    }
+
+    public void setEnabled(final boolean enabled) {
+        this.enabled.set(enabled);
+    }
+
+    public boolean isEnabled() {
+        return this.enabled.get();
+    }
+
+    public void setProperties(final Map<PropertyDescriptor, String> props) {
+        this.properties = new HashMap<>(props);
+    }
+
+    public String getProperty(final PropertyDescriptor descriptor) {
+        final String value = properties.get(descriptor);
+        if (value == null) {
+            return descriptor.getDefaultValue();
+        } else {
+            return value;
+        }
+    }
+
+    public void setAnnotationData(final String annotationData) {
+        this.annotationData = annotationData;
+    }
+
+    public String getAnnotationData() {
+        return annotationData;
+    }
+
+    public Map<PropertyDescriptor, String> getProperties() {
+        return Collections.unmodifiableMap(properties);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-mock/src/main/java/org/apache/nifi/util/MockBulletinRepository.java
----------------------------------------------------------------------
diff --git 
a/nifi-mock/src/main/java/org/apache/nifi/util/MockBulletinRepository.java 
b/nifi-mock/src/main/java/org/apache/nifi/util/MockBulletinRepository.java
new file mode 100644
index 0000000..04fc496
--- /dev/null
+++ b/nifi-mock/src/main/java/org/apache/nifi/util/MockBulletinRepository.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.util;
+
+import java.util.List;
+
+import org.apache.nifi.reporting.Bulletin;
+import org.apache.nifi.reporting.BulletinQuery;
+import org.apache.nifi.reporting.BulletinRepository;
+
+public class MockBulletinRepository implements BulletinRepository {
+
+    @Override
+    public void addBulletin(Bulletin bulletin) {
+        // TODO: Implement
+
+    }
+
+    @Override
+    public int getControllerBulletinCapacity() {
+        // TODO: Implement
+        return 0;
+    }
+
+    @Override
+    public int getComponentBulletinCapacity() {
+        // TODO: Implement
+        return 0;
+    }
+
+    @Override
+    public List<Bulletin> findBulletins(BulletinQuery bulletinQuery) {
+        // TODO: Implement
+        return null;
+    }
+
+    @Override
+    public List<Bulletin> findBulletinsForGroupBySource(String groupId) {
+        // TODO: Implement
+        return null;
+    }
+
+    @Override
+    public List<Bulletin> findBulletinsForGroupBySource(String groupId, int 
maxPerComponent) {
+        // TODO: Implement
+        return null;
+    }
+
+    @Override
+    public List<Bulletin> findBulletinsForController() {
+        // TODO: Implement
+        return null;
+    }
+
+    @Override
+    public List<Bulletin> findBulletinsForController(int max) {
+        // TODO: Implement
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-mock/src/main/java/org/apache/nifi/util/MockConfigurationContext.java
----------------------------------------------------------------------
diff --git 
a/nifi-mock/src/main/java/org/apache/nifi/util/MockConfigurationContext.java 
b/nifi-mock/src/main/java/org/apache/nifi/util/MockConfigurationContext.java
new file mode 100644
index 0000000..c90e722
--- /dev/null
+++ b/nifi-mock/src/main/java/org/apache/nifi/util/MockConfigurationContext.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.util;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.PropertyValue;
+import org.apache.nifi.controller.ConfigurationContext;
+import org.apache.nifi.controller.ControllerService;
+import org.apache.nifi.controller.ControllerServiceLookup;
+
+public class MockConfigurationContext implements ConfigurationContext {
+
+    private final Map<PropertyDescriptor, String> properties;
+    private final ControllerServiceLookup serviceLookup;
+    private final ControllerService service;
+
+    public MockConfigurationContext(final Map<PropertyDescriptor, String> 
properties, final ControllerServiceLookup serviceLookup) {
+        this(null, properties, serviceLookup);
+    }
+
+    public MockConfigurationContext(final ControllerService service, final 
Map<PropertyDescriptor, String> properties, final ControllerServiceLookup 
serviceLookup) {
+        this.service = service;
+        this.properties = properties;
+        this.serviceLookup = serviceLookup;
+    }
+
+    @Override
+    public PropertyValue getProperty(final PropertyDescriptor property) {
+        String value = properties.get(property);
+        if (value == null) {
+            value = getActualDescriptor(property).getDefaultValue();
+        }
+        return new MockPropertyValue(value, serviceLookup);
+    }
+
+    @Override
+    public Map<PropertyDescriptor, String> getProperties() {
+        return new HashMap<>(this.properties);
+    }
+
+    private PropertyDescriptor getActualDescriptor(final PropertyDescriptor 
property) {
+        if (service == null) {
+            return property;
+        }
+
+        final PropertyDescriptor resolved = 
service.getPropertyDescriptor(property.getName());
+        return resolved == null ? property : resolved;
+    }
+
+    @Override
+    public String getSchedulingPeriod() {
+        return "0 secs";
+    }
+
+    @Override
+    public Long getSchedulingPeriod(final TimeUnit timeUnit) {
+        return 0L;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-mock/src/main/java/org/apache/nifi/util/MockControllerServiceInitializationContext.java
----------------------------------------------------------------------
diff --git 
a/nifi-mock/src/main/java/org/apache/nifi/util/MockControllerServiceInitializationContext.java
 
b/nifi-mock/src/main/java/org/apache/nifi/util/MockControllerServiceInitializationContext.java
new file mode 100644
index 0000000..bff1d62
--- /dev/null
+++ 
b/nifi-mock/src/main/java/org/apache/nifi/util/MockControllerServiceInitializationContext.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.util;
+
+import org.apache.nifi.controller.ControllerService;
+import org.apache.nifi.controller.ControllerServiceInitializationContext;
+import org.apache.nifi.controller.ControllerServiceLookup;
+import org.apache.nifi.logging.ComponentLog;
+
+public class MockControllerServiceInitializationContext extends 
MockControllerServiceLookup implements ControllerServiceInitializationContext, 
ControllerServiceLookup {
+
+    private final String identifier;
+    private final ComponentLog logger;
+
+    public MockControllerServiceInitializationContext(final ControllerService 
controllerService, final String identifier) {
+        this(controllerService, identifier, new MockProcessorLog(identifier, 
controllerService));
+    }
+
+    public MockControllerServiceInitializationContext(final ControllerService 
controllerService, final String identifier, final ComponentLog logger) {
+        this.identifier = identifier;
+        this.logger = logger;
+        addControllerService(controllerService, identifier);
+    }
+
+    @Override
+    public String getIdentifier() {
+        return identifier;
+    }
+
+    @Override
+    public String getControllerServiceName(final String serviceIdentifier) {
+        return null;
+    }
+
+    @Override
+    public ControllerServiceLookup getControllerServiceLookup() {
+        return this;
+    }
+
+    @Override
+    public ComponentLog getLogger() {
+        return logger;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-mock/src/main/java/org/apache/nifi/util/MockControllerServiceLookup.java
----------------------------------------------------------------------
diff --git 
a/nifi-mock/src/main/java/org/apache/nifi/util/MockControllerServiceLookup.java 
b/nifi-mock/src/main/java/org/apache/nifi/util/MockControllerServiceLookup.java
new file mode 100644
index 0000000..219ee24
--- /dev/null
+++ 
b/nifi-mock/src/main/java/org/apache/nifi/util/MockControllerServiceLookup.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.util;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.nifi.controller.ControllerService;
+import org.apache.nifi.controller.ControllerServiceLookup;
+
+public abstract class MockControllerServiceLookup implements 
ControllerServiceLookup {
+
+    private final Map<String, ControllerServiceConfiguration> 
controllerServiceMap = new ConcurrentHashMap<>();
+
+    public ControllerServiceConfiguration addControllerService(final 
ControllerService service, final String identifier) {
+        final ControllerServiceConfiguration config = new 
ControllerServiceConfiguration(service);
+        controllerServiceMap.put(identifier, config);
+        return config;
+    }
+
+    public ControllerServiceConfiguration addControllerService(final 
ControllerService service) {
+        return addControllerService(service, service.getIdentifier());
+    }
+
+    public void removeControllerService(ControllerService service) {
+        final ControllerService canonical = 
getControllerService(service.getIdentifier());
+        if (canonical == null || canonical != service) {
+            throw new IllegalArgumentException("Controller Service " + service 
+ " is not known");
+        }
+
+        controllerServiceMap.remove(service.getIdentifier());
+    }
+
+    protected void addControllerServices(final MockControllerServiceLookup 
other) {
+        this.controllerServiceMap.putAll(other.controllerServiceMap);
+    }
+
+    protected ControllerServiceConfiguration getConfiguration(final String 
identifier) {
+        return controllerServiceMap.get(identifier);
+    }
+
+    @Override
+    public ControllerService getControllerService(final String identifier) {
+        final ControllerServiceConfiguration status = 
controllerServiceMap.get(identifier);
+        return (status == null) ? null : status.getService();
+    }
+
+    @Override
+    public boolean isControllerServiceEnabled(final String serviceIdentifier) {
+        final ControllerServiceConfiguration status = 
controllerServiceMap.get(serviceIdentifier);
+        if (status == null) {
+            throw new IllegalArgumentException("No ControllerService exists 
with identifier " + serviceIdentifier);
+        }
+
+        return status.isEnabled();
+    }
+
+    @Override
+    public boolean isControllerServiceEnabled(final ControllerService service) 
{
+        return isControllerServiceEnabled(service.getIdentifier());
+    }
+
+    @Override
+    public boolean isControllerServiceEnabling(final String serviceIdentifier) 
{
+        return false;
+    }
+
+    @Override
+    public Set<String> getControllerServiceIdentifiers(final Class<? extends 
ControllerService> serviceType) {
+        final Set<String> ids = new HashSet<>();
+        for (final Map.Entry<String, ControllerServiceConfiguration> entry : 
controllerServiceMap.entrySet()) {
+            if 
(serviceType.isAssignableFrom(entry.getValue().getService().getClass())) {
+                ids.add(entry.getKey());
+            }
+        }
+        return ids;
+    }
+
+    @Override
+    public String getControllerServiceName(String serviceIdentifier) {
+        final ControllerServiceConfiguration status = 
controllerServiceMap.get(serviceIdentifier);
+        return status == null ? null : serviceIdentifier;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-mock/src/main/java/org/apache/nifi/util/MockEventAccess.java
----------------------------------------------------------------------
diff --git a/nifi-mock/src/main/java/org/apache/nifi/util/MockEventAccess.java 
b/nifi-mock/src/main/java/org/apache/nifi/util/MockEventAccess.java
new file mode 100644
index 0000000..b5f6b11
--- /dev/null
+++ b/nifi-mock/src/main/java/org/apache/nifi/util/MockEventAccess.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.util;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.nifi.controller.status.ProcessGroupStatus;
+import org.apache.nifi.provenance.ProvenanceEventRecord;
+import org.apache.nifi.provenance.ProvenanceEventRepository;
+import org.apache.nifi.reporting.EventAccess;
+
+public class MockEventAccess implements EventAccess {
+
+    private ProcessGroupStatus processGroupStatus;
+    private final List<ProvenanceEventRecord> provenanceRecords = new 
ArrayList<>();
+
+    public void setProcessGroupStatus(final ProcessGroupStatus status) {
+        this.processGroupStatus = status;
+    }
+
+    @Override
+    public ProcessGroupStatus getControllerStatus() {
+        return processGroupStatus;
+    }
+
+    @Override
+    public List<ProvenanceEventRecord> getProvenanceEvents(long firstEventId, 
int maxRecords) throws IOException {
+        if (firstEventId < 0 || maxRecords < 1) {
+            throw new IllegalArgumentException();
+        }
+
+        final List<ProvenanceEventRecord> records = new ArrayList<>();
+
+        for (final ProvenanceEventRecord record : provenanceRecords) {
+            if (record.getEventId() >= firstEventId) {
+                records.add(record);
+                if (records.size() >= maxRecords) {
+                    return records;
+                }
+            }
+        }
+
+        return records;
+    }
+
+    public void addProvenanceEvent(final ProvenanceEventRecord record) {
+        this.provenanceRecords.add(record);
+    }
+
+    @Override
+    public ProvenanceEventRepository getProvenanceRepository() {
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-mock/src/main/java/org/apache/nifi/util/MockFlowFile.java
----------------------------------------------------------------------
diff --git a/nifi-mock/src/main/java/org/apache/nifi/util/MockFlowFile.java 
b/nifi-mock/src/main/java/org/apache/nifi/util/MockFlowFile.java
new file mode 100644
index 0000000..e9fb9d6
--- /dev/null
+++ b/nifi-mock/src/main/java/org/apache/nifi/util/MockFlowFile.java
@@ -0,0 +1,277 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.util;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import org.apache.nifi.flowfile.FlowFile;
+import org.apache.nifi.flowfile.attributes.CoreAttributes;
+
+import org.junit.Assert;
+
+public class MockFlowFile implements FlowFile {
+
+    private final Map<String, String> attributes = new HashMap<>();
+
+    private final long id;
+    private final long entryDate;
+    private final Set<String> lineageIdentifiers = new HashSet<>();
+    private final long creationTime;
+    private boolean penalized = false;
+
+    private byte[] data = new byte[0];
+
+    public MockFlowFile(final long id) {
+        this.creationTime = System.nanoTime();
+        this.id = id;
+        entryDate = System.currentTimeMillis();
+        attributes.put(CoreAttributes.FILENAME.key(), 
String.valueOf(System.nanoTime()) + ".mockFlowFile");
+        attributes.put(CoreAttributes.PATH.key(), "target");
+
+        final String uuid = UUID.randomUUID().toString();
+        attributes.put(CoreAttributes.UUID.key(), uuid);
+        lineageIdentifiers.add(uuid);
+    }
+
+    public MockFlowFile(final long id, final FlowFile toCopy) {
+        this(id);
+        attributes.putAll(toCopy.getAttributes());
+        final byte[] dataToCopy = ((MockFlowFile) toCopy).data;
+        this.data = new byte[dataToCopy.length];
+        System.arraycopy(dataToCopy, 0, this.data, 0, dataToCopy.length);
+
+        lineageIdentifiers.addAll(toCopy.getLineageIdentifiers());
+    }
+
+    void setPenalized() {
+        this.penalized = true;
+    }
+
+    public long getCreationTime() {
+        return creationTime;
+    }
+
+    @Override
+    public Set<String> getLineageIdentifiers() {
+        return lineageIdentifiers;
+    }
+
+    @Override
+    public long getLineageStartDate() {
+        return entryDate;
+    }
+
+    @Override
+    public int compareTo(final FlowFile o) {
+        return 
getAttribute(CoreAttributes.UUID.key()).compareTo(o.getAttribute(CoreAttributes.UUID.key()));
+    }
+
+    @Override
+    public String getAttribute(final String attrName) {
+        return attributes.get(attrName);
+    }
+
+    @Override
+    public Map<String, String> getAttributes() {
+        return Collections.unmodifiableMap(attributes);
+    }
+
+    @Override
+    public long getEntryDate() {
+        return entryDate;
+    }
+
+    @Override
+    public long getId() {
+        return id;
+    }
+
+    @Override
+    public long getSize() {
+        return data.length;
+    }
+
+    void setData(final byte[] data) {
+        this.data = data;
+    }
+
+    byte[] getData() {
+        return this.data;
+    }
+
+    @Override
+    public boolean isPenalized() {
+        return penalized;
+    }
+
+    public void putAttributes(final Map<String, String> attrs) {
+        attributes.putAll(attrs);
+    }
+
+    public void removeAttributes(final Set<String> attrNames) {
+        for (final String attrName : attrNames) {
+            attributes.remove(attrName);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "FlowFile[" + id + "," + 
getAttribute(CoreAttributes.FILENAME.key()) + "," + getSize() + "B]";
+    }
+
+    @Override
+    public int hashCode() {
+        return (int) id;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (obj instanceof MockFlowFile) {
+            return ((MockFlowFile) obj).id == this.id;
+        }
+        return false;
+    }
+
+    public void assertAttributeExists(final String attributeName) {
+        Assert.assertTrue("Attribute " + attributeName + " does not exist", 
attributes.containsKey(attributeName));
+    }
+
+    public void assertAttributeNotExists(final String attributeName) {
+        Assert.assertFalse("Attribute " + attributeName + " not exists with 
value " + attributes.get(attributeName),
+                attributes.containsKey(attributeName));
+    }
+
+    public void assertAttributeEquals(final String attributeName, final String 
expectedValue) {
+        Assert.assertEquals(expectedValue, attributes.get(attributeName));
+    }
+
+    public void assertAttributeNotEquals(final String attributeName, final 
String expectedValue) {
+        Assert.assertNotSame(expectedValue, attributes.get(attributeName));
+    }
+
+    /**
+     * Asserts that the content of this FlowFile is the same as the content of
+     * the given file
+     *
+     * @param file to compare content against
+     * @throws IOException if fails doing IO during comparison
+     */
+    public void assertContentEquals(final File file) throws IOException {
+        assertContentEquals(file.toPath());
+    }
+
+    /**
+     * Asserts that the content of this FlowFile is the same as the content of
+     * the given path
+     *
+     * @param path where to find content to compare to
+     * @throws IOException if io error occurs while comparing content
+     */
+    public void assertContentEquals(final Path path) throws IOException {
+        try (final InputStream in = Files.newInputStream(path, 
StandardOpenOption.READ)) {
+            assertContentEquals(in);
+        }
+    }
+
+    /**
+     * Asserts that the content of this FlowFile is the same as the content of
+     * the given byte array
+     *
+     * @param data the data to compare
+     * @throws IOException if any ioe occurs while reading flowfile
+     */
+    public void assertContentEquals(final byte[] data) throws IOException {
+        try (final InputStream in = new ByteArrayInputStream(data)) {
+            assertContentEquals(in);
+        }
+    }
+
+    public void assertContentEquals(final String data) {
+        assertContentEquals(data, "UTF-8");
+    }
+
+    public void assertContentEquals(final String data, final String charset) {
+        assertContentEquals(data, Charset.forName(charset));
+    }
+
+    public void assertContentEquals(final String data, final Charset charset) {
+        final String value = new String(this.data, charset);
+        Assert.assertEquals(data, value);
+    }
+
+    /**
+     * Asserts that the content of this FlowFile is the same as the content of
+     * the given InputStream. This method closes the InputStream when it is
+     * finished.
+     *
+     * @param in the stream to source comparison data from
+     * @throws IOException if any issues reading from given source
+     */
+    public void assertContentEquals(final InputStream in) throws IOException {
+        int bytesRead = 0;
+        try (final BufferedInputStream buffered = new BufferedInputStream(in)) 
{
+            for (int i = 0; i < data.length; i++) {
+                final int fromStream = buffered.read();
+                if (fromStream < 0) {
+                    Assert.fail("FlowFile content is " + data.length + " bytes 
but provided input is only " + bytesRead + " bytes");
+                }
+
+                if ((fromStream & 0xFF) != (data[i] & 0xFF)) {
+                    Assert.fail("FlowFile content differs from input at byte " 
+ bytesRead + " with input having value "
+                            + (fromStream & 0xFF) + " and FlowFile having 
value " + (data[i] & 0xFF));
+                }
+
+                bytesRead++;
+            }
+
+            final int nextByte = buffered.read();
+            if (nextByte >= 0) {
+                Assert.fail("Contents of input and FlowFile were the same 
through byte " + data.length + "; however, FlowFile's content ended at this 
point, and input has more data");
+            }
+        }
+    }
+
+    /**
+     * @return a copy of the the contents of the FlowFile as a byte array
+     */
+    public byte[] toByteArray() {
+        return Arrays.copyOf(this.data, this.data.length);
+    }
+
+    @Override
+    public Long getLastQueueDate() {
+        return entryDate;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-mock/src/main/java/org/apache/nifi/util/MockFlowFileQueue.java
----------------------------------------------------------------------
diff --git 
a/nifi-mock/src/main/java/org/apache/nifi/util/MockFlowFileQueue.java 
b/nifi-mock/src/main/java/org/apache/nifi/util/MockFlowFileQueue.java
new file mode 100644
index 0000000..775a1d5
--- /dev/null
+++ b/nifi-mock/src/main/java/org/apache/nifi/util/MockFlowFileQueue.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.util;
+
+import java.util.Collection;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.apache.nifi.processor.QueueSize;
+
+public class MockFlowFileQueue {
+
+    private final BlockingQueue<MockFlowFile> queue;
+
+    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
+    private final Lock readLock = rwLock.readLock();
+    private final Lock writeLock = rwLock.writeLock();
+
+    public MockFlowFileQueue() {
+        queue = new LinkedBlockingQueue<>();
+    }
+
+    public void offer(final MockFlowFile flowFile) {
+        writeLock.lock();
+        try {
+            queue.offer(flowFile);
+        } finally {
+            writeLock.unlock();
+        }
+    }
+
+    public MockFlowFile poll() {
+        writeLock.lock();
+        try {
+            return queue.poll();
+        } finally {
+            writeLock.unlock();
+        }
+    }
+
+    public void addAll(final Collection<MockFlowFile> flowFiles) {
+        writeLock.lock();
+        try {
+            queue.addAll(flowFiles);
+        } finally {
+            writeLock.unlock();
+        }
+    }
+
+    public QueueSize size() {
+        readLock.lock();
+        try {
+            final int count = queue.size();
+
+            long contentSize = 0L;
+            for (final MockFlowFile flowFile : queue) {
+                contentSize += flowFile.getSize();
+            }
+            return new QueueSize(count, contentSize);
+        } finally {
+            readLock.unlock();
+        }
+    }
+
+    public boolean isEmpty() {
+        return size().getObjectCount() == 0;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa998847/nifi-mock/src/main/java/org/apache/nifi/util/MockProcessContext.java
----------------------------------------------------------------------
diff --git 
a/nifi-mock/src/main/java/org/apache/nifi/util/MockProcessContext.java 
b/nifi-mock/src/main/java/org/apache/nifi/util/MockProcessContext.java
new file mode 100644
index 0000000..20a2f7c
--- /dev/null
+++ b/nifi-mock/src/main/java/org/apache/nifi/util/MockProcessContext.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.util;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.nifi.components.ConfigurableComponent;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.PropertyValue;
+import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.controller.ControllerService;
+import org.apache.nifi.controller.ControllerServiceLookup;
+import org.apache.nifi.processor.Processor;
+import org.apache.nifi.processor.Relationship;
+import org.apache.nifi.processor.SchedulingContext;
+import org.junit.Assert;
+
+public class MockProcessContext extends MockControllerServiceLookup implements 
SchedulingContext, ControllerServiceLookup {
+
+    private final ConfigurableComponent component;
+    private final Map<PropertyDescriptor, String> properties = new HashMap<>();
+
+    private String annotationData = null;
+    private boolean yieldCalled = false;
+    private boolean enableExpressionValidation = false;
+    private boolean allowExpressionValidation = true;
+
+    private volatile Set<Relationship> unavailableRelationships = new 
HashSet<>();
+
+    /**
+     * Creates a new MockProcessContext for the given Processor
+     *
+     * @param component being mocked
+     */
+    public MockProcessContext(final ConfigurableComponent component) {
+        this.component = Objects.requireNonNull(component);
+    }
+
+    public MockProcessContext(final ControllerService component, final 
MockProcessContext context) {
+        this(component);
+
+        try {
+            annotationData = 
context.getControllerServiceAnnotationData(component);
+            final Map<PropertyDescriptor, String> props = 
context.getControllerServiceProperties(component);
+            properties.putAll(props);
+
+            super.addControllerServices(context);
+        } catch (IllegalArgumentException e) {
+            // do nothing...the service is being loaded
+        }
+    }
+
+    @Override
+    public PropertyValue getProperty(final PropertyDescriptor descriptor) {
+        return getProperty(descriptor.getName());
+    }
+
+    @Override
+    public PropertyValue getProperty(final String propertyName) {
+        final PropertyDescriptor descriptor = 
component.getPropertyDescriptor(propertyName);
+        if (descriptor == null) {
+            return null;
+        }
+
+        final String setPropertyValue = properties.get(descriptor);
+        final String propValue = (setPropertyValue == null) ? 
descriptor.getDefaultValue() : setPropertyValue;
+        return new MockPropertyValue(propValue, this, 
(enableExpressionValidation && allowExpressionValidation) ? descriptor : null);
+    }
+
+    @Override
+    public PropertyValue newPropertyValue(final String rawValue) {
+        return new MockPropertyValue(rawValue, this);
+    }
+
+    public ValidationResult setProperty(final String propertyName, final 
String propertyValue) {
+        return setProperty(new 
PropertyDescriptor.Builder().name(propertyName).build(), propertyValue);
+    }
+
+    /**
+     * Updates the value of the property with the given PropertyDescriptor to
+     * the specified value IF and ONLY IF the value is valid according to the
+     * descriptor's validator. Otherwise, the property value is not updated. In
+     * either case, the ValidationResult is returned, indicating whether or not
+     * the property is valid
+     *
+     * @param descriptor of property to modify
+     * @param value new value
+     * @return result
+     */
+    public ValidationResult setProperty(final PropertyDescriptor descriptor, 
final String value) {
+        requireNonNull(descriptor);
+        requireNonNull(value, "Cannot set property to null value; if the 
intent is to remove the property, call removeProperty instead");
+        final PropertyDescriptor fullyPopulatedDescriptor = 
component.getPropertyDescriptor(descriptor.getName());
+
+        final ValidationResult result = 
fullyPopulatedDescriptor.validate(value, new MockValidationContext(this));
+        String oldValue = properties.put(fullyPopulatedDescriptor, value);
+        if (oldValue == null) {
+            oldValue = fullyPopulatedDescriptor.getDefaultValue();
+        }
+        if ((value == null && oldValue != null) || (value != null && 
!value.equals(oldValue))) {
+            component.onPropertyModified(fullyPopulatedDescriptor, oldValue, 
value);
+        }
+
+        return result;
+    }
+
+    public boolean removeProperty(final PropertyDescriptor descriptor) {
+        Objects.requireNonNull(descriptor);
+        final PropertyDescriptor fullyPopulatedDescriptor = 
component.getPropertyDescriptor(descriptor.getName());
+        String value = null;
+        if (!fullyPopulatedDescriptor.isRequired() && (value = 
properties.remove(fullyPopulatedDescriptor)) != null) {
+            component.onPropertyModified(fullyPopulatedDescriptor, value, 
null);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void yield() {
+        yieldCalled = true;
+    }
+
+    public boolean isYieldCalled() {
+        return yieldCalled;
+    }
+
+    public void addControllerService(final String serviceIdentifier, final 
ControllerService controllerService, final Map<PropertyDescriptor, String> 
properties, final String annotationData) {
+        requireNonNull(controllerService);
+        final ControllerServiceConfiguration config = 
addControllerService(controllerService);
+        config.setProperties(properties);
+        config.setAnnotationData(annotationData);
+    }
+
+    @Override
+    public int getMaxConcurrentTasks() {
+        return 1;
+    }
+
+    public void setAnnotationData(final String annotationData) {
+        this.annotationData = annotationData;
+    }
+
+    @Override
+    public String getAnnotationData() {
+        return annotationData;
+    }
+
+    @Override
+    public Map<PropertyDescriptor, String> getProperties() {
+        final List<PropertyDescriptor> supported = 
component.getPropertyDescriptors();
+        if (supported == null || supported.isEmpty()) {
+            return Collections.unmodifiableMap(properties);
+        } else {
+            final Map<PropertyDescriptor, String> props = new 
LinkedHashMap<>();
+            for (final PropertyDescriptor descriptor : supported) {
+                props.put(descriptor, null);
+            }
+            props.putAll(properties);
+            return props;
+        }
+    }
+
+    /**
+     * Validates the current properties, returning ValidationResults for any
+     * invalid properties. All processor defined properties will be validated.
+     * If they are not included in the in the purposed configuration, the
+     * default value will be used.
+     *
+     * @return Collection of validation result objects for any invalid findings
+     * only. If the collection is empty then the processor is valid. Guaranteed
+     * non-null
+     */
+    public Collection<ValidationResult> validate() {
+        return component.validate(new MockValidationContext(this));
+    }
+
+    public boolean isValid() {
+        for (final ValidationResult result : validate()) {
+            if (!result.isValid()) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public void assertValid() {
+        final StringBuilder sb = new StringBuilder();
+        int failureCount = 0;
+
+        for (final ValidationResult result : validate()) {
+            if (!result.isValid()) {
+                sb.append(result.toString()).append("\n");
+                failureCount++;
+            }
+        }
+
+        if (failureCount > 0) {
+            Assert.fail("Processor has " + failureCount + " validation 
failures:\n" + sb.toString());
+        }
+    }
+
+    @Override
+    public String encrypt(final String unencrypted) {
+        return "enc{" + unencrypted + "}";
+    }
+
+    @Override
+    public String decrypt(final String encrypted) {
+        if (encrypted.startsWith("enc{") && encrypted.endsWith("}")) {
+            return encrypted.substring(4, encrypted.length() - 2);
+        }
+        return encrypted;
+    }
+
+    public void setValidateExpressionUsage(final boolean validate) {
+        allowExpressionValidation = validate;
+    }
+
+    public void enableExpressionValidation() {
+        enableExpressionValidation = true;
+    }
+
+    public void disableExpressionValidation() {
+        enableExpressionValidation = false;
+    }
+
+    Map<PropertyDescriptor, String> getControllerServiceProperties(final 
ControllerService controllerService) {
+        return 
super.getConfiguration(controllerService.getIdentifier()).getProperties();
+    }
+
+    String getControllerServiceAnnotationData(final ControllerService 
controllerService) {
+        return 
super.getConfiguration(controllerService.getIdentifier()).getAnnotationData();
+    }
+
+    @Override
+    public ControllerServiceLookup getControllerServiceLookup() {
+        return this;
+    }
+
+    @Override
+    public void leaseControllerService(final String identifier) {
+    }
+
+    public Set<Relationship> getAvailableRelationships() {
+        if (!(component instanceof Processor)) {
+            return Collections.emptySet();
+        }
+
+        final Set<Relationship> relationships = new HashSet<>(((Processor) 
component).getRelationships());
+        relationships.removeAll(unavailableRelationships);
+        return relationships;
+    }
+
+    public void setUnavailableRelationships(final Set<Relationship> 
relationships) {
+        this.unavailableRelationships = Collections.unmodifiableSet(new 
HashSet<>(relationships));
+    }
+
+    public Set<Relationship> getUnavailableRelationships() {
+        return unavailableRelationships;
+    }
+}

Reply via email to