You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by tk...@apache.org on 2015/11/19 07:21:00 UTC

[17/24] nifi git commit: NIFI-1054: Fixing Line endings of source code

http://git-wip-us.apache.org/repos/asf/nifi/blob/e2d3d1b7/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/DocGenerator.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/DocGenerator.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/DocGenerator.java
index 327da04..3b29102 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/DocGenerator.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/DocGenerator.java
@@ -1,180 +1,180 @@
-/*
- * 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.BufferedOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.FilenameFilter;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.apache.nifi.components.ConfigurableComponent;
-import org.apache.nifi.controller.ControllerService;
-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.nar.ExtensionManager;
-import org.apache.nifi.processor.Processor;
-import org.apache.nifi.reporting.InitializationException;
-import org.apache.nifi.reporting.ReportingTask;
-import org.apache.nifi.util.NiFiProperties;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Uses the ExtensionManager to get a list of Processor, ControllerService, and
- * Reporting Task classes that were loaded and generate documentation for them.
- *
- *
- */
-public class DocGenerator {
-
-    private static final Logger logger = LoggerFactory.getLogger(DocGenerator.class);
-
-    /**
-     * Generates documentation into the work/docs dir specified by
-     * NiFiProperties.
-     *
-     * @param properties to lookup nifi properties
-     */
-    public static void generate(final NiFiProperties properties) {
-        @SuppressWarnings("rawtypes")
-        final Set<Class> extensionClasses = new HashSet<>();
-        extensionClasses.addAll(ExtensionManager.getExtensions(Processor.class));
-        extensionClasses.addAll(ExtensionManager.getExtensions(ControllerService.class));
-        extensionClasses.addAll(ExtensionManager.getExtensions(ReportingTask.class));
-
-        final File explodedNiFiDocsDir = properties.getComponentDocumentationWorkingDirectory();
-
-        logger.debug("Generating documentation for: " + extensionClasses.size() + " components in: "
-                + explodedNiFiDocsDir);
-
-        for (final Class<?> extensionClass : extensionClasses) {
-            if (ConfigurableComponent.class.isAssignableFrom(extensionClass)) {
-                final Class<? extends ConfigurableComponent> componentClass = extensionClass.asSubclass(ConfigurableComponent.class);
-                try {
-                    logger.debug("Documenting: " + componentClass);
-                    document(explodedNiFiDocsDir, componentClass);
-                } catch (Exception e) {
-                    logger.warn("Unable to document: " + componentClass, e);
-                }
-            }
-        }
-    }
-
-    /**
-     * Generates the documentation for a particular configurable comopnent. Will
-     * check to see if an "additionalDetails.html" file exists and will link
-     * that from the generated documentation.
-     *
-     * @param docsDir the work\docs\components dir to stick component
-     * documentation in
-     * @param componentClass the class to document
-     * @throws InstantiationException ie
-     * @throws IllegalAccessException iae
-     * @throws IOException ioe
-     * @throws InitializationException ie
-     */
-    private static void document(final File docsDir, final Class<? extends ConfigurableComponent> componentClass)
-            throws InstantiationException, IllegalAccessException, IOException, InitializationException {
-
-        final ConfigurableComponent component = componentClass.newInstance();
-        final ConfigurableComponentInitializer initializer = getComponentInitializer(componentClass);
-        initializer.initialize(component);
-
-        final DocumentationWriter writer = getDocumentWriter(componentClass);
-
-        final File directory = new File(docsDir, componentClass.getCanonicalName());
-        directory.mkdirs();
-
-        final File baseDocumenationFile = new File(directory, "index.html");
-        if (baseDocumenationFile.exists()) {
-            logger.warn(baseDocumenationFile + " already exists, overwriting!");
-        }
-
-        try (final OutputStream output = new BufferedOutputStream(new FileOutputStream(baseDocumenationFile))) {
-            writer.write(component, output, hasAdditionalInfo(directory));
-        }
-
-        initializer.teardown(component);
-    }
-
-    /**
-     * Returns the DocumentationWriter for the type of component. Currently
-     * Processor, ControllerService, and ReportingTask are supported.
-     *
-     * @param componentClass the class that requires a DocumentationWriter
-     * @return a DocumentationWriter capable of generating documentation for
-     * that specific type of class
-     */
-    private static DocumentationWriter getDocumentWriter(final Class<? extends ConfigurableComponent> componentClass) {
-        if (Processor.class.isAssignableFrom(componentClass)) {
-            return new HtmlProcessorDocumentationWriter();
-        } else if (ControllerService.class.isAssignableFrom(componentClass)) {
-            return new HtmlDocumentationWriter();
-        } else if (ReportingTask.class.isAssignableFrom(componentClass)) {
-            return new HtmlDocumentationWriter();
-        }
-
-        return null;
-    }
-
-    /**
-     * Returns a ConfigurableComponentInitializer for the type of component.
-     * Currently Processor, ControllerService and ReportingTask are supported.
-     *
-     * @param componentClass the class that requires a
-     * ConfigurableComponentInitializer
-     * @return a ConfigurableComponentInitializer capable of initializing that
-     * specific type of class
-     */
-    private static ConfigurableComponentInitializer getComponentInitializer(
-            final Class<? extends ConfigurableComponent> componentClass) {
-        if (Processor.class.isAssignableFrom(componentClass)) {
-            return new ProcessorInitializer();
-        } else if (ControllerService.class.isAssignableFrom(componentClass)) {
-            return new ControllerServiceInitializer();
-        } else if (ReportingTask.class.isAssignableFrom(componentClass)) {
-            return new ReportingTaskingInitializer();
-        }
-
-        return null;
-    }
-
-    /**
-     * Checks to see if a directory to write to has an additionalDetails.html in
-     * it already.
-     *
-     * @param directory to check
-     * @return true if additionalDetails.html exists, false otherwise.
-     */
-    private static boolean hasAdditionalInfo(File directory) {
-        return directory.list(new FilenameFilter() {
-
-            @Override
-            public boolean accept(File dir, String name) {
-                return name.equalsIgnoreCase(HtmlDocumentationWriter.ADDITIONAL_DETAILS_HTML);
-            }
-
-        }).length > 0;
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.documentation;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.nifi.components.ConfigurableComponent;
+import org.apache.nifi.controller.ControllerService;
+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.nar.ExtensionManager;
+import org.apache.nifi.processor.Processor;
+import org.apache.nifi.reporting.InitializationException;
+import org.apache.nifi.reporting.ReportingTask;
+import org.apache.nifi.util.NiFiProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Uses the ExtensionManager to get a list of Processor, ControllerService, and
+ * Reporting Task classes that were loaded and generate documentation for them.
+ *
+ *
+ */
+public class DocGenerator {
+
+    private static final Logger logger = LoggerFactory.getLogger(DocGenerator.class);
+
+    /**
+     * Generates documentation into the work/docs dir specified by
+     * NiFiProperties.
+     *
+     * @param properties to lookup nifi properties
+     */
+    public static void generate(final NiFiProperties properties) {
+        @SuppressWarnings("rawtypes")
+        final Set<Class> extensionClasses = new HashSet<>();
+        extensionClasses.addAll(ExtensionManager.getExtensions(Processor.class));
+        extensionClasses.addAll(ExtensionManager.getExtensions(ControllerService.class));
+        extensionClasses.addAll(ExtensionManager.getExtensions(ReportingTask.class));
+
+        final File explodedNiFiDocsDir = properties.getComponentDocumentationWorkingDirectory();
+
+        logger.debug("Generating documentation for: " + extensionClasses.size() + " components in: "
+                + explodedNiFiDocsDir);
+
+        for (final Class<?> extensionClass : extensionClasses) {
+            if (ConfigurableComponent.class.isAssignableFrom(extensionClass)) {
+                final Class<? extends ConfigurableComponent> componentClass = extensionClass.asSubclass(ConfigurableComponent.class);
+                try {
+                    logger.debug("Documenting: " + componentClass);
+                    document(explodedNiFiDocsDir, componentClass);
+                } catch (Exception e) {
+                    logger.warn("Unable to document: " + componentClass, e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Generates the documentation for a particular configurable comopnent. Will
+     * check to see if an "additionalDetails.html" file exists and will link
+     * that from the generated documentation.
+     *
+     * @param docsDir the work\docs\components dir to stick component
+     * documentation in
+     * @param componentClass the class to document
+     * @throws InstantiationException ie
+     * @throws IllegalAccessException iae
+     * @throws IOException ioe
+     * @throws InitializationException ie
+     */
+    private static void document(final File docsDir, final Class<? extends ConfigurableComponent> componentClass)
+            throws InstantiationException, IllegalAccessException, IOException, InitializationException {
+
+        final ConfigurableComponent component = componentClass.newInstance();
+        final ConfigurableComponentInitializer initializer = getComponentInitializer(componentClass);
+        initializer.initialize(component);
+
+        final DocumentationWriter writer = getDocumentWriter(componentClass);
+
+        final File directory = new File(docsDir, componentClass.getCanonicalName());
+        directory.mkdirs();
+
+        final File baseDocumenationFile = new File(directory, "index.html");
+        if (baseDocumenationFile.exists()) {
+            logger.warn(baseDocumenationFile + " already exists, overwriting!");
+        }
+
+        try (final OutputStream output = new BufferedOutputStream(new FileOutputStream(baseDocumenationFile))) {
+            writer.write(component, output, hasAdditionalInfo(directory));
+        }
+
+        initializer.teardown(component);
+    }
+
+    /**
+     * Returns the DocumentationWriter for the type of component. Currently
+     * Processor, ControllerService, and ReportingTask are supported.
+     *
+     * @param componentClass the class that requires a DocumentationWriter
+     * @return a DocumentationWriter capable of generating documentation for
+     * that specific type of class
+     */
+    private static DocumentationWriter getDocumentWriter(final Class<? extends ConfigurableComponent> componentClass) {
+        if (Processor.class.isAssignableFrom(componentClass)) {
+            return new HtmlProcessorDocumentationWriter();
+        } else if (ControllerService.class.isAssignableFrom(componentClass)) {
+            return new HtmlDocumentationWriter();
+        } else if (ReportingTask.class.isAssignableFrom(componentClass)) {
+            return new HtmlDocumentationWriter();
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns a ConfigurableComponentInitializer for the type of component.
+     * Currently Processor, ControllerService and ReportingTask are supported.
+     *
+     * @param componentClass the class that requires a
+     * ConfigurableComponentInitializer
+     * @return a ConfigurableComponentInitializer capable of initializing that
+     * specific type of class
+     */
+    private static ConfigurableComponentInitializer getComponentInitializer(
+            final Class<? extends ConfigurableComponent> componentClass) {
+        if (Processor.class.isAssignableFrom(componentClass)) {
+            return new ProcessorInitializer();
+        } else if (ControllerService.class.isAssignableFrom(componentClass)) {
+            return new ControllerServiceInitializer();
+        } else if (ReportingTask.class.isAssignableFrom(componentClass)) {
+            return new ReportingTaskingInitializer();
+        }
+
+        return null;
+    }
+
+    /**
+     * Checks to see if a directory to write to has an additionalDetails.html in
+     * it already.
+     *
+     * @param directory to check
+     * @return true if additionalDetails.html exists, false otherwise.
+     */
+    private static boolean hasAdditionalInfo(File directory) {
+        return directory.list(new FilenameFilter() {
+
+            @Override
+            public boolean accept(File dir, String name) {
+                return name.equalsIgnoreCase(HtmlDocumentationWriter.ADDITIONAL_DETAILS_HTML);
+            }
+
+        }).length > 0;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/e2d3d1b7/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/DocumentationWriter.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/DocumentationWriter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/DocumentationWriter.java
index d178636..391873d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/DocumentationWriter.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/DocumentationWriter.java
@@ -1,33 +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;
-}
+/*
+ * 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/e2d3d1b7/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/html/HtmlDocumentationWriter.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/html/HtmlDocumentationWriter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/html/HtmlDocumentationWriter.java
index 7586484..ae21e70 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/html/HtmlDocumentationWriter.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/html/HtmlDocumentationWriter.java
@@ -1,614 +1,614 @@
-/*
- * 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 org.apache.nifi.nar.ExtensionManager;
-
-/**
- * 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 = ExtensionManager.getExtensions(ControllerService.class);
-
-        // 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;
-    }
-}
+/*
+ * 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 org.apache.nifi.nar.ExtensionManager;
+
+/**
+ * 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 = ExtensionManager.getExtensions(ControllerService.class);
+
+        // 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;
+    }
+}