NIFI-725 - adding nifi-documentation-plugin

Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/af87b52b
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/af87b52b
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/af87b52b

Branch: refs/heads/NIFI-725-master
Commit: af87b52b8657582d6e0dec162d760a85d35eccfa
Parents: 1d19a34
Author: danbress <dbr...@onyxconsults.com>
Authored: Tue Aug 18 21:46:18 2015 -0400
Committer: danbress <dbr...@onyxconsults.com>
Committed: Tue Aug 18 21:46:18 2015 -0400

----------------------------------------------------------------------
 nifi-documentation-plugin/pom.xml               | 113 ++++
 nifi-documentation-plugin/src/it/settings.xml   |  55 ++
 .../src/it/simple-it/pom.xml                    |  34 +
 .../src/it/simple-it/verify.groovy              |   3 +
 .../main/java/org/apache/nifi/DocumentMojo.java | 177 ++++++
 .../org/apache/nifi/ExtractDocumentation.java   | 111 ++++
 .../org/apache/nifi/NoCloseInputStream.java     |  32 +
 .../ConfigurableComponentInitializer.java       |  45 ++
 .../nifi/documentation/DocumentationWriter.java |  33 +
 .../html/HtmlDocumentationWriter.java           | 615 +++++++++++++++++++
 .../html/HtmlProcessorDocumentationWriter.java  | 256 ++++++++
 .../init/ControllerServiceInitializer.java      |  53 ++
 .../init/ProcessorInitializer.java              |  53 ++
 .../init/ReportingTaskingInitializer.java       |  52 ++
 .../mock/MockConfigurationContext.java          |  48 ++
 ...kControllerServiceInitializationContext.java |  46 ++
 .../mock/MockControllerServiceLookup.java       |  65 ++
 .../documentation/mock/MockProcessContext.java  |  85 +++
 .../MockProcessorInitializationContext.java     |  45 ++
 .../documentation/mock/MockProcessorLogger.java | 169 +++++
 .../MockReportingInitializationContext.java     |  67 ++
 .../documentation/util/ReflectionUtils.java     | 139 +++++
 22 files changed, 2296 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/af87b52b/nifi-documentation-plugin/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-documentation-plugin/pom.xml 
b/nifi-documentation-plugin/pom.xml
new file mode 100644
index 0000000..cc5e160
--- /dev/null
+++ b/nifi-documentation-plugin/pom.xml
@@ -0,0 +1,113 @@
+<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";>
+        <!--
+      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.
+    -->
+    <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.apache</groupId>
+               <artifactId>apache</artifactId>
+               <version>17</version>
+               <relativePath />
+       </parent>
+       <groupId>org.apache.nifi</groupId>
+       <artifactId>nifi-documentation-plugin</artifactId>
+       <version>1.0.0-SNAPSHOT</version>
+       <packaging>maven-plugin</packaging>
+
+       <name>nifi-documentation-plugin Maven Plugin</name>
+
+       <!-- FIXME change it to the project's website -->
+       <url>http://maven.apache.org</url>
+
+       <properties>
+               <maven.compiler.source>1.7</maven.compiler.source>
+               <maven.compiler.target>1.7</maven.compiler.target>
+               <maven.min-version>3.0.5</maven.min-version>
+               
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+               
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+               <inceptionYear>2014</inceptionYear>
+       </properties>
+
+       <dependencies>
+               <dependency>
+                       <groupId>org.apache.maven</groupId>
+                       <artifactId>maven-plugin-api</artifactId>
+                       <version>2.2.1</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.apache.maven</groupId>
+                       <artifactId>maven-project</artifactId>
+                       <version>2.2.1</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.apache.maven.plugin-tools</groupId>
+                       <artifactId>maven-plugin-annotations</artifactId>
+                       <version>3.3</version>
+                       <scope>provided</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.codehaus.plexus</groupId>
+                       <artifactId>plexus-utils</artifactId>
+                       <version>3.0.8</version>
+               </dependency>
+               <dependency>
+                       <groupId>junit</groupId>
+                       <artifactId>junit</artifactId>
+                       <version>4.8.2</version>
+                       <scope>test</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.apache.nifi</groupId>
+                       <artifactId>nifi-api</artifactId>
+                       <version>0.2.1</version>
+                       <scope>provided</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.slf4j</groupId>
+                       <artifactId>slf4j-api</artifactId>
+                       <version>1.7.12</version>
+                       <scope>provided</scope>
+               </dependency>
+               <dependency>
+                       <groupId>com.google.guava</groupId>
+                       <artifactId>guava</artifactId>
+                       <version>17.0</version>
+               </dependency>
+       </dependencies>
+       <build>
+               <plugins>
+                       <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-plugin-plugin</artifactId>
+                               <executions>
+                                       <execution>
+                                               <id>default-descriptor</id>
+                                               <goals>
+                                                       <goal>descriptor</goal>
+                                               </goals>
+                                               <phase>process-classes</phase>
+                                       </execution>
+                                       <execution>
+                                               <id>help-descriptor</id>
+                                               <goals>
+                                                       <goal>helpmojo</goal>
+                                               </goals>
+                                               <phase>process-classes</phase>
+                                       </execution>
+                               </executions>
+                       </plugin>
+               </plugins>
+       </build>
+</project>

http://git-wip-us.apache.org/repos/asf/nifi/blob/af87b52b/nifi-documentation-plugin/src/it/settings.xml
----------------------------------------------------------------------
diff --git a/nifi-documentation-plugin/src/it/settings.xml 
b/nifi-documentation-plugin/src/it/settings.xml
new file mode 100644
index 0000000..dc02120
--- /dev/null
+++ b/nifi-documentation-plugin/src/it/settings.xml
@@ -0,0 +1,55 @@
+<?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.
+-->
+
+<settings>
+  <profiles>
+    <profile>
+      <id>it-repo</id>
+      <activation>
+        <activeByDefault>true</activeByDefault>
+      </activation>
+      <repositories>
+        <repository>
+          <id>local.central</id>
+          <url>@localRepositoryUrl@</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+          </snapshots>
+        </repository>
+      </repositories>
+      <pluginRepositories>
+        <pluginRepository>
+          <id>local.central</id>
+          <url>@localRepositoryUrl@</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+          </snapshots>
+        </pluginRepository>
+      </pluginRepositories>
+    </profile>
+  </profiles>
+</settings>

http://git-wip-us.apache.org/repos/asf/nifi/blob/af87b52b/nifi-documentation-plugin/src/it/simple-it/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-documentation-plugin/src/it/simple-it/pom.xml 
b/nifi-documentation-plugin/src/it/simple-it/pom.xml
new file mode 100644
index 0000000..c8b4d7a
--- /dev/null
+++ b/nifi-documentation-plugin/src/it/simple-it/pom.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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>
+
+  <groupId>org.apache.nifi.it</groupId>
+  <artifactId>simple-it</artifactId>
+  <version>1.0-SNAPSHOT</version>
+
+  <description>A simple IT verifying the basic use case.</description>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>@project.groupId@</groupId>
+        <artifactId>@project.artifactId@</artifactId>
+        <version>@project.version@</version>
+        <executions>
+          <execution>
+            <id>touch</id>
+            <phase>validate</phase>
+            <goals>
+              <goal>touch</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>

http://git-wip-us.apache.org/repos/asf/nifi/blob/af87b52b/nifi-documentation-plugin/src/it/simple-it/verify.groovy
----------------------------------------------------------------------
diff --git a/nifi-documentation-plugin/src/it/simple-it/verify.groovy 
b/nifi-documentation-plugin/src/it/simple-it/verify.groovy
new file mode 100644
index 0000000..ff79813
--- /dev/null
+++ b/nifi-documentation-plugin/src/it/simple-it/verify.groovy
@@ -0,0 +1,3 @@
+File touchFile = new File( basedir, "target/touch.txt" );
+
+assert touchFile.isFile()

http://git-wip-us.apache.org/repos/asf/nifi/blob/af87b52b/nifi-documentation-plugin/src/main/java/org/apache/nifi/DocumentMojo.java
----------------------------------------------------------------------
diff --git 
a/nifi-documentation-plugin/src/main/java/org/apache/nifi/DocumentMojo.java 
b/nifi-documentation-plugin/src/main/java/org/apache/nifi/DocumentMojo.java
new file mode 100644
index 0000000..bd97149
--- /dev/null
+++ b/nifi-documentation-plugin/src/main/java/org/apache/nifi/DocumentMojo.java
@@ -0,0 +1,177 @@
+/*
+ * 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;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ServiceConfigurationError;
+import java.util.ServiceLoader;
+
+import org.apache.maven.artifact.DependencyResolutionRequiredException;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.plugins.annotations.ResolutionScope;
+import org.apache.maven.project.MavenProject;
+import org.apache.nifi.components.ConfigurableComponent;
+import org.apache.nifi.controller.ControllerService;
+import org.apache.nifi.documentation.ConfigurableComponentInitializer;
+import org.apache.nifi.documentation.html.HtmlDocumentationWriter;
+import org.apache.nifi.documentation.html.HtmlProcessorDocumentationWriter;
+import org.apache.nifi.documentation.init.ControllerServiceInitializer;
+import org.apache.nifi.documentation.init.ProcessorInitializer;
+import org.apache.nifi.documentation.init.ReportingTaskingInitializer;
+import org.apache.nifi.processor.Processor;
+import org.apache.nifi.reporting.InitializationException;
+import org.apache.nifi.reporting.ReportingTask;
+
+@Mojo(name = "document", defaultPhase = LifecyclePhase.PROCESS_CLASSES, 
threadSafe = false, requiresDependencyResolution = 
ResolutionScope.COMPILE_PLUS_RUNTIME, requiresProject = true)
+public class DocumentMojo extends AbstractMojo {
+
+    /**
+     * Location of the file.
+     */
+    @Parameter(defaultValue = "${project.build.outputDirectory}", property = 
"outputDir", required = true)
+    private File outputDirectory;
+
+    @Parameter(defaultValue = "${project}", required = true)
+    private MavenProject mavenProject;
+
+    public void execute() throws MojoExecutionException {
+        final File docsDir = new File(outputDirectory, "docs");
+        if (!docsDir.exists()) {
+            docsDir.mkdirs();
+        }
+
+        try {
+            final List<?> compileClassPathElements = 
mavenProject.getCompileClasspathElements();
+            // final List<?> compileClassPathElements = 
mavenProject.getRuntimeClasspathElements();
+            final URL[] urls = new URL[compileClassPathElements.size()];
+
+            for (int i = 0; i < compileClassPathElements.size(); i++) {
+                final String path = compileClassPathElements.get(i).toString();
+                final File filePath = new File(path);
+                final URL url = filePath.toURI().toURL();
+                urls[i] = url;
+                getLog().info("Adding url: " + url);
+            }
+
+            final ClassLoader myClassLoader = 
Thread.currentThread().getContextClassLoader();
+            final ClassLoader projectClassLoader = new URLClassLoader(urls, 
myClassLoader);
+
+            final List<ConfigurableComponent> components = new ArrayList<>();
+            loadServices(projectClassLoader, components, Processor.class);
+            loadServices(projectClassLoader, components, 
ControllerService.class);
+            loadServices(projectClassLoader, components, ReportingTask.class);
+
+            for (ConfigurableComponent component : components) {
+                initializeComponent(component);
+                generateDocumentation(component, projectClassLoader, docsDir);
+                tearDownComponent(component);
+            }
+
+        } catch (DependencyResolutionRequiredException e) {
+            throw new MojoExecutionException("Unable to resolve classpath", e);
+        } catch (MalformedURLException e) {
+            throw new MojoExecutionException("Unable to resolve classpath", e);
+        } catch (InitializationException e) {
+            throw new MojoExecutionException("Unable to initializer 
component", e);
+        } catch (FileNotFoundException e) {
+            throw new MojoExecutionException("Unable to generate 
documentation", e);
+        } catch (IOException e) {
+            throw new MojoExecutionException("Unable to generate 
documentation", e);
+        } catch (ServiceConfigurationError e) {
+            throw new MojoExecutionException("Unable to generate 
documentation", e);
+        }
+    }
+
+    private static <T extends ConfigurableComponent> void loadServices(final 
ClassLoader projectClassLoader, final List<ConfigurableComponent> components, 
final Class<T> service) {
+        final ServiceLoader<T> nifiServiceLoader = ServiceLoader.load(service, 
projectClassLoader);
+
+        for (T processor : nifiServiceLoader) {
+            components.add(processor);
+        }
+    }
+
+    private void tearDownComponent(ConfigurableComponent component) {
+        final ConfigurableComponentInitializer initializer;
+        if (component instanceof Processor) {
+            initializer = new ProcessorInitializer();
+        } else if (component instanceof ReportingTask) {
+            initializer = new ReportingTaskingInitializer();
+        } else if (component instanceof ControllerService) {
+            initializer = new ControllerServiceInitializer();
+        } else {
+            throw new NullPointerException("Unknown type: " + 
component.getClass());
+        }
+
+        initializer.teardown(component);
+
+    }
+
+    private void generateDocumentation(ConfigurableComponent component, 
ClassLoader classLoader, File docsDirectory) throws FileNotFoundException, 
IOException {
+        final HtmlDocumentationWriter writer;
+        if (component instanceof Processor) {
+            writer = new HtmlProcessorDocumentationWriter();
+        } else if (component instanceof ReportingTask) {
+            writer = new HtmlDocumentationWriter();
+        } else if (component instanceof ControllerService) {
+            writer = new HtmlDocumentationWriter();
+        } else {
+            throw new NullPointerException("Unknown type: " + 
component.getClass());
+        }
+
+        final String className = component.getClass().getName();
+
+        boolean hasAdditionalDetails = classLoader.getResource("docs" + 
File.separator + className + File.separator + "additionalDetails.html") != null;
+        final File componentDocumentationDir = new File(docsDirectory, 
className);
+        if (!componentDocumentationDir.exists()) {
+            componentDocumentationDir.mkdirs();
+        }
+
+        final File generatedFile = new File(componentDocumentationDir, 
"index.html");
+
+        try (FileOutputStream fos = new FileOutputStream(generatedFile); 
BufferedOutputStream bos = new BufferedOutputStream(fos)) {
+            writer.write(component, bos, hasAdditionalDetails);
+        }
+    }
+
+    private void initializeComponent(ConfigurableComponent component) throws 
InitializationException {
+        final ConfigurableComponentInitializer initializer;
+        if (component instanceof Processor) {
+            initializer = new ProcessorInitializer();
+        } else if (component instanceof ReportingTask) {
+            initializer = new ReportingTaskingInitializer();
+        } else if (component instanceof ControllerService) {
+            initializer = new ControllerServiceInitializer();
+        } else {
+            throw new NullPointerException("Unknown type: " + 
component.getClass());
+        }
+
+        initializer.initialize(component);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/af87b52b/nifi-documentation-plugin/src/main/java/org/apache/nifi/ExtractDocumentation.java
----------------------------------------------------------------------
diff --git 
a/nifi-documentation-plugin/src/main/java/org/apache/nifi/ExtractDocumentation.java
 
b/nifi-documentation-plugin/src/main/java/org/apache/nifi/ExtractDocumentation.java
new file mode 100644
index 0000000..957b757
--- /dev/null
+++ 
b/nifi-documentation-plugin/src/main/java/org/apache/nifi/ExtractDocumentation.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.plugins.annotations.ResolutionScope;
+import org.apache.maven.project.MavenProject;
+
+import com.google.common.io.ByteStreams;
+
+@Mojo(name = "extract", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, 
threadSafe = false, requiresProject = false, requiresDependencyResolution = 
ResolutionScope.COMPILE_PLUS_RUNTIME)
+public class ExtractDocumentation extends AbstractMojo {
+    /**
+     * Location of the file.
+     */
+    @Parameter(defaultValue = "${project.build.directory}", property = 
"outputDir", required = true)
+    private File outputDirectory;
+
+    @Parameter(defaultValue = "${project}", required = true)
+    private MavenProject mavenProject;
+
+    @Override
+    public void execute() throws MojoExecutionException, MojoFailureException {
+
+        @SuppressWarnings("unchecked")
+        final List<Dependency> runtimeDependencies = 
mavenProject.getDependencies();
+
+        for (Dependency dependency : runtimeDependencies) {
+            getLog().debug("Looking at: " + dependency);
+            // TODO dedup
+            if (dependency.getType().equals("nar")) {
+
+                final Artifact artifact = (Artifact) 
mavenProject.getArtifactMap().get(dependency.getGroupId() + ":" + 
dependency.getArtifactId());
+                final File narFile = artifact.getFile();
+                try (FileInputStream fos = new FileInputStream(narFile); 
BufferedInputStream bis = new BufferedInputStream(fos); JarInputStream jis = 
new JarInputStream(bis)) {
+
+                    JarEntry jarEntry = null;
+
+                    while ((jarEntry = jis.getNextJarEntry()) != null) {
+                        if (jarEntry.getName().endsWith(".jar")) {
+                            getLog().debug("Found a jar inside of a nar " + 
jarEntry + " -> " + narFile);
+                            processJar(new NoCloseInputStream(jis));
+                        }
+                    }
+
+                } catch (IOException e) {
+                    throw new MojoExecutionException("Unable to process: " + 
narFile, e);
+                }
+
+            }
+        }
+
+    }
+
+    private void processJar(InputStream inputStream) throws IOException {
+        try (JarInputStream jis = new JarInputStream(inputStream)) {
+            JarEntry jarEntry = null;
+
+            while ((jarEntry = jis.getNextJarEntry()) != null) {
+                
+                if (!jarEntry.isDirectory()) {
+                    final String jarName = jarEntry.getName();
+                    if (jarName.startsWith("docs")) {
+                        getLog().debug("Looking inside a jar at: " + jarEntry);
+                        File outputFile = new File(outputDirectory, jarName);
+                        File parentFile = outputFile.getParentFile();
+                        if (!parentFile.exists()) {
+                            parentFile.mkdirs();
+                        }
+
+                        try (FileOutputStream fos = new 
FileOutputStream(outputFile)) {
+                            ByteStreams.copy(jis, fos);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/af87b52b/nifi-documentation-plugin/src/main/java/org/apache/nifi/NoCloseInputStream.java
----------------------------------------------------------------------
diff --git 
a/nifi-documentation-plugin/src/main/java/org/apache/nifi/NoCloseInputStream.java
 
b/nifi-documentation-plugin/src/main/java/org/apache/nifi/NoCloseInputStream.java
new file mode 100644
index 0000000..80b259e
--- /dev/null
+++ 
b/nifi-documentation-plugin/src/main/java/org/apache/nifi/NoCloseInputStream.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class NoCloseInputStream extends FilterInputStream {
+
+    protected NoCloseInputStream(InputStream arg0) {
+        super(arg0);
+    }
+
+    @Override
+    public void close() throws IOException {
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/af87b52b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/ConfigurableComponentInitializer.java
----------------------------------------------------------------------
diff --git 
a/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/ConfigurableComponentInitializer.java
 
b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/ConfigurableComponentInitializer.java
new file mode 100644
index 0000000..ad21f21
--- /dev/null
+++ 
b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/ConfigurableComponentInitializer.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.documentation;
+
+import org.apache.nifi.components.ConfigurableComponent;
+import org.apache.nifi.reporting.InitializationException;
+
+/**
+ * An interface for initializing and tearing down a ConfigurableComponent. It 
is up to the
+ * implementer to call "init" so that you can call
+ * ConfigurableComponent.getPropertyDescriptors()
+ *
+ */
+public interface ConfigurableComponentInitializer {
+
+    /**
+     * Initializes a configurable component to the point that you can call
+     * getPropertyDescriptors() on it
+     *
+     * @param component the component to initialize
+     * @throws InitializationException if the component could not be 
initialized
+     */
+    void initialize(ConfigurableComponent component) throws 
InitializationException;
+
+    /**
+     * Calls the lifecycle methods that should be called when a flow is 
shutdown.
+     *
+     * @param component the component to initialize
+     */
+    void teardown(ConfigurableComponent component);
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/af87b52b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/DocumentationWriter.java
----------------------------------------------------------------------
diff --git 
a/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/DocumentationWriter.java
 
b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/DocumentationWriter.java
new file mode 100644
index 0000000..d178636
--- /dev/null
+++ 
b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/DocumentationWriter.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.documentation;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.nifi.components.ConfigurableComponent;
+
+/**
+ * Generates documentation for an instance of a ConfigurableComponent
+ *
+ *
+ */
+public interface DocumentationWriter {
+
+    void write(ConfigurableComponent configurableComponent, OutputStream 
streamToWriteTo,
+            boolean includesAdditionalDocumentation) throws IOException;
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/af87b52b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/html/HtmlDocumentationWriter.java
----------------------------------------------------------------------
diff --git 
a/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/html/HtmlDocumentationWriter.java
 
b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/html/HtmlDocumentationWriter.java
new file mode 100644
index 0000000..394897e
--- /dev/null
+++ 
b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/html/HtmlDocumentationWriter.java
@@ -0,0 +1,615 @@
+/*
+ * 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.documentation.html;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import javax.xml.stream.FactoryConfigurationError;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+import org.apache.nifi.annotation.behavior.DynamicProperties;
+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.components.AllowableValue;
+import org.apache.nifi.components.ConfigurableComponent;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.controller.ControllerService;
+import org.apache.nifi.documentation.DocumentationWriter;
+
+import edu.emory.mathcs.backport.java.util.Collections;
+
+/**
+ * Generates HTML documentation for a ConfigurableComponent. This class is used
+ * to generate documentation for ControllerService and ReportingTask because
+ * they have no additional information.
+ *
+ *
+ */
+public class HtmlDocumentationWriter implements DocumentationWriter {
+
+    /**
+     * The filename where additional user specified information may be stored.
+     */
+    public static final String ADDITIONAL_DETAILS_HTML = 
"additionalDetails.html";
+
+    @Override
+    public void write(final ConfigurableComponent configurableComponent, final 
OutputStream streamToWriteTo,
+            final boolean includesAdditionalDocumentation) throws IOException {
+
+        try {
+            XMLStreamWriter xmlStreamWriter = 
XMLOutputFactory.newInstance().createXMLStreamWriter(
+                    streamToWriteTo, "UTF-8");
+            xmlStreamWriter.writeDTD("<!DOCTYPE html>");
+            xmlStreamWriter.writeStartElement("html");
+            xmlStreamWriter.writeAttribute("lang", "en");
+            writeHead(configurableComponent, xmlStreamWriter);
+            writeBody(configurableComponent, xmlStreamWriter, 
includesAdditionalDocumentation);
+            xmlStreamWriter.writeEndElement();
+            xmlStreamWriter.close();
+        } catch (XMLStreamException | FactoryConfigurationError e) {
+            throw new IOException("Unable to create XMLOutputStream", e);
+        }
+    }
+
+    /**
+     * Writes the head portion of the HTML documentation.
+     *
+     * @param configurableComponent the component to describe
+     * @param xmlStreamWriter the stream to write to
+     * @throws XMLStreamException thrown if there was a problem writing to the
+     * stream
+     */
+    protected void writeHead(final ConfigurableComponent configurableComponent,
+            final XMLStreamWriter xmlStreamWriter) throws XMLStreamException {
+        xmlStreamWriter.writeStartElement("head");
+        xmlStreamWriter.writeStartElement("meta");
+        xmlStreamWriter.writeAttribute("charset", "utf-8");
+        xmlStreamWriter.writeEndElement();
+        writeSimpleElement(xmlStreamWriter, "title", 
getTitle(configurableComponent));
+
+        xmlStreamWriter.writeStartElement("link");
+        xmlStreamWriter.writeAttribute("rel", "stylesheet");
+        xmlStreamWriter.writeAttribute("href", 
"../../css/component-usage.css");
+        xmlStreamWriter.writeAttribute("type", "text/css");
+        xmlStreamWriter.writeEndElement();
+
+        xmlStreamWriter.writeEndElement();
+    }
+
+    /**
+     * Gets the class name of the component.
+     *
+     * @param configurableComponent the component to describe
+     * @return the class name of the component
+     */
+    protected String getTitle(final ConfigurableComponent 
configurableComponent) {
+        return configurableComponent.getClass().getSimpleName();
+    }
+
+    /**
+     * Writes the body section of the documentation, this consists of the
+     * component description, the tags, and the PropertyDescriptors.
+     *
+     * @param configurableComponent the component to describe
+     * @param xmlStreamWriter the stream writer
+     * @param hasAdditionalDetails whether there are additional details present
+     * or not
+     * @throws XMLStreamException thrown if there was a problem writing to the
+     * XML stream
+     */
+    private void writeBody(final ConfigurableComponent configurableComponent,
+            final XMLStreamWriter xmlStreamWriter, final boolean 
hasAdditionalDetails)
+            throws XMLStreamException {
+        xmlStreamWriter.writeStartElement("body");
+        writeDescription(configurableComponent, xmlStreamWriter, 
hasAdditionalDetails);
+        writeTags(configurableComponent, xmlStreamWriter);
+        writeProperties(configurableComponent, xmlStreamWriter);
+        writeDynamicProperties(configurableComponent, xmlStreamWriter);
+        writeAdditionalBodyInfo(configurableComponent, xmlStreamWriter);
+        writeSeeAlso(configurableComponent, xmlStreamWriter);
+        xmlStreamWriter.writeEndElement();
+    }
+
+    /**
+     * Writes the list of components that may be linked from this component.
+     *
+     * @param configurableComponent the component to describe
+     * @param xmlStreamWriter the stream writer to use
+     * @throws XMLStreamException thrown if there was a problem writing the XML
+     */
+    private void writeSeeAlso(ConfigurableComponent configurableComponent, 
XMLStreamWriter xmlStreamWriter)
+            throws XMLStreamException {
+        final SeeAlso seeAlso = 
configurableComponent.getClass().getAnnotation(SeeAlso.class);
+        if (seeAlso != null) {
+            writeSimpleElement(xmlStreamWriter, "h3", "See Also:");
+            xmlStreamWriter.writeStartElement("p");
+            int index = 0;
+            for (final Class<? extends ConfigurableComponent> linkedComponent 
: seeAlso.value()) {
+                if (index != 0) {
+                    xmlStreamWriter.writeCharacters(", ");
+                }
+
+                writeLinkForComponent(xmlStreamWriter, linkedComponent);
+
+                ++index;
+            }
+
+            for (final String linkedComponent : seeAlso.classNames()) {
+                if (index != 0) {
+                    xmlStreamWriter.writeCharacters(", ");
+                }
+
+                final String link = "../" + linkedComponent + "/index.html";
+
+                final int indexOfLastPeriod = linkedComponent.lastIndexOf(".") 
+ 1;
+
+                writeLink(xmlStreamWriter, 
linkedComponent.substring(indexOfLastPeriod), link);
+
+                ++index;
+            }
+            xmlStreamWriter.writeEndElement();
+        }
+    }
+
+    /**
+     * This method may be overridden by sub classes to write additional
+     * information to the body of the documentation.
+     *
+     * @param configurableComponent the component to describe
+     * @param xmlStreamWriter the stream writer
+     * @throws XMLStreamException thrown if there was a problem writing to the
+     * XML stream
+     */
+    protected void writeAdditionalBodyInfo(final ConfigurableComponent 
configurableComponent,
+            final XMLStreamWriter xmlStreamWriter) throws XMLStreamException {
+
+    }
+
+    private void writeTags(final ConfigurableComponent configurableComponent,
+            final XMLStreamWriter xmlStreamWriter) throws XMLStreamException {
+        final Tags tags = 
configurableComponent.getClass().getAnnotation(Tags.class);
+        xmlStreamWriter.writeStartElement("h3");
+        xmlStreamWriter.writeCharacters("Tags: ");
+        xmlStreamWriter.writeEndElement();
+        xmlStreamWriter.writeStartElement("p");
+        if (tags != null) {
+            final String tagString = join(tags.value(), ", ");
+            xmlStreamWriter.writeCharacters(tagString);
+        } else {
+            xmlStreamWriter.writeCharacters("None.");
+        }
+        xmlStreamWriter.writeEndElement();
+    }
+
+    static String join(final String[] toJoin, final String delimiter) {
+        final StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < toJoin.length; i++) {
+            sb.append(toJoin[i]);
+            if (i < toJoin.length - 1) {
+                sb.append(delimiter);
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Writes a description of the configurable component.
+     *
+     * @param configurableComponent the component to describe
+     * @param xmlStreamWriter the stream writer
+     * @param hasAdditionalDetails whether there are additional details
+     * available as 'additionalDetails.html'
+     * @throws XMLStreamException thrown if there was a problem writing to the
+     * XML stream
+     */
+    protected void writeDescription(final ConfigurableComponent 
configurableComponent,
+            final XMLStreamWriter xmlStreamWriter, final boolean 
hasAdditionalDetails)
+            throws XMLStreamException {
+        writeSimpleElement(xmlStreamWriter, "h2", "Description: ");
+        writeSimpleElement(xmlStreamWriter, "p", 
getDescription(configurableComponent));
+        if (hasAdditionalDetails) {
+            xmlStreamWriter.writeStartElement("p");
+
+            writeLink(xmlStreamWriter, "Additional Details...", 
ADDITIONAL_DETAILS_HTML);
+
+            xmlStreamWriter.writeEndElement();
+        }
+    }
+
+    /**
+     * Gets a description of the ConfigurableComponent using the
+     * CapabilityDescription annotation.
+     *
+     * @param configurableComponent the component to describe
+     * @return a description of the configurableComponent
+     */
+    protected String getDescription(final ConfigurableComponent 
configurableComponent) {
+        final CapabilityDescription capabilityDescription = 
configurableComponent.getClass().getAnnotation(
+                CapabilityDescription.class);
+
+        final String description;
+        if (capabilityDescription != null) {
+            description = capabilityDescription.value();
+        } else {
+            description = "No description provided.";
+        }
+
+        return description;
+    }
+
+    /**
+     * Writes the PropertyDescriptors out as a table.
+     *
+     * @param configurableComponent the component to describe
+     * @param xmlStreamWriter the stream writer
+     * @throws XMLStreamException thrown if there was a problem writing to the
+     * XML Stream
+     */
+    protected void writeProperties(final ConfigurableComponent 
configurableComponent,
+            final XMLStreamWriter xmlStreamWriter) throws XMLStreamException {
+
+        final List<PropertyDescriptor> properties = 
configurableComponent.getPropertyDescriptors();
+        writeSimpleElement(xmlStreamWriter, "h3", "Properties: ");
+
+        if (properties.size() > 0) {
+            final boolean containsExpressionLanguage = 
containsExpressionLanguage(configurableComponent);
+            final boolean containsSensitiveProperties = 
containsSensitiveProperties(configurableComponent);
+            xmlStreamWriter.writeStartElement("p");
+            xmlStreamWriter.writeCharacters("In the list below, the names of 
required properties appear in ");
+            writeSimpleElement(xmlStreamWriter, "strong", "bold");
+            xmlStreamWriter.writeCharacters(". Any other properties (not in 
bold) are considered optional. " +
+                    "The table also indicates any default values");
+            if (containsExpressionLanguage) {
+                if (!containsSensitiveProperties) {
+                    xmlStreamWriter.writeCharacters(", and ");
+                } else {
+                    xmlStreamWriter.writeCharacters(", ");
+                }
+                xmlStreamWriter.writeCharacters("whether a property supports 
the ");
+                writeLink(xmlStreamWriter, "NiFi Expression Language", 
"../../html/expression-language-guide.html");
+            }
+            if (containsSensitiveProperties) {
+                xmlStreamWriter.writeCharacters(", and whether a property is 
considered " + "\"sensitive\", meaning that its value will be encrypted. Before 
entering a "
+                        + "value in a sensitive property, ensure that the ");
+
+                writeSimpleElement(xmlStreamWriter, "strong", 
"nifi.properties");
+                xmlStreamWriter.writeCharacters(" file has " + "an entry for 
the property ");
+                writeSimpleElement(xmlStreamWriter, "strong", 
"nifi.sensitive.props.key");
+            }
+            xmlStreamWriter.writeCharacters(".");
+            xmlStreamWriter.writeEndElement();
+
+            xmlStreamWriter.writeStartElement("table");
+            xmlStreamWriter.writeAttribute("id", "properties");
+
+            // write the header row
+            xmlStreamWriter.writeStartElement("tr");
+            writeSimpleElement(xmlStreamWriter, "th", "Name");
+            writeSimpleElement(xmlStreamWriter, "th", "Default Value");
+            writeSimpleElement(xmlStreamWriter, "th", "Allowable Values");
+            writeSimpleElement(xmlStreamWriter, "th", "Description");
+            xmlStreamWriter.writeEndElement();
+
+            // write the individual properties
+            for (PropertyDescriptor property : properties) {
+                xmlStreamWriter.writeStartElement("tr");
+                xmlStreamWriter.writeStartElement("td");
+                xmlStreamWriter.writeAttribute("id", "name");
+                if (property.isRequired()) {
+                    writeSimpleElement(xmlStreamWriter, "strong", 
property.getDisplayName());
+                } else {
+                    xmlStreamWriter.writeCharacters(property.getDisplayName());
+                }
+
+                xmlStreamWriter.writeEndElement();
+                writeSimpleElement(xmlStreamWriter, "td", 
property.getDefaultValue(), false, "default-value");
+                xmlStreamWriter.writeStartElement("td");
+                xmlStreamWriter.writeAttribute("id", "allowable-values");
+                writeValidValues(xmlStreamWriter, property);
+                xmlStreamWriter.writeEndElement();
+                xmlStreamWriter.writeStartElement("td");
+                xmlStreamWriter.writeAttribute("id", "description");
+                if (property.getDescription() != null && 
property.getDescription().trim().length() > 0) {
+                    xmlStreamWriter.writeCharacters(property.getDescription());
+                } else {
+                    xmlStreamWriter.writeCharacters("No Description 
Provided.");
+                }
+
+                if (property.isSensitive()) {
+                    xmlStreamWriter.writeEmptyElement("br");
+                    writeSimpleElement(xmlStreamWriter, "strong", "Sensitive 
Property: true");
+                }
+
+                if (property.isExpressionLanguageSupported()) {
+                    xmlStreamWriter.writeEmptyElement("br");
+                    writeSimpleElement(xmlStreamWriter, "strong", "Supports 
Expression Language: true");
+                }
+                xmlStreamWriter.writeEndElement();
+
+                xmlStreamWriter.writeEndElement();
+            }
+
+            // TODO support dynamic properties...
+            xmlStreamWriter.writeEndElement();
+
+        } else {
+            writeSimpleElement(xmlStreamWriter, "p", "This component has no 
required or optional properties.");
+        }
+    }
+
+    /**
+     * Indicates whether or not the component contains at least one sensitive 
property.
+     *
+     * @param component the component to interogate
+     * @return whether or not the component contains at least one sensitive 
property.
+     */
+    private boolean containsSensitiveProperties(final ConfigurableComponent 
component) {
+        for (PropertyDescriptor descriptor : 
component.getPropertyDescriptors()) {
+            if (descriptor.isSensitive()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Indicates whether or not the component contains at least one property 
that supports Expression Language.
+     *
+     * @param component the component to interogate
+     * @return whether or not the component contains at least one sensitive 
property.
+     */
+    private boolean containsExpressionLanguage(final ConfigurableComponent 
component) {
+        for (PropertyDescriptor descriptor : 
component.getPropertyDescriptors()) {
+            if (descriptor.isExpressionLanguageSupported()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void writeDynamicProperties(final ConfigurableComponent 
configurableComponent,
+            final XMLStreamWriter xmlStreamWriter) throws XMLStreamException {
+
+        final List<DynamicProperty> dynamicProperties = 
getDynamicProperties(configurableComponent);
+
+        if (dynamicProperties != null && dynamicProperties.size() > 0) {
+            writeSimpleElement(xmlStreamWriter, "h3", "Dynamic Properties: ");
+            xmlStreamWriter.writeStartElement("p");
+            xmlStreamWriter
+                    .writeCharacters("Dynamic Properties allow the user to 
specify both the name and value of a property.");
+            xmlStreamWriter.writeStartElement("table");
+            xmlStreamWriter.writeAttribute("id", "dynamic-properties");
+            xmlStreamWriter.writeStartElement("tr");
+            writeSimpleElement(xmlStreamWriter, "th", "Name");
+            writeSimpleElement(xmlStreamWriter, "th", "Value");
+            writeSimpleElement(xmlStreamWriter, "th", "Description");
+            xmlStreamWriter.writeEndElement();
+            for (final DynamicProperty dynamicProperty : dynamicProperties) {
+                xmlStreamWriter.writeStartElement("tr");
+                writeSimpleElement(xmlStreamWriter, "td", 
dynamicProperty.name(), false, "name");
+                writeSimpleElement(xmlStreamWriter, "td", 
dynamicProperty.value(), false, "value");
+                xmlStreamWriter.writeStartElement("td");
+                xmlStreamWriter.writeCharacters(dynamicProperty.description());
+                if (dynamicProperty.supportsExpressionLanguage()) {
+                    xmlStreamWriter.writeEmptyElement("br");
+                    writeSimpleElement(xmlStreamWriter, "strong", "Supports 
Expression Language: true");
+                }
+                xmlStreamWriter.writeEndElement();
+                xmlStreamWriter.writeEndElement();
+            }
+
+            xmlStreamWriter.writeEndElement();
+            xmlStreamWriter.writeEndElement();
+        }
+    }
+
+    private List<DynamicProperty> getDynamicProperties(ConfigurableComponent 
configurableComponent) {
+        final List<DynamicProperty> dynamicProperties = new ArrayList<>();
+        final DynamicProperties dynProps = 
configurableComponent.getClass().getAnnotation(DynamicProperties.class);
+        if (dynProps != null) {
+            for (final DynamicProperty dynProp : dynProps.value()) {
+                dynamicProperties.add(dynProp);
+            }
+        }
+
+        final DynamicProperty dynProp = 
configurableComponent.getClass().getAnnotation(DynamicProperty.class);
+        if (dynProp != null) {
+            dynamicProperties.add(dynProp);
+        }
+
+        return dynamicProperties;
+    }
+
+    private void writeValidValueDescription(XMLStreamWriter xmlStreamWriter, 
String description)
+            throws XMLStreamException {
+        xmlStreamWriter.writeCharacters(" ");
+        xmlStreamWriter.writeStartElement("img");
+        xmlStreamWriter.writeAttribute("src", 
"../../html/images/iconInfo.png");
+        xmlStreamWriter.writeAttribute("alt", description);
+        xmlStreamWriter.writeAttribute("title", description);
+        xmlStreamWriter.writeEndElement();
+
+    }
+
+    /**
+     * Interrogates a PropertyDescriptor to get a list of AllowableValues, if
+     * there are none, nothing is written to the stream.
+     *
+     * @param xmlStreamWriter the stream writer to use
+     * @param property the property to describe
+     * @throws XMLStreamException thrown if there was a problem writing to the
+     * XML Stream
+     */
+    protected void writeValidValues(XMLStreamWriter xmlStreamWriter, 
PropertyDescriptor property)
+            throws XMLStreamException {
+        if (property.getAllowableValues() != null && 
property.getAllowableValues().size() > 0) {
+            xmlStreamWriter.writeStartElement("ul");
+            for (AllowableValue value : property.getAllowableValues()) {
+                xmlStreamWriter.writeStartElement("li");
+                xmlStreamWriter.writeCharacters(value.getDisplayName());
+
+                if (value.getDescription() != null) {
+                    writeValidValueDescription(xmlStreamWriter, 
value.getDescription());
+                }
+                xmlStreamWriter.writeEndElement();
+
+            }
+            xmlStreamWriter.writeEndElement();
+        } else if (property.getControllerServiceDefinition() != null) {
+            Class<? extends ControllerService> controllerServiceClass = 
property.getControllerServiceDefinition();
+
+            writeSimpleElement(xmlStreamWriter, "strong", "Controller Service 
API: ");
+            xmlStreamWriter.writeEmptyElement("br");
+            
xmlStreamWriter.writeCharacters(controllerServiceClass.getSimpleName());
+
+            final List<Class<? extends ControllerService>> implementations = 
lookupControllerServiceImpls(controllerServiceClass);
+            xmlStreamWriter.writeEmptyElement("br");
+            if (implementations.size() > 0) {
+                final String title = implementations.size() > 1 ? 
"Implementations: " : "Implementation:";
+                writeSimpleElement(xmlStreamWriter, "strong", title);
+                for (int i = 0; i < implementations.size(); i++) {
+                    xmlStreamWriter.writeEmptyElement("br");
+                    writeLinkForComponent(xmlStreamWriter, 
implementations.get(i));
+                }
+            } else {
+                xmlStreamWriter.writeCharacters("No implementations found.");
+            }
+        }
+    }
+
+    /**
+     * Writes a begin element, then text, then end element for the element of a
+     * users choosing. Example: &lt;p&gt;text&lt;/p&gt;
+     *
+     * @param writer the stream writer to use
+     * @param elementName the name of the element
+     * @param characters the characters to insert into the element
+     * @param strong whether the characters should be strong or not.
+     * @throws XMLStreamException thrown if there was a problem writing to the
+     * stream.
+     */
+    protected final static void writeSimpleElement(final XMLStreamWriter 
writer, final String elementName,
+            final String characters, boolean strong) throws XMLStreamException 
{
+        writeSimpleElement(writer, elementName, characters, strong, null);
+    }
+
+    /**
+     * Writes a begin element, an id attribute(if specified), then text, then
+     * end element for element of the users choosing. Example: &lt;p
+     * id="p-id"&gt;text&lt;/p&gt;
+     *
+     * @param writer the stream writer to use
+     * @param elementName the name of the element
+     * @param characters the text of the element
+     * @param strong whether to bold the text of the element or not
+     * @param id the id of the element. specifying null will cause no element 
to
+     * be written.
+     * @throws XMLStreamException xse
+     */
+    protected final static void writeSimpleElement(final XMLStreamWriter 
writer, final String elementName,
+            final String characters, boolean strong, String id) throws 
XMLStreamException {
+        writer.writeStartElement(elementName);
+        if (id != null) {
+            writer.writeAttribute("id", id);
+        }
+        if (strong) {
+            writer.writeStartElement("strong");
+        }
+        writer.writeCharacters(characters);
+        if (strong) {
+            writer.writeEndElement();
+        }
+        writer.writeEndElement();
+    }
+
+    /**
+     * Writes a begin element, then text, then end element for the element of a
+     * users choosing. Example: &lt;p&gt;text&lt;/p&gt;
+     *
+     * @param writer the stream writer to use
+     * @param elementName the name of the element
+     * @param characters the characters to insert into the element
+     * @throws XMLStreamException thrown if there was a problem writing to the
+     * stream
+     */
+    protected final static void writeSimpleElement(final XMLStreamWriter 
writer, final String elementName,
+            final String characters) throws XMLStreamException {
+        writeSimpleElement(writer, elementName, characters, false);
+    }
+
+    /**
+     * A helper method to write a link
+     *
+     * @param xmlStreamWriter the stream to write to
+     * @param text the text of the link
+     * @param location the location of the link
+     * @throws XMLStreamException thrown if there was a problem writing to the
+     * stream
+     */
+    protected void writeLink(final XMLStreamWriter xmlStreamWriter, final 
String text, final String location)
+            throws XMLStreamException {
+        xmlStreamWriter.writeStartElement("a");
+        xmlStreamWriter.writeAttribute("href", location);
+        xmlStreamWriter.writeCharacters(text);
+        xmlStreamWriter.writeEndElement();
+    }
+
+    /**
+     * Writes a link to another configurable component
+     *
+     * @param xmlStreamWriter the xml stream writer
+     * @param clazz the configurable component to link to
+     * @throws XMLStreamException thrown if there is a problem writing the XML
+     */
+    protected void writeLinkForComponent(final XMLStreamWriter 
xmlStreamWriter, final Class<?> clazz) throws XMLStreamException {
+        writeLink(xmlStreamWriter, clazz.getSimpleName(), "../" + 
clazz.getCanonicalName() + "/index.html");
+    }
+
+    /**
+     * Uses the {@link ExtensionManager} to discover any {@link 
ControllerService} implementations that implement a specific
+     * ControllerService API.
+     *
+     * @param parent the controller service API
+     * @return a list of controller services that implement the controller 
service API
+     */
+    private List<Class<? extends ControllerService>> 
lookupControllerServiceImpls(
+            final Class<? extends ControllerService> parent) {
+
+        final List<Class<? extends ControllerService>> implementations = new 
ArrayList<>();
+
+        // first get all ControllerService implementations
+        final Set<Class> controllerServices = Collections.emptySet();
+
+        // then iterate over all controller services looking for any that is a 
child of the parent
+        // ControllerService API that was passed in as a parameter
+        for (final Class<? extends ControllerService> controllerServiceClass : 
controllerServices) {
+            if (parent.isAssignableFrom(controllerServiceClass)) {
+                implementations.add(controllerServiceClass);
+            }
+        }
+
+        return implementations;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/af87b52b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/html/HtmlProcessorDocumentationWriter.java
----------------------------------------------------------------------
diff --git 
a/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/html/HtmlProcessorDocumentationWriter.java
 
b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/html/HtmlProcessorDocumentationWriter.java
new file mode 100644
index 0000000..4a15b50
--- /dev/null
+++ 
b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/html/HtmlProcessorDocumentationWriter.java
@@ -0,0 +1,256 @@
+/*
+ * 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.documentation.html;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+import org.apache.nifi.annotation.behavior.DynamicRelationship;
+import org.apache.nifi.annotation.behavior.ReadsAttribute;
+import org.apache.nifi.annotation.behavior.ReadsAttributes;
+import org.apache.nifi.annotation.behavior.WritesAttribute;
+import org.apache.nifi.annotation.behavior.WritesAttributes;
+import org.apache.nifi.components.ConfigurableComponent;
+import org.apache.nifi.processor.Processor;
+import org.apache.nifi.processor.Relationship;
+
+/**
+ * Writes documentation specific for a Processor. This includes everything for 
a
+ * ConfigurableComponent as well as Relationship information.
+ *
+ *
+ */
+public class HtmlProcessorDocumentationWriter extends HtmlDocumentationWriter {
+
+    @Override
+    protected void writeAdditionalBodyInfo(final ConfigurableComponent 
configurableComponent,
+            final XMLStreamWriter xmlStreamWriter) throws XMLStreamException {
+        final Processor processor = (Processor) configurableComponent;
+        writeRelationships(processor, xmlStreamWriter);
+        writeDynamicRelationships(processor, xmlStreamWriter);
+        writeAttributeInfo(processor, xmlStreamWriter);
+    }
+
+    /**
+     * Writes all the attributes that a processor says it reads and writes
+     *
+     * @param processor the processor to describe
+     * @param xmlStreamWriter the xml stream writer to use
+     * @throws XMLStreamException thrown if there was a problem writing the XML
+     */
+    private void writeAttributeInfo(Processor processor, XMLStreamWriter 
xmlStreamWriter)
+            throws XMLStreamException {
+
+        handleReadsAttributes(xmlStreamWriter, processor);
+        handleWritesAttributes(xmlStreamWriter, processor);
+    }
+
+    private String defaultIfBlank(final String test, final String 
defaultValue) {
+        if (test == null || test.trim().isEmpty()) {
+            return defaultValue;
+        }
+        return test;
+    }
+
+    /**
+     * Writes out just the attributes that are being read in a table form.
+     *
+     * @param xmlStreamWriter the xml stream writer to use
+     * @param processor the processor to describe
+     * @throws XMLStreamException xse
+     */
+    private void handleReadsAttributes(XMLStreamWriter xmlStreamWriter, final 
Processor processor)
+            throws XMLStreamException {
+        List<ReadsAttribute> attributesRead = getReadsAttributes(processor);
+
+        writeSimpleElement(xmlStreamWriter, "h3", "Reads Attributes: ");
+        if (attributesRead.size() > 0) {
+            xmlStreamWriter.writeStartElement("table");
+            xmlStreamWriter.writeAttribute("id", "reads-attributes");
+            xmlStreamWriter.writeStartElement("tr");
+            writeSimpleElement(xmlStreamWriter, "th", "Name");
+            writeSimpleElement(xmlStreamWriter, "th", "Description");
+            xmlStreamWriter.writeEndElement();
+            for (ReadsAttribute attribute : attributesRead) {
+                xmlStreamWriter.writeStartElement("tr");
+                writeSimpleElement(xmlStreamWriter, "td",
+                        defaultIfBlank(attribute.attribute(), "Not 
Specified"));
+                // TODO allow for HTML characters here.
+                writeSimpleElement(xmlStreamWriter, "td",
+                        defaultIfBlank(attribute.description(), "Not 
Specified"));
+                xmlStreamWriter.writeEndElement();
+
+            }
+            xmlStreamWriter.writeEndElement();
+
+        } else {
+            xmlStreamWriter.writeCharacters("None specified.");
+        }
+    }
+
+    /**
+     * Writes out just the attributes that are being written to in a table 
form.
+     *
+     * @param xmlStreamWriter the xml stream writer to use
+     * @param processor the processor to describe
+     * @throws XMLStreamException xse
+     */
+    private void handleWritesAttributes(XMLStreamWriter xmlStreamWriter, final 
Processor processor)
+            throws XMLStreamException {
+        List<WritesAttribute> attributesRead = getWritesAttributes(processor);
+
+        writeSimpleElement(xmlStreamWriter, "h3", "Writes Attributes: ");
+        if (attributesRead.size() > 0) {
+            xmlStreamWriter.writeStartElement("table");
+            xmlStreamWriter.writeAttribute("id", "writes-attributes");
+            xmlStreamWriter.writeStartElement("tr");
+            writeSimpleElement(xmlStreamWriter, "th", "Name");
+            writeSimpleElement(xmlStreamWriter, "th", "Description");
+            xmlStreamWriter.writeEndElement();
+            for (WritesAttribute attribute : attributesRead) {
+                xmlStreamWriter.writeStartElement("tr");
+                writeSimpleElement(xmlStreamWriter, "td",
+                        defaultIfBlank(attribute.attribute(), "Not 
Specified"));
+                // TODO allow for HTML characters here.
+                writeSimpleElement(xmlStreamWriter, "td",
+                        defaultIfBlank(attribute.description(), "Not 
Specified"));
+                xmlStreamWriter.writeEndElement();
+            }
+            xmlStreamWriter.writeEndElement();
+
+        } else {
+            xmlStreamWriter.writeCharacters("None specified.");
+        }
+    }
+
+    /**
+     * Collects the attributes that a processor is reading from.
+     *
+     * @param processor the processor to describe
+     * @return the list of attributes that processor is reading
+     */
+    private List<ReadsAttribute> getReadsAttributes(Processor processor) {
+        List<ReadsAttribute> attributes = new ArrayList<>();
+
+        ReadsAttributes readsAttributes = 
processor.getClass().getAnnotation(ReadsAttributes.class);
+        if (readsAttributes != null) {
+            attributes.addAll(Arrays.asList(readsAttributes.value()));
+        }
+
+        ReadsAttribute readsAttribute = 
processor.getClass().getAnnotation(ReadsAttribute.class);
+        if (readsAttribute != null) {
+            attributes.add(readsAttribute);
+        }
+
+        return attributes;
+    }
+
+    /**
+     * Collects the attributes that a processor is writing to.
+     *
+     * @param processor the processor to describe
+     * @return the list of attributes the processor is writing
+     */
+    private List<WritesAttribute> getWritesAttributes(Processor processor) {
+        List<WritesAttribute> attributes = new ArrayList<>();
+
+        WritesAttributes writesAttributes = 
processor.getClass().getAnnotation(WritesAttributes.class);
+        if (writesAttributes != null) {
+            attributes.addAll(Arrays.asList(writesAttributes.value()));
+        }
+
+        WritesAttribute writeAttribute = 
processor.getClass().getAnnotation(WritesAttribute.class);
+        if (writeAttribute != null) {
+            attributes.add(writeAttribute);
+        }
+
+        return attributes;
+    }
+
+    /**
+     * Writes a table describing the relations a processor has.
+     *
+     * @param processor the processor to describe
+     * @param xmlStreamWriter the stream writer to use
+     * @throws XMLStreamException thrown if there was a problem writing the xml
+     */
+    private void writeRelationships(final Processor processor, final 
XMLStreamWriter xmlStreamWriter)
+            throws XMLStreamException {
+
+        writeSimpleElement(xmlStreamWriter, "h3", "Relationships: ");
+
+        if (processor.getRelationships().size() > 0) {
+            xmlStreamWriter.writeStartElement("table");
+            xmlStreamWriter.writeAttribute("id", "relationships");
+            xmlStreamWriter.writeStartElement("tr");
+            writeSimpleElement(xmlStreamWriter, "th", "Name");
+            writeSimpleElement(xmlStreamWriter, "th", "Description");
+            xmlStreamWriter.writeEndElement();
+
+            for (Relationship relationship : processor.getRelationships()) {
+                xmlStreamWriter.writeStartElement("tr");
+                writeSimpleElement(xmlStreamWriter, "td", 
relationship.getName());
+                writeSimpleElement(xmlStreamWriter, "td", 
relationship.getDescription());
+                xmlStreamWriter.writeEndElement();
+            }
+            xmlStreamWriter.writeEndElement();
+        } else {
+            xmlStreamWriter.writeCharacters("This processor has no 
relationships.");
+        }
+    }
+
+    private void writeDynamicRelationships(final Processor processor, final 
XMLStreamWriter xmlStreamWriter) throws XMLStreamException {
+
+        List<DynamicRelationship> dynamicRelationships = 
getDynamicRelationships(processor);
+
+        if (dynamicRelationships.size() > 0) {
+            writeSimpleElement(xmlStreamWriter, "h3", "Dynamic Relationships: 
");
+            xmlStreamWriter.writeStartElement("p");
+            xmlStreamWriter.writeCharacters("A Dynamic Relationship may be 
created based on how the user configures the Processor.");
+            xmlStreamWriter.writeStartElement("table");
+            xmlStreamWriter.writeAttribute("id", "dynamic-relationships");
+            xmlStreamWriter.writeStartElement("tr");
+            writeSimpleElement(xmlStreamWriter, "th", "Name");
+            writeSimpleElement(xmlStreamWriter, "th", "Description");
+            xmlStreamWriter.writeEndElement();
+
+            for (DynamicRelationship dynamicRelationship : 
dynamicRelationships) {
+                xmlStreamWriter.writeStartElement("tr");
+                writeSimpleElement(xmlStreamWriter, "td", 
dynamicRelationship.name());
+                writeSimpleElement(xmlStreamWriter, "td", 
dynamicRelationship.description());
+                xmlStreamWriter.writeEndElement();
+            }
+            xmlStreamWriter.writeEndElement();
+            xmlStreamWriter.writeEndElement();
+        }
+    }
+
+    private List<DynamicRelationship> getDynamicRelationships(Processor 
processor) {
+        List<DynamicRelationship> results = new ArrayList<>();
+
+        DynamicRelationship dynamicRelationships = 
processor.getClass().getAnnotation(DynamicRelationship.class);
+        if (dynamicRelationships != null) {
+            results.add(dynamicRelationships);
+        }
+
+        return results;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/af87b52b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/init/ControllerServiceInitializer.java
----------------------------------------------------------------------
diff --git 
a/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/init/ControllerServiceInitializer.java
 
b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/init/ControllerServiceInitializer.java
new file mode 100644
index 0000000..dc9f82f
--- /dev/null
+++ 
b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/init/ControllerServiceInitializer.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.documentation.init;
+
+import org.apache.nifi.annotation.lifecycle.OnShutdown;
+import org.apache.nifi.components.ConfigurableComponent;
+import org.apache.nifi.controller.ControllerService;
+import org.apache.nifi.documentation.ConfigurableComponentInitializer;
+import org.apache.nifi.documentation.mock.MockConfigurationContext;
+import 
org.apache.nifi.documentation.mock.MockControllerServiceInitializationContext;
+import org.apache.nifi.documentation.mock.MockProcessorLogger;
+import org.apache.nifi.documentation.util.ReflectionUtils;
+import org.apache.nifi.logging.ProcessorLog;
+import org.apache.nifi.reporting.InitializationException;
+
+/**
+ * Initializes a ControllerService using a 
MockControllerServiceInitializationContext
+ *
+ *
+ */
+public class ControllerServiceInitializer implements 
ConfigurableComponentInitializer {
+
+    @Override
+    public void initialize(ConfigurableComponent component) throws 
InitializationException {
+        ControllerService controllerService = (ControllerService) component;
+
+        controllerService.initialize(new 
MockControllerServiceInitializationContext());
+
+    }
+
+    @Override
+    public void teardown(ConfigurableComponent component) {
+
+        final ProcessorLog logger = new MockProcessorLogger();
+        final MockConfigurationContext context = new 
MockConfigurationContext();
+        ReflectionUtils.quietlyInvokeMethodsWithAnnotations(OnShutdown.class, 
org.apache.nifi.processor.annotation.OnShutdown.class, component, logger, 
context);
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/af87b52b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/init/ProcessorInitializer.java
----------------------------------------------------------------------
diff --git 
a/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/init/ProcessorInitializer.java
 
b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/init/ProcessorInitializer.java
new file mode 100644
index 0000000..f4ca36d
--- /dev/null
+++ 
b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/init/ProcessorInitializer.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.documentation.init;
+
+import org.apache.nifi.annotation.lifecycle.OnShutdown;
+import org.apache.nifi.components.ConfigurableComponent;
+import org.apache.nifi.documentation.ConfigurableComponentInitializer;
+import org.apache.nifi.documentation.mock.MockProcessContext;
+import org.apache.nifi.documentation.mock.MockProcessorInitializationContext;
+import org.apache.nifi.documentation.mock.MockProcessorLogger;
+import org.apache.nifi.documentation.util.ReflectionUtils;
+import org.apache.nifi.logging.ProcessorLog;
+import org.apache.nifi.processor.Processor;
+
+/**
+ * Initializes a Procesor using a MockProcessorInitializationContext
+ *
+ *
+ */
+public class ProcessorInitializer implements ConfigurableComponentInitializer {
+
+    @Override
+    public void initialize(ConfigurableComponent component) {
+        Processor processor = (Processor) component;
+
+        processor.initialize(new MockProcessorInitializationContext());
+
+    }
+
+    @Override
+    public void teardown(ConfigurableComponent component) {
+        Processor processor = (Processor) component;
+
+        final ProcessorLog logger = new MockProcessorLogger();
+        final MockProcessContext context = new MockProcessContext();
+        ReflectionUtils.quietlyInvokeMethodsWithAnnotations(OnShutdown.class, 
org.apache.nifi.processor.annotation.OnShutdown.class, processor, logger, 
context);
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/af87b52b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/init/ReportingTaskingInitializer.java
----------------------------------------------------------------------
diff --git 
a/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/init/ReportingTaskingInitializer.java
 
b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/init/ReportingTaskingInitializer.java
new file mode 100644
index 0000000..0a43f0b
--- /dev/null
+++ 
b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/init/ReportingTaskingInitializer.java
@@ -0,0 +1,52 @@
+/*
+ * 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.documentation.init;
+
+import org.apache.nifi.annotation.lifecycle.OnShutdown;
+import org.apache.nifi.components.ConfigurableComponent;
+import org.apache.nifi.documentation.ConfigurableComponentInitializer;
+import org.apache.nifi.documentation.mock.MockConfigurationContext;
+import org.apache.nifi.documentation.mock.MockProcessorLogger;
+import org.apache.nifi.documentation.mock.MockReportingInitializationContext;
+import org.apache.nifi.documentation.util.ReflectionUtils;
+import org.apache.nifi.reporting.InitializationException;
+import org.apache.nifi.reporting.ReportingTask;
+
+/**
+ * Initializes a ReportingTask using a MockReportingInitializationContext;
+ *
+ *
+ */
+public class ReportingTaskingInitializer implements 
ConfigurableComponentInitializer {
+
+    @Override
+    public void initialize(ConfigurableComponent component) throws 
InitializationException {
+        ReportingTask reportingTask = (ReportingTask) component;
+
+        reportingTask.initialize(new MockReportingInitializationContext());
+
+    }
+
+    @Override
+    public void teardown(ConfigurableComponent component) {
+        ReportingTask reportingTask = (ReportingTask) component;
+
+        final MockConfigurationContext context = new 
MockConfigurationContext();
+        ReflectionUtils.quietlyInvokeMethodsWithAnnotations(OnShutdown.class, 
org.apache.nifi.processor.annotation.OnShutdown.class, reportingTask, new 
MockProcessorLogger(), context);
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/af87b52b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/mock/MockConfigurationContext.java
----------------------------------------------------------------------
diff --git 
a/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/mock/MockConfigurationContext.java
 
b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/mock/MockConfigurationContext.java
new file mode 100644
index 0000000..6c9ec9d
--- /dev/null
+++ 
b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/mock/MockConfigurationContext.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.documentation.mock;
+
+import java.util.Collections;
+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;
+
+public class MockConfigurationContext implements ConfigurationContext {
+
+    @Override
+    public PropertyValue getProperty(PropertyDescriptor property) {
+        return null;
+    }
+
+    @Override
+    public Map<PropertyDescriptor, String> getProperties() {
+        return Collections.emptyMap();
+    }
+
+    @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/af87b52b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/mock/MockControllerServiceInitializationContext.java
----------------------------------------------------------------------
diff --git 
a/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/mock/MockControllerServiceInitializationContext.java
 
b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/mock/MockControllerServiceInitializationContext.java
new file mode 100644
index 0000000..14076a3
--- /dev/null
+++ 
b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/mock/MockControllerServiceInitializationContext.java
@@ -0,0 +1,46 @@
+/*
+ * 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.documentation.mock;
+
+import org.apache.nifi.controller.ControllerServiceInitializationContext;
+import org.apache.nifi.controller.ControllerServiceLookup;
+import org.apache.nifi.logging.ComponentLog;
+
+/**
+ * A Mock ControllerServiceInitializationContext so that ControllerServices can
+ * be initialized for the purpose of generating documentation.
+ *
+ *
+ */
+public class MockControllerServiceInitializationContext implements 
ControllerServiceInitializationContext {
+
+    @Override
+    public String getIdentifier() {
+        return "mock-controller-service";
+    }
+
+    @Override
+    public ControllerServiceLookup getControllerServiceLookup() {
+        return new MockControllerServiceLookup();
+    }
+
+    @Override
+    public ComponentLog getLogger() {
+        return new MockProcessorLogger();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/af87b52b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/mock/MockControllerServiceLookup.java
----------------------------------------------------------------------
diff --git 
a/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/mock/MockControllerServiceLookup.java
 
b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/mock/MockControllerServiceLookup.java
new file mode 100644
index 0000000..5c60881
--- /dev/null
+++ 
b/nifi-documentation-plugin/src/main/java/org/apache/nifi/documentation/mock/MockControllerServiceLookup.java
@@ -0,0 +1,65 @@
+/*
+ * 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.documentation.mock;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.apache.nifi.controller.ControllerService;
+import org.apache.nifi.controller.ControllerServiceLookup;
+
+/**
+ * A Mock ControllerServiceLookup that can be used so that
+ * ConfigurableComponents can be initialized for the purpose of generating
+ * documentation
+ *
+ *
+ */
+public class MockControllerServiceLookup implements ControllerServiceLookup {
+
+    @Override
+    public ControllerService getControllerService(String serviceIdentifier) {
+        return null;
+    }
+
+    @Override
+    public boolean isControllerServiceEnabled(String serviceIdentifier) {
+        return false;
+    }
+
+    @Override
+    public boolean isControllerServiceEnabled(ControllerService service) {
+        return false;
+    }
+
+    @Override
+    public Set<String> getControllerServiceIdentifiers(Class<? extends 
ControllerService> serviceType)
+            throws IllegalArgumentException {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public boolean isControllerServiceEnabling(String serviceIdentifier) {
+        return false;
+    }
+
+    @Override
+    public String getControllerServiceName(String serviceIdentifier) {
+        return serviceIdentifier;
+    }
+
+}

Reply via email to