You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by ma...@apache.org on 2015/04/10 15:43:47 UTC

[12/62] [abbrv] incubator-nifi git commit: Squashed commit of the following:

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/e9647717/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ReportingTaskResource.java
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ReportingTaskResource.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ReportingTaskResource.java
new file mode 100644
index 0000000..38ddc36
--- /dev/null
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ReportingTaskResource.java
@@ -0,0 +1,663 @@
+/*
+ * 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.web.api;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import org.apache.nifi.cluster.manager.impl.WebClusterManager;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.ConfigurationSnapshot;
+import org.apache.nifi.web.NiFiServiceFacade;
+import org.apache.nifi.web.Revision;
+import org.apache.nifi.web.api.dto.RevisionDTO;
+import org.apache.nifi.web.api.request.ClientIdParameter;
+import org.apache.nifi.web.api.request.LongParameter;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.ui.extension.UiExtension;
+import org.apache.nifi.ui.extension.UiExtensionMapping;
+import org.apache.nifi.web.UiExtensionType;
+import static org.apache.nifi.web.api.ApplicationResource.CLIENT_ID;
+import org.apache.nifi.web.api.dto.PropertyDescriptorDTO;
+import org.apache.nifi.web.api.dto.ReportingTaskDTO;
+import org.apache.nifi.web.api.entity.PropertyDescriptorEntity;
+import org.apache.nifi.web.api.entity.ReportingTaskEntity;
+import org.apache.nifi.web.api.entity.ReportingTasksEntity;
+import org.apache.nifi.web.util.Availability;
+import org.codehaus.enunciate.jaxrs.TypeHint;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.access.prepost.PreAuthorize;
+
+/**
+ * RESTful endpoint for managing a Reporting Task.
+ */
+public class ReportingTaskResource extends ApplicationResource {
+
+    private static final Logger logger = LoggerFactory.getLogger(ReportingTaskResource.class);
+
+    private NiFiServiceFacade serviceFacade;
+    private WebClusterManager clusterManager;
+    private NiFiProperties properties;
+    
+    @Context
+    private ServletContext servletContext;
+    
+    /**
+     * Populates the uri for the specified reporting task.
+     * 
+     * @param reportingTasks
+     * @return 
+     */
+    private Set<ReportingTaskDTO> populateRemainingReportingTasksContent(final String availability, final Set<ReportingTaskDTO> reportingTasks) {
+        for (ReportingTaskDTO reportingTask : reportingTasks) {
+            populateRemainingReportingTaskContent(availability, reportingTask);
+        }
+        return reportingTasks;
+    }
+    
+    /**
+     * Populates the uri for the specified reporting task.
+     */
+    private ReportingTaskDTO populateRemainingReportingTaskContent(final String availability, final ReportingTaskDTO reportingTask) {
+        // populate the reporting task href
+        reportingTask.setUri(generateResourceUri("controller", "reporting-tasks", availability, reportingTask.getId()));
+        reportingTask.setAvailability(availability);
+        
+        // see if this processor has any ui extensions
+        final UiExtensionMapping uiExtensionMapping = (UiExtensionMapping) servletContext.getAttribute("nifi-ui-extensions");
+        if (uiExtensionMapping.hasUiExtension(reportingTask.getType())) {
+            final List<UiExtension> uiExtensions = uiExtensionMapping.getUiExtension(reportingTask.getType());
+            for (final UiExtension uiExtension : uiExtensions) {
+                if (UiExtensionType.ReportingTaskConfiguration.equals(uiExtension.getExtensionType())) {
+                    reportingTask.setCustomUiUrl(uiExtension.getContextPath() + "/configure");
+                }
+            }
+        }
+        
+        return reportingTask;
+    }
+
+    /**
+     * Parses the availability and ensure that the specified availability makes sense for the
+     * given NiFi instance.
+     * 
+     * @param availability
+     * @return 
+     */
+    private Availability parseAvailability(final String availability) {
+        final Availability avail;
+        try {
+            avail = Availability.valueOf(availability.toUpperCase());
+        } catch (IllegalArgumentException iae) {
+            throw new IllegalArgumentException(String.format("Availability: Value must be one of [%s]", StringUtils.join(Availability.values(), ", ")));
+        }
+        
+        // ensure this nifi is an NCM is specifying NCM availability
+        if (!properties.isClusterManager() && Availability.NCM.equals(avail)) {
+            throw new IllegalArgumentException("Availability of NCM is only applicable when the NiFi instance is the cluster manager.");
+        }
+        
+        return avail;
+    }
+    
+    /**
+     * Retrieves all the of reporting tasks in this NiFi.
+     *
+     * @param clientId Optional client id. If the client id is not specified, a
+     * new one will be generated. This value (whether specified or generated) is
+     * included in the response.
+     * @param availability Whether the reporting task is available on the NCM only (ncm) or on the 
+     * nodes only (node). If this instance is not clustered all tasks should use the node availability.
+     * @return A reportingTasksEntity.
+     */
+    @GET
+    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Path("/{availability}")
+    @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
+    @TypeHint(ReportingTasksEntity.class)
+    public Response getReportingTasks(@QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId, @PathParam("availability") String availability) {
+        final Availability avail = parseAvailability(availability);
+        
+        // replicate if cluster manager
+        if (properties.isClusterManager() && Availability.NODE.equals(avail)) {
+            return clusterManager.applyRequest(HttpMethod.GET, getAbsolutePath(), getRequestParameters(true), getHeaders()).getResponse();
+        }
+
+        // get all the reporting tasks
+        final Set<ReportingTaskDTO> reportingTasks = populateRemainingReportingTasksContent(availability, serviceFacade.getReportingTasks());
+        
+        // create the revision
+        final RevisionDTO revision = new RevisionDTO();
+        revision.setClientId(clientId.getClientId());
+
+        // create the response entity
+        final ReportingTasksEntity entity = new ReportingTasksEntity();
+        entity.setRevision(revision);
+        entity.setReportingTasks(reportingTasks);
+
+        // generate the response
+        return clusterContext(generateOkResponse(entity)).build();
+    }
+
+    /**
+     * Creates a new reporting task.
+     *
+     * @param httpServletRequest
+     * @param version The revision is used to verify the client is working with
+     * the latest version of the flow.
+     * @param clientId Optional client id. If the client id is not specified, a
+     * new one will be generated. This value (whether specified or generated) is
+     * included in the response.
+     * @param availability Whether the reporting task is available on the NCM only (ncm) or on the 
+     * nodes only (node). If this instance is not clustered all tasks should use the node availability.
+     * @param type The type of reporting task to create.
+     * @return A reportingTaskEntity.
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Path("/{availability}")
+    @PreAuthorize("hasRole('ROLE_DFM')")
+    @TypeHint(ReportingTaskEntity.class)
+    public Response createReportingTask(
+            @Context HttpServletRequest httpServletRequest,
+            @FormParam(VERSION) LongParameter version,
+            @FormParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId,
+            @PathParam("availability") String availability, 
+            @FormParam("type") String type) {
+        
+        // create the reporting task DTO
+        final ReportingTaskDTO reportingTaskDTO = new ReportingTaskDTO();
+        reportingTaskDTO.setType(type);
+
+        // create the revision
+        final RevisionDTO revision = new RevisionDTO();
+        revision.setClientId(clientId.getClientId());
+        if (version != null) {
+            revision.setVersion(version.getLong());
+        }
+
+        // create the reporting task entity
+        final ReportingTaskEntity reportingTaskEntity = new ReportingTaskEntity();
+        reportingTaskEntity.setRevision(revision);
+        reportingTaskEntity.setReportingTask(reportingTaskDTO);
+
+        return createReportingTask(httpServletRequest, availability, reportingTaskEntity);
+    }
+
+    /**
+     * Creates a new Reporting Task.
+     *
+     * @param httpServletRequest
+     * @param availability Whether the reporting task is available on the NCM only (ncm) or on the 
+     * nodes only (node). If this instance is not clustered all tasks should use the node availability.
+     * @param reportingTaskEntity A reportingTaskEntity.
+     * @return A reportingTaskEntity.
+     */
+    @POST
+    @Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Path("/{availability}")
+    @PreAuthorize("hasRole('ROLE_DFM')")
+    @TypeHint(ReportingTaskEntity.class)
+    public Response createReportingTask(
+            @Context HttpServletRequest httpServletRequest,
+            @PathParam("availability") String availability, 
+            ReportingTaskEntity reportingTaskEntity) {
+        
+        final Availability avail = parseAvailability(availability);
+
+        if (reportingTaskEntity == null || reportingTaskEntity.getReportingTask()== null) {
+            throw new IllegalArgumentException("Reporting task details must be specified.");
+        }
+
+        if (reportingTaskEntity.getRevision() == null) {
+            throw new IllegalArgumentException("Revision must be specified.");
+        }
+        
+        if (reportingTaskEntity.getReportingTask().getId() != null) {
+            throw new IllegalArgumentException("Reporting task ID cannot be specified.");
+        }
+        
+        if (StringUtils.isBlank(reportingTaskEntity.getReportingTask().getType())) {
+            throw new IllegalArgumentException("The type of reporting task to create must be specified.");
+        }
+        
+        // get the revision
+        final RevisionDTO revision = reportingTaskEntity.getRevision();
+
+        // if cluster manager, convert POST to PUT (to maintain same ID across nodes) and replicate
+        if (properties.isClusterManager() && Availability.NODE.equals(avail)) {
+            // create ID for resource
+            final String id = UUID.randomUUID().toString();
+
+            // set ID for resource
+            reportingTaskEntity.getReportingTask().setId(id);
+
+            // convert POST request to PUT request to force entity ID to be the same across nodes
+            URI putUri = null;
+            try {
+                putUri = new URI(getAbsolutePath().toString() + "/" + id);
+            } catch (final URISyntaxException e) {
+                throw new WebApplicationException(e);
+            }
+
+            // change content type to JSON for serializing entity
+            final Map<String, String> headersToOverride = new HashMap<>();
+            headersToOverride.put("content-type", MediaType.APPLICATION_JSON);
+
+            // replicate put request
+            return (Response) clusterManager.applyRequest(HttpMethod.PUT, putUri, updateClientId(reportingTaskEntity), getHeaders(headersToOverride)).getResponse();
+        }
+
+        // handle expects request (usually from the cluster manager)
+        final String expects = httpServletRequest.getHeader(WebClusterManager.NCM_EXPECTS_HTTP_HEADER);
+        if (expects != null) {
+            return generateContinueResponse().build();
+        }
+
+        // create the reporting task and generate the json
+        final ConfigurationSnapshot<ReportingTaskDTO> controllerResponse = serviceFacade.createReportingTask(
+                new Revision(revision.getVersion(), revision.getClientId()), reportingTaskEntity.getReportingTask());
+        final ReportingTaskDTO reportingTask = controllerResponse.getConfiguration();
+
+        // get the updated revision
+        final RevisionDTO updatedRevision = new RevisionDTO();
+        updatedRevision.setClientId(revision.getClientId());
+        updatedRevision.setVersion(controllerResponse.getVersion());
+
+        // build the response entity
+        final ReportingTaskEntity entity = new ReportingTaskEntity();
+        entity.setRevision(updatedRevision);
+        entity.setReportingTask(populateRemainingReportingTaskContent(availability, reportingTask));
+
+        // build the response
+        return clusterContext(generateCreatedResponse(URI.create(reportingTask.getUri()), entity)).build();
+    }
+
+    /**
+     * Retrieves the specified reporting task.
+     *
+     * @param clientId Optional client id. If the client id is not specified, a
+     * new one will be generated. This value (whether specified or generated) is
+     * included in the response.
+     * @param availability Whether the reporting task is available on the NCM only (ncm) or on the 
+     * nodes only (node). If this instance is not clustered all tasks should use the node availability.
+     * @param id The id of the reporting task to retrieve
+     * @return A reportingTaskEntity.
+     */
+    @GET
+    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Path("/{availability}/{id}")
+    @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
+    @TypeHint(ReportingTaskEntity.class)
+    public Response getReportingTask(@QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId, 
+            @PathParam("availability") String availability, @PathParam("id") String id) {
+
+        final Availability avail = parseAvailability(availability);
+        
+        // replicate if cluster manager
+        if (properties.isClusterManager() && Availability.NODE.equals(avail)) {
+            return clusterManager.applyRequest(HttpMethod.GET, getAbsolutePath(), getRequestParameters(true), getHeaders()).getResponse();
+        }
+
+        // get the reporting task
+        final ReportingTaskDTO reportingTask = serviceFacade.getReportingTask(id);
+
+        // create the revision
+        final RevisionDTO revision = new RevisionDTO();
+        revision.setClientId(clientId.getClientId());
+
+        // create the response entity
+        final ReportingTaskEntity entity = new ReportingTaskEntity();
+        entity.setRevision(revision);
+        entity.setReportingTask(populateRemainingReportingTaskContent(availability, reportingTask));
+
+        return clusterContext(generateOkResponse(entity)).build();
+    }
+    
+    /**
+     * Returns the descriptor for the specified property.
+     * 
+     * @param clientId Optional client id. If the client id is not specified, a
+     * new one will be generated. This value (whether specified or generated) is
+     * included in the response.
+     * @param availability
+     * @param id The id of the reporting task.
+     * @param propertyName The property
+     * @return a propertyDescriptorEntity
+     */
+    @GET
+    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Path("/{availability}/{id}/descriptors")
+    @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
+    @TypeHint(PropertyDescriptorEntity.class)
+    public Response getPropertyDescriptor(
+            @QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId, 
+            @PathParam("availability") String availability, @PathParam("id") String id, 
+            @QueryParam("propertyName") String propertyName) {
+        
+        final Availability avail = parseAvailability(availability);
+        
+        // ensure the property name is specified
+        if (propertyName == null) {
+            throw new IllegalArgumentException("The property name must be specified.");
+        }
+        
+        // replicate if cluster manager and task is on node
+        if (properties.isClusterManager() && Availability.NODE.equals(avail)) {
+            return clusterManager.applyRequest(HttpMethod.GET, getAbsolutePath(), getRequestParameters(true), getHeaders()).getResponse();
+        }
+        
+        // get the property descriptor
+        final PropertyDescriptorDTO descriptor = serviceFacade.getReportingTaskPropertyDescriptor(id, propertyName);
+        
+        // create the revision
+        final RevisionDTO revision = new RevisionDTO();
+        revision.setClientId(clientId.getClientId());
+        
+        // generate the response entity
+        final PropertyDescriptorEntity entity = new PropertyDescriptorEntity();
+        entity.setRevision(revision);
+        entity.setPropertyDescriptor(descriptor);
+        
+        // generate the response
+        return clusterContext(generateOkResponse(entity)).build();
+    }
+    
+    /**
+     * Updates the specified reporting task.
+     *
+     * @param httpServletRequest
+     * @param version The revision is used to verify the client is working with
+     * the latest version of the flow.
+     * @param clientId Optional client id. If the client id is not specified, a
+     * new one will be generated. This value (whether specified or generated) is
+     * included in the response.
+     * @param availability Whether the reporting task is available on the NCM only (ncm) or on the 
+     * nodes only (node). If this instance is not clustered all tasks should use the node availability.
+     * @param id The id of the reporting task to update.
+     * @param name The name of the reporting task
+     * @param annotationData The annotation data for the reporting task
+     * @param markedForDeletion Array of property names whose value should be removed.
+     * @param state The updated scheduled state
+     * @param schedulingStrategy The scheduling strategy for this reporting task
+     * @param schedulingPeriod The scheduling period for this reporting task
+     * @param comments The comments for this reporting task
+     * @param formParams Additionally, the processor properties and styles are
+     * specified in the form parameters. Because the property names and styles
+     * differ from processor to processor they are specified in a map-like
+     * fashion:
+     * <br>
+     * <ul>
+     * <li>properties[required.file.path]=/path/to/file</li>
+     * <li>properties[required.hostname]=localhost</li>
+     * <li>properties[required.port]=80</li>
+     * <li>properties[optional.file.path]=/path/to/file</li>
+     * <li>properties[optional.hostname]=localhost</li>
+     * <li>properties[optional.port]=80</li>
+     * <li>properties[user.defined.pattern]=^.*?s.*$</li>
+     * </ul>
+     * @return A reportingTaskEntity.
+     */
+    @PUT
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Path("/{availability}/{id}")
+    @PreAuthorize("hasRole('ROLE_DFM')")
+    @TypeHint(ReportingTaskEntity.class)
+    public Response updateReportingTask(
+            @Context HttpServletRequest httpServletRequest,
+            @FormParam(VERSION) LongParameter version,
+            @FormParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId,
+            @PathParam("availability") String availability, @PathParam("id") String id, @FormParam("name") String name,
+            @FormParam("annotationData") String annotationData, @FormParam("markedForDeletion[]") List<String> markedForDeletion,
+            @FormParam("state") String state, @FormParam("schedulingStrategy") String schedulingStrategy,
+            @FormParam("schedulingPeriod") String schedulingPeriod, @FormParam("comments") String comments,
+            MultivaluedMap<String, String> formParams) {
+
+        // create collections for holding the reporting task properties
+        final Map<String, String> updatedProperties = new LinkedHashMap<>();
+        
+        // go through each parameter and look for processor properties
+        for (String parameterName : formParams.keySet()) {
+            if (StringUtils.isNotBlank(parameterName)) {
+                // see if the parameter name starts with an expected parameter type...
+                // if so, store the parameter name and value in the corresponding collection
+                if (parameterName.startsWith("properties")) {
+                    final int startIndex = StringUtils.indexOf(parameterName, "[");
+                    final int endIndex = StringUtils.lastIndexOf(parameterName, "]");
+                    if (startIndex != -1 && endIndex != -1) {
+                        final String propertyName = StringUtils.substring(parameterName, startIndex + 1, endIndex);
+                        updatedProperties.put(propertyName, formParams.getFirst(parameterName));
+                    }
+                }
+            }
+        }
+        
+        // set the properties to remove
+        for (String propertyToDelete : markedForDeletion) {
+            updatedProperties.put(propertyToDelete, null);
+        }
+        
+        // create the reporting task DTO
+        final ReportingTaskDTO reportingTaskDTO = new ReportingTaskDTO();
+        reportingTaskDTO.setId(id);
+        reportingTaskDTO.setName(name);
+        reportingTaskDTO.setState(state);
+        reportingTaskDTO.setSchedulingStrategy(schedulingStrategy);
+        reportingTaskDTO.setSchedulingPeriod(schedulingPeriod);
+        reportingTaskDTO.setAnnotationData(annotationData);
+        reportingTaskDTO.setComments(comments);
+
+        // only set the properties when appropriate
+        if (!updatedProperties.isEmpty()) {
+            reportingTaskDTO.setProperties(updatedProperties);
+        }
+        
+        // create the revision
+        final RevisionDTO revision = new RevisionDTO();
+        revision.setClientId(clientId.getClientId());
+        if (version != null) {
+            revision.setVersion(version.getLong());
+        }
+
+        // create the reporting task entity
+        final ReportingTaskEntity reportingTaskEntity = new ReportingTaskEntity();
+        reportingTaskEntity.setRevision(revision);
+        reportingTaskEntity.setReportingTask(reportingTaskDTO);
+
+        // update the reporting task
+        return updateReportingTask(httpServletRequest, availability, id, reportingTaskEntity);
+    }
+
+    /**
+     * Updates the specified a Reporting Task.
+     *
+     * @param httpServletRequest
+     * @param availability Whether the reporting task is available on the NCM only (ncm) or on the 
+     * nodes only (node). If this instance is not clustered all tasks should use the node availability.
+     * @param id The id of the reporting task to update.
+     * @param reportingTaskEntity A reportingTaskEntity.
+     * @return A reportingTaskEntity.
+     */
+    @PUT
+    @Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Path("/{availability}/{id}")
+    @PreAuthorize("hasRole('ROLE_DFM')")
+    @TypeHint(ReportingTaskEntity.class)
+    public Response updateReportingTask(
+            @Context HttpServletRequest httpServletRequest,
+            @PathParam("availability") String availability, 
+            @PathParam("id") String id,
+            ReportingTaskEntity reportingTaskEntity) {
+
+        final Availability avail = parseAvailability(availability);
+        
+        if (reportingTaskEntity == null || reportingTaskEntity.getReportingTask() == null) {
+            throw new IllegalArgumentException("Reporting task details must be specified.");
+        }
+
+        if (reportingTaskEntity.getRevision() == null) {
+            throw new IllegalArgumentException("Revision must be specified.");
+        }
+
+        // ensure the ids are the same
+        final ReportingTaskDTO requestReportingTaskDTO = reportingTaskEntity.getReportingTask();
+        if (!id.equals(requestReportingTaskDTO.getId())) {
+            throw new IllegalArgumentException(String.format("The reporting task id (%s) in the request body does not equal the "
+                    + "reporting task id of the requested resource (%s).", requestReportingTaskDTO.getId(), id));
+        }
+
+        // replicate if cluster manager
+        if (properties.isClusterManager() && Availability.NODE.equals(avail)) {
+            // change content type to JSON for serializing entity
+            final Map<String, String> headersToOverride = new HashMap<>();
+            headersToOverride.put("content-type", MediaType.APPLICATION_JSON);
+
+            // replicate the request
+            return clusterManager.applyRequest(HttpMethod.PUT, getAbsolutePath(), updateClientId(reportingTaskEntity), getHeaders(headersToOverride)).getResponse();
+        }
+        
+        // handle expects request (usually from the cluster manager)
+        final String expects = httpServletRequest.getHeader(WebClusterManager.NCM_EXPECTS_HTTP_HEADER);
+        if (expects != null) {
+            serviceFacade.verifyUpdateReportingTask(requestReportingTaskDTO);
+            return generateContinueResponse().build();
+        }
+
+        // update the reporting task
+        final RevisionDTO revision = reportingTaskEntity.getRevision();
+        final ConfigurationSnapshot<ReportingTaskDTO> controllerResponse = serviceFacade.updateReportingTask(
+                new Revision(revision.getVersion(), revision.getClientId()), requestReportingTaskDTO);
+
+        // get the results
+        final ReportingTaskDTO responseReportingTaskDTO = controllerResponse.getConfiguration();
+
+        // get the updated revision
+        final RevisionDTO updatedRevision = new RevisionDTO();
+        updatedRevision.setClientId(revision.getClientId());
+        updatedRevision.setVersion(controllerResponse.getVersion());
+
+        // build the response entity
+        final ReportingTaskEntity entity = new ReportingTaskEntity();
+        entity.setRevision(updatedRevision);
+        entity.setReportingTask(populateRemainingReportingTaskContent(availability, responseReportingTaskDTO));
+
+        return clusterContext(generateOkResponse(entity)).build();
+    }
+
+    /**
+     * Removes the specified reporting task.
+     *
+     * @param httpServletRequest
+     * @param version The revision is used to verify the client is working with
+     * the latest version of the flow.
+     * @param clientId Optional client id. If the client id is not specified, a
+     * new one will be generated. This value (whether specified or generated) is
+     * included in the response.
+     * @param availability Whether the reporting task is available on the NCM only (ncm) or on the 
+     * nodes only (node). If this instance is not clustered all tasks should use the node availability.
+     * @param id The id of the reporting task to remove.
+     * @return A entity containing the client id and an updated revision.
+     */
+    @DELETE
+    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Path("/{availability}/{id}")
+    @PreAuthorize("hasRole('ROLE_DFM')")
+    @TypeHint(ReportingTaskEntity.class)
+    public Response removeReportingTask(
+            @Context HttpServletRequest httpServletRequest,
+            @QueryParam(VERSION) LongParameter version,
+            @QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId,
+            @PathParam("availability") String availability, @PathParam("id") String id) {
+
+        final Availability avail = parseAvailability(availability);
+        
+        // replicate if cluster manager
+        if (properties.isClusterManager() && Availability.NODE.equals(avail)) {
+            return clusterManager.applyRequest(HttpMethod.DELETE, getAbsolutePath(), getRequestParameters(true), getHeaders()).getResponse();
+        }
+
+        // handle expects request (usually from the cluster manager)
+        final String expects = httpServletRequest.getHeader(WebClusterManager.NCM_EXPECTS_HTTP_HEADER);
+        if (expects != null) {
+            serviceFacade.verifyDeleteReportingTask(id);
+            return generateContinueResponse().build();
+        }
+
+        // determine the specified version
+        Long clientVersion = null;
+        if (version != null) {
+            clientVersion = version.getLong();
+        }
+
+        // delete the specified reporting task
+        final ConfigurationSnapshot<Void> controllerResponse = serviceFacade.deleteReportingTask(new Revision(clientVersion, clientId.getClientId()), id);
+
+        // get the updated revision
+        final RevisionDTO revision = new RevisionDTO();
+        revision.setClientId(clientId.getClientId());
+        revision.setVersion(controllerResponse.getVersion());
+
+        // build the response entity
+        final ReportingTaskEntity entity = new ReportingTaskEntity();
+        entity.setRevision(revision);
+
+        return clusterContext(generateOkResponse(entity)).build();
+    }
+
+    // setters
+    
+    public void setServiceFacade(NiFiServiceFacade serviceFacade) {
+        this.serviceFacade = serviceFacade;
+    }
+
+    public void setClusterManager(WebClusterManager clusterManager) {
+        this.clusterManager = clusterManager;
+    }
+
+    public void setProperties(NiFiProperties properties) {
+        this.properties = properties;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/e9647717/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SnippetResource.java
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SnippetResource.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SnippetResource.java
index e0b7788..275b133 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SnippetResource.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SnippetResource.java
@@ -340,7 +340,7 @@ public class SnippetResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO updatedRevision = new RevisionDTO();
         updatedRevision.setClientId(revision.getClientId());
-        updatedRevision.setVersion(response.getRevision());
+        updatedRevision.setVersion(response.getVersion());
 
         // build the response entity
         SnippetEntity entity = new SnippetEntity();
@@ -520,7 +520,7 @@ public class SnippetResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO updatedRevision = new RevisionDTO();
         updatedRevision.setClientId(revision.getClientId());
-        updatedRevision.setVersion(controllerResponse.getRevision());
+        updatedRevision.setVersion(controllerResponse.getVersion());
 
         // build the response entity
         SnippetEntity entity = new SnippetEntity();
@@ -577,7 +577,7 @@ public class SnippetResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO revision = new RevisionDTO();
         revision.setClientId(clientId.getClientId());
-        revision.setVersion(controllerResponse.getRevision());
+        revision.setVersion(controllerResponse.getVersion());
 
         // build the response entity
         SnippetEntity entity = new SnippetEntity();

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/e9647717/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/ThrowableMapper.java
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/ThrowableMapper.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/ThrowableMapper.java
index 091653a..0ef6edb 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/ThrowableMapper.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/ThrowableMapper.java
@@ -34,11 +34,8 @@ public class ThrowableMapper implements ExceptionMapper<Throwable> {
     @Override
     public Response toResponse(Throwable exception) {
         // log the error
-        logger.info(String.format("An unexpected error has occurred: %s. Returning %s response.", exception, Response.Status.INTERNAL_SERVER_ERROR));
-
-        if (logger.isDebugEnabled()) {
-            logger.debug(StringUtils.EMPTY, exception);
-        }
+        logger.error(String.format("An unexpected error has occurred: %s. Returning %s response.", exception, Response.Status.INTERNAL_SERVER_ERROR));
+        logger.error(StringUtils.EMPTY, exception);
 
         return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("An unexpected error has occurred. Please check the logs for additional details.").type("text/plain").build();
     }

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/e9647717/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
index 92a5449..7fe76ad 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
@@ -39,7 +39,7 @@ import javax.ws.rs.WebApplicationException;
 
 import org.apache.nifi.action.Action;
 import org.apache.nifi.action.component.details.ComponentDetails;
-import org.apache.nifi.action.component.details.ProcessorDetails;
+import org.apache.nifi.action.component.details.ExtensionDetails;
 import org.apache.nifi.action.component.details.RemoteProcessGroupDetails;
 import org.apache.nifi.action.details.ActionDetails;
 import org.apache.nifi.action.details.ConfigureDetails;
@@ -97,14 +97,12 @@ import org.apache.nifi.scheduling.SchedulingStrategy;
 import org.apache.nifi.user.NiFiUser;
 import org.apache.nifi.user.NiFiUserGroup;
 import org.apache.nifi.util.FormatUtils;
-import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.web.Revision;
-import org.apache.nifi.web.api.dto.ProcessorConfigDTO.AllowableValueDTO;
-import org.apache.nifi.web.api.dto.ProcessorConfigDTO.PropertyDescriptorDTO;
+import org.apache.nifi.web.api.dto.PropertyDescriptorDTO.AllowableValueDTO;
 import org.apache.nifi.web.api.dto.action.ActionDTO;
 import org.apache.nifi.web.api.dto.action.HistoryDTO;
 import org.apache.nifi.web.api.dto.action.component.details.ComponentDetailsDTO;
-import org.apache.nifi.web.api.dto.action.component.details.ProcessorDetailsDTO;
+import org.apache.nifi.web.api.dto.action.component.details.ExtensionDetailsDTO;
 import org.apache.nifi.web.api.dto.action.component.details.RemoteProcessGroupDetailsDTO;
 import org.apache.nifi.web.api.dto.action.details.ActionDetailsDTO;
 import org.apache.nifi.web.api.dto.action.details.ConfigureDetailsDTO;
@@ -124,6 +122,12 @@ import org.apache.nifi.web.api.dto.status.ProcessorStatusDTO;
 import org.apache.nifi.web.api.dto.status.RemoteProcessGroupStatusDTO;
 import org.apache.nifi.web.api.dto.status.StatusDTO;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.controller.ConfiguredComponent;
+import org.apache.nifi.controller.ReportingTaskNode;
+import org.apache.nifi.controller.service.ControllerServiceNode;
+import org.apache.nifi.controller.service.ControllerServiceReference;
+import org.apache.nifi.reporting.ReportingTask;
+import org.apache.nifi.web.FlowModification;
 
 /**
  *
@@ -218,9 +222,9 @@ public final class DtoFactory {
             return null;
         }
 
-        if (componentDetails instanceof ProcessorDetails) {
-            final ProcessorDetailsDTO processorDetails = new ProcessorDetailsDTO();
-            processorDetails.setType(((ProcessorDetails) componentDetails).getType());
+        if (componentDetails instanceof ExtensionDetails) {
+            final ExtensionDetailsDTO processorDetails = new ExtensionDetailsDTO();
+            processorDetails.setType(((ExtensionDetails) componentDetails).getType());
             return processorDetails;
         } else if (componentDetails instanceof RemoteProcessGroupDetails) {
             final RemoteProcessGroupDetailsDTO remoteProcessGroupDetails = new RemoteProcessGroupDetailsDTO();
@@ -834,6 +838,241 @@ public final class DtoFactory {
         return dto;
     }
 
+    public ReportingTaskDTO createReportingTaskDto(final ReportingTaskNode reportingTaskNode) {
+        final ReportingTaskDTO dto = new ReportingTaskDTO();
+        dto.setId(reportingTaskNode.getIdentifier());
+        dto.setName(reportingTaskNode.getName());
+        dto.setType(reportingTaskNode.getReportingTask().getClass().getName());
+        dto.setSchedulingStrategy(reportingTaskNode.getSchedulingStrategy().name());
+        dto.setSchedulingPeriod(reportingTaskNode.getSchedulingPeriod());
+        dto.setState(reportingTaskNode.getScheduledState().name());
+        dto.setActiveThreadCount(reportingTaskNode.getActiveThreadCount());
+        dto.setAnnotationData(reportingTaskNode.getAnnotationData());
+        dto.setComments(reportingTaskNode.getComments());
+
+        final Map<String, String> defaultSchedulingPeriod = new HashMap<>();
+        defaultSchedulingPeriod.put(SchedulingStrategy.TIMER_DRIVEN.name(), SchedulingStrategy.TIMER_DRIVEN.getDefaultSchedulingPeriod());
+        defaultSchedulingPeriod.put(SchedulingStrategy.CRON_DRIVEN.name(), SchedulingStrategy.CRON_DRIVEN.getDefaultSchedulingPeriod());
+        dto.setDefaultSchedulingPeriod(defaultSchedulingPeriod);
+        
+        // sort a copy of the properties
+        final Map<PropertyDescriptor, String> sortedProperties = new TreeMap<>(new Comparator<PropertyDescriptor>() {
+            @Override
+            public int compare(PropertyDescriptor o1, PropertyDescriptor o2) {
+                return Collator.getInstance(Locale.US).compare(o1.getName(), o2.getName());
+            }
+        });
+        sortedProperties.putAll(reportingTaskNode.getProperties());
+
+        // get the property order from the reporting task
+        final ReportingTask reportingTask = reportingTaskNode.getReportingTask();
+        final Map<PropertyDescriptor, String> orderedProperties = new LinkedHashMap<>();
+        final List<PropertyDescriptor> descriptors = reportingTask.getPropertyDescriptors();
+        if (descriptors != null && !descriptors.isEmpty()) {
+            for (PropertyDescriptor descriptor : descriptors) {
+                orderedProperties.put(descriptor, null);
+            }
+        }
+        orderedProperties.putAll(sortedProperties);
+        
+        // build the descriptor and property dtos
+        dto.setDescriptors(new LinkedHashMap<String, PropertyDescriptorDTO>());
+        dto.setProperties(new LinkedHashMap<String, String>());
+        for (final Map.Entry<PropertyDescriptor, String> entry : orderedProperties.entrySet()) {
+            final PropertyDescriptor descriptor = entry.getKey();
+
+            // store the property descriptor
+            dto.getDescriptors().put(descriptor.getName(), createPropertyDescriptorDto(descriptor));
+
+            // determine the property value - don't include sensitive properties
+            String propertyValue = entry.getValue();
+            if (propertyValue != null && descriptor.isSensitive()) {
+                propertyValue = "********";
+            }
+
+            // set the property value
+            dto.getProperties().put(descriptor.getName(), propertyValue);
+        }
+        
+        // add the validation errors
+        final Collection<ValidationResult> validationErrors = reportingTaskNode.getValidationErrors();
+        if (validationErrors != null && !validationErrors.isEmpty()) {
+            final List<String> errors = new ArrayList<>();
+            for (final ValidationResult validationResult : validationErrors) {
+                errors.add(validationResult.toString());
+            }
+
+            dto.setValidationErrors(errors);
+        }
+        
+        return dto;
+    }
+    
+    public ControllerServiceDTO createControllerServiceDto(final ControllerServiceNode controllerServiceNode) {
+        final ControllerServiceDTO dto = new ControllerServiceDTO();
+        dto.setId(controllerServiceNode.getIdentifier());
+        dto.setName(controllerServiceNode.getName());
+        dto.setType(controllerServiceNode.getControllerServiceImplementation().getClass().getName());
+        dto.setState(controllerServiceNode.getState().name());
+        dto.setAnnotationData(controllerServiceNode.getAnnotationData());
+        dto.setComments(controllerServiceNode.getComments());
+        
+        // sort a copy of the properties
+        final Map<PropertyDescriptor, String> sortedProperties = new TreeMap<>(new Comparator<PropertyDescriptor>() {
+            @Override
+            public int compare(PropertyDescriptor o1, PropertyDescriptor o2) {
+                return Collator.getInstance(Locale.US).compare(o1.getName(), o2.getName());
+            }
+        });
+        sortedProperties.putAll(controllerServiceNode.getProperties());
+
+        // get the property order from the controller service
+        final ControllerService controllerService = controllerServiceNode.getControllerServiceImplementation();
+        final Map<PropertyDescriptor, String> orderedProperties = new LinkedHashMap<>();
+        final List<PropertyDescriptor> descriptors = controllerService.getPropertyDescriptors();
+        if (descriptors != null && !descriptors.isEmpty()) {
+            for (PropertyDescriptor descriptor : descriptors) {
+                orderedProperties.put(descriptor, null);
+            }
+        }
+        orderedProperties.putAll(sortedProperties);
+        
+        // build the descriptor and property dtos
+        dto.setDescriptors(new LinkedHashMap<String, PropertyDescriptorDTO>());
+        dto.setProperties(new LinkedHashMap<String, String>());
+        for (final Map.Entry<PropertyDescriptor, String> entry : orderedProperties.entrySet()) {
+            final PropertyDescriptor descriptor = entry.getKey();
+
+            // store the property descriptor
+            dto.getDescriptors().put(descriptor.getName(), createPropertyDescriptorDto(descriptor));
+
+            // determine the property value - don't include sensitive properties
+            String propertyValue = entry.getValue();
+            if (propertyValue != null && descriptor.isSensitive()) {
+                propertyValue = "********";
+            }
+
+            // set the property value
+            dto.getProperties().put(descriptor.getName(), propertyValue);
+        }
+        
+        // create the reference dto's
+        dto.setReferencingComponents(createControllerServiceReferencingComponentsDto(controllerServiceNode.getReferences()));
+
+        // add the validation errors
+        final Collection<ValidationResult> validationErrors = controllerServiceNode.getValidationErrors();
+        if (validationErrors != null && !validationErrors.isEmpty()) {
+            final List<String> errors = new ArrayList<>();
+            for (final ValidationResult validationResult : validationErrors) {
+                errors.add(validationResult.toString());
+            }
+
+            dto.setValidationErrors(errors);
+        }
+        
+        return dto;
+    }
+    
+    public Set<ControllerServiceReferencingComponentDTO> createControllerServiceReferencingComponentsDto(final ControllerServiceReference reference) {
+        return createControllerServiceReferencingComponentsDto(reference, new HashSet<ControllerServiceNode>());
+    }
+    
+    private Set<ControllerServiceReferencingComponentDTO> createControllerServiceReferencingComponentsDto(final ControllerServiceReference reference, final Set<ControllerServiceNode> visited) {
+        final Set<ControllerServiceReferencingComponentDTO> referencingComponents = new LinkedHashSet<>();
+        
+        // get all references
+        for (final ConfiguredComponent component : reference.getReferencingComponents()) {
+            final ControllerServiceReferencingComponentDTO dto = new ControllerServiceReferencingComponentDTO();
+            dto.setId(component.getIdentifier());
+            dto.setName(component.getName());
+            
+            List<PropertyDescriptor> propertyDescriptors = null;
+            Collection<ValidationResult> validationErrors = null;
+            if (component instanceof ProcessorNode) {
+                final ProcessorNode node = ((ProcessorNode) component);
+                dto.setGroupId(node.getProcessGroup().getIdentifier());
+                dto.setState(node.getScheduledState().name());
+                dto.setActiveThreadCount(node.getActiveThreadCount());
+                dto.setType(node.getProcessor().getClass().getName());
+                dto.setReferenceType(Processor.class.getSimpleName());
+                
+                propertyDescriptors = node.getProcessor().getPropertyDescriptors();
+                validationErrors = node.getValidationErrors();
+            } else if (component instanceof ControllerServiceNode) {
+                final ControllerServiceNode node = ((ControllerServiceNode) component);
+                dto.setState(node.getState().name());
+                dto.setType(node.getControllerServiceImplementation().getClass().getName());
+                dto.setReferenceType(ControllerService.class.getSimpleName());
+                dto.setReferenceCycle(visited.contains(node));
+                
+                // if we haven't encountered this service before include it's referencing components
+                if (!dto.getReferenceCycle()) {
+                    dto.setReferencingComponents(createControllerServiceReferencingComponentsDto(node.getReferences(), visited));
+                }
+                
+                propertyDescriptors = node.getControllerServiceImplementation().getPropertyDescriptors();
+                validationErrors = node.getValidationErrors();
+            } else if (component instanceof ReportingTaskNode) {
+                final ReportingTaskNode node = ((ReportingTaskNode) component);
+                dto.setState(node.getScheduledState().name());
+                dto.setActiveThreadCount(node.getActiveThreadCount());
+                dto.setType(node.getReportingTask().getClass().getName());
+                dto.setReferenceType(ReportingTask.class.getSimpleName());
+                
+                propertyDescriptors = node.getReportingTask().getPropertyDescriptors();
+                validationErrors = node.getValidationErrors();
+            }
+            
+            if (propertyDescriptors != null && !propertyDescriptors.isEmpty()) {
+                final Map<PropertyDescriptor, String> sortedProperties = new TreeMap<>(new Comparator<PropertyDescriptor>() {
+                    @Override
+                    public int compare(PropertyDescriptor o1, PropertyDescriptor o2) {
+                        return Collator.getInstance(Locale.US).compare(o1.getName(), o2.getName());
+                    }
+                });
+                sortedProperties.putAll(component.getProperties());
+
+                final Map<PropertyDescriptor, String> orderedProperties = new LinkedHashMap<>();
+                for (PropertyDescriptor descriptor : propertyDescriptors) {
+                    orderedProperties.put(descriptor, null);
+                }
+                orderedProperties.putAll(sortedProperties);
+
+                // build the descriptor and property dtos
+                dto.setDescriptors(new LinkedHashMap<String, PropertyDescriptorDTO>());
+                dto.setProperties(new LinkedHashMap<String, String>());
+                for (final Map.Entry<PropertyDescriptor, String> entry : orderedProperties.entrySet()) {
+                    final PropertyDescriptor descriptor = entry.getKey();
+
+                    // store the property descriptor
+                    dto.getDescriptors().put(descriptor.getName(), createPropertyDescriptorDto(descriptor));
+
+                    // determine the property value - don't include sensitive properties
+                    String propertyValue = entry.getValue();
+                    if (propertyValue != null && descriptor.isSensitive()) {
+                        propertyValue = "********";
+                    }
+
+                    // set the property value
+                    dto.getProperties().put(descriptor.getName(), propertyValue);
+                }
+            }
+            
+            if (validationErrors != null && !validationErrors.isEmpty()) {
+                final List<String> errors = new ArrayList<>();
+                for (final ValidationResult validationResult : validationErrors) {
+                    errors.add(validationResult.toString());
+                }
+
+                dto.setValidationErrors(errors);
+            }
+            
+            referencingComponents.add(dto);
+        }
+        
+        return referencingComponents;
+    }
+    
     public RemoteProcessGroupPortDTO createRemoteProcessGroupPortDto(final RemoteGroupPort port) {
         if (port == null) {
             return null;
@@ -1143,6 +1382,135 @@ public final class DtoFactory {
 
         return types;
     }
+    
+    /**
+     * Identifies all baseTypes for the specified type that are assignable to the specified baseType.
+     * 
+     * @param baseType
+     * @param type
+     * @param baseTypes 
+     */
+    private void identifyBaseTypes(final Class baseType, final Class type, final Set<Class> baseTypes, final  boolean recurse) {
+        final Class[] interfaces = type.getInterfaces();
+        for (final Class i : interfaces) {
+            if (baseType.isAssignableFrom(i) && !baseType.equals(i)) {
+                baseTypes.add(i);
+            }
+        }
+        
+        if (recurse) {
+            if (type.getSuperclass() != null) {
+                identifyBaseTypes(baseType, type.getSuperclass(), baseTypes, recurse);
+            }
+        }
+    }
+    
+    /**
+     * Gets the DocumentedTypeDTOs from the specified classes for the specified baseClass.
+     *
+     * @param baseClass
+     * @param classes
+     * @return
+     */
+    public Set<DocumentedTypeDTO> fromDocumentedTypes(final Class baseClass, final Set<Class> classes) {
+        final Set<DocumentedTypeDTO> types = new LinkedHashSet<>();
+        final Set<Class> sortedClasses = new TreeSet<>(CLASS_NAME_COMPARATOR);
+        sortedClasses.addAll(classes);
+        
+        // identify all interfaces that extend baseClass for all classes
+        final Set<Class> interfaces = new HashSet<>();
+        for (final Class<?> cls : sortedClasses) {
+            identifyBaseTypes(baseClass, cls, interfaces, true);
+        }
+        
+        // build a lookup of all interfaces
+        final Map<Class, DocumentedTypeDTO> lookup = new HashMap<>();
+        
+        // convert the interfaces to DTO form
+        for (final Class<?> i : interfaces) {
+            final DocumentedTypeDTO type = new DocumentedTypeDTO();
+            type.setType(i.getName());
+            type.setDescription(getCapabilityDescription(i));
+            type.setTags(getTags(i));
+            type.setChildTypes(new LinkedHashSet<DocumentedTypeDTO>());
+            lookup.put(i, type);
+        }
+        
+        // move the interfaces into the appropriate hierarchy
+        final Collection<Class> rootTypes = new ArrayList<>();
+        for (final Class<?> i : interfaces) {
+            rootTypes.add(i);
+            
+            // identify the base types
+            final Set<Class> baseTypes = new LinkedHashSet<>();
+            identifyBaseTypes(baseClass, i, baseTypes, false);
+            
+            // move this interfaces into the hierarchy where appropriate
+            if (!baseTypes.isEmpty()) {
+                // get the DTO for each base type
+                for (final Class baseType : baseTypes) {
+                    final DocumentedTypeDTO parentInteface = lookup.get(baseType);
+                    final DocumentedTypeDTO childInterface = lookup.get(i);
+                    
+                    // include all parent tags in the respective children
+                    childInterface.getTags().addAll(parentInteface.getTags());
+                    
+                    // update the hierarchy
+                    parentInteface.getChildTypes().add(childInterface);
+                }
+                
+                // remove this interface from the lookup (this will only
+                // leave the interfaces that are ancestor roots)
+                rootTypes.remove(i);
+            }
+        }
+
+        // include the interfaces
+        sortedClasses.addAll(rootTypes);
+        
+        // get the DTO form for all interfaces and classes
+        for (final Class<?> cls : sortedClasses) {
+            boolean add = false;
+            
+            final DocumentedTypeDTO type;
+            if (rootTypes.contains(cls)) {
+                type = lookup.get(cls);
+                add = true;
+            } else {
+                type = new DocumentedTypeDTO();
+                type.setType(cls.getName());
+                type.setDescription(getCapabilityDescription(cls));
+                type.setTags(getTags(cls));
+            }
+            
+            // identify the base types
+            final Set<Class> baseTypes = new LinkedHashSet<>();
+            identifyBaseTypes(baseClass, cls, baseTypes, false);
+            
+            // include this type if it doesn't belong to another hierarchy
+            if (baseTypes.isEmpty()) {
+                add = true;
+            } else {
+                // get the DTO for each base type
+                for (final Class baseType : baseTypes) {
+                    final DocumentedTypeDTO parentInterface = lookup.get(baseType);
+
+                    // include all parent tags in the respective children
+                    type.getTags().addAll(parentInterface.getTags());
+
+                    // update the hierarchy
+                    parentInterface.getChildTypes().add(type);
+                }
+            }
+            
+            // add if appropriate
+            if (add) {
+                types.add(type);
+            }
+        }
+
+        return types;
+    }
 
     /**
      * Creates a ProcessorDTO from the specified ProcessorNode.
@@ -1575,12 +1943,12 @@ public final class DtoFactory {
      * @param propertyDescriptor
      * @return
      */
-    private ProcessorConfigDTO.PropertyDescriptorDTO createPropertyDescriptorDto(final PropertyDescriptor propertyDescriptor) {
+    public PropertyDescriptorDTO createPropertyDescriptorDto(final PropertyDescriptor propertyDescriptor) {
         if (propertyDescriptor == null) {
             return null;
         }
 
-        final ProcessorConfigDTO.PropertyDescriptorDTO dto = new ProcessorConfigDTO.PropertyDescriptorDTO();
+        final PropertyDescriptorDTO dto = new PropertyDescriptorDTO();
 
         dto.setName(propertyDescriptor.getName());
         dto.setDisplayName(propertyDescriptor.getDisplayName());
@@ -1590,18 +1958,16 @@ public final class DtoFactory {
         dto.setDescription(propertyDescriptor.getDescription());
         dto.setDefaultValue(propertyDescriptor.getDefaultValue());
         dto.setSupportsEl(propertyDescriptor.isExpressionLanguageSupported());
+        dto.setIdentifiesControllerService(propertyDescriptor.getControllerServiceDefinition() != null);
 
         final Class<? extends ControllerService> serviceDefinition = propertyDescriptor.getControllerServiceDefinition();
         if (propertyDescriptor.getAllowableValues() == null) {
             if (serviceDefinition == null) {
                 dto.setAllowableValues(null);
             } else {
-                final Set<AllowableValueDTO> allowableValues = new LinkedHashSet<>();
+                final List<AllowableValueDTO> allowableValues = new ArrayList<>();
                 for (final String serviceIdentifier : controllerServiceLookup.getControllerServiceIdentifiers(serviceDefinition)) {
-                    String displayName = serviceIdentifier;
-
-                    // TODO: attempt to get the controller service name
-                    final ControllerService controllerService = controllerServiceLookup.getControllerService(serviceIdentifier);
+                	final String displayName = controllerServiceLookup.getControllerServiceName(serviceIdentifier);
 
                     final AllowableValueDTO allowableValue = new AllowableValueDTO();
                     allowableValue.setDisplayName(displayName);
@@ -1611,7 +1977,7 @@ public final class DtoFactory {
                 dto.setAllowableValues(allowableValues);
             }
         } else {
-            final Set<AllowableValueDTO> allowableValues = new LinkedHashSet<>();
+            final List<AllowableValueDTO> allowableValues = new ArrayList<>();
             for (final AllowableValue allowableValue : propertyDescriptor.getAllowableValues()) {
                 final AllowableValueDTO allowableValueDto = new AllowableValueDTO();
                 allowableValueDto.setDisplayName(allowableValue.getDisplayName());
@@ -1642,6 +2008,25 @@ public final class DtoFactory {
         return copy;
     }
 
+    
+    public ControllerServiceDTO copy(final ControllerServiceDTO original) {
+        final ControllerServiceDTO copy = new ControllerServiceDTO();
+        copy.setAnnotationData(original.getAnnotationData());
+        copy.setAvailability(original.getAvailability());
+        copy.setComments(original.getComments());
+        copy.setCustomUiUrl(original.getCustomUiUrl());
+        copy.setDescriptors(copy(original.getDescriptors()));
+        copy.setId(original.getId());
+        copy.setName(original.getName());
+        copy.setProperties(copy(original.getProperties()));
+        copy.setReferencingComponents(copy(original.getReferencingComponents()));
+        copy.setState(original.getState());
+        copy.setType(original.getType());
+        copy.setUri(original.getUri());
+        copy.setValidationErrors(copy(original.getValidationErrors()));
+        return copy;
+    }
+    
     public FunnelDTO copy(final FunnelDTO original) {
         final FunnelDTO copy = new FunnelDTO();
         copy.setId(original.getId());
@@ -2029,14 +2414,17 @@ public final class DtoFactory {
     /**
      * Factory method for creating a new RevisionDTO based on this controller.
      *
-     * @param revision
+     * @param lastMod
      * @return
      */
-    public RevisionDTO createRevisionDTO(Revision revision) {
+    public RevisionDTO createRevisionDTO(FlowModification lastMod) {
+        final Revision revision = lastMod.getRevision();
+        
         // create the dto
         final RevisionDTO revisionDTO = new RevisionDTO();
         revisionDTO.setVersion(revision.getVersion());
         revisionDTO.setClientId(revision.getClientId());
+        revisionDTO.setLastModifier(lastMod.getLastModifier());
 
         return revisionDTO;
     }
@@ -2146,8 +2534,6 @@ public final class DtoFactory {
     }
 
     /* setters */
-    public void setProperties(NiFiProperties properties) {
-    }
 
     public void setControllerServiceLookup(ControllerServiceLookup lookup) {
         this.controllerServiceLookup = lookup;

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/e9647717/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
index 117555a..a373f05 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
@@ -52,8 +52,6 @@ import org.apache.nifi.controller.ProcessorNode;
 import org.apache.nifi.controller.ScheduledState;
 import org.apache.nifi.controller.repository.ContentNotFoundException;
 import org.apache.nifi.controller.repository.claim.ContentDirection;
-import org.apache.nifi.controller.service.ControllerServiceNode;
-import org.apache.nifi.controller.service.ControllerServiceProvider;
 import org.apache.nifi.controller.status.ProcessGroupStatus;
 import org.apache.nifi.diagnostics.SystemDiagnostics;
 import org.apache.nifi.flowfile.FlowFilePrioritizer;
@@ -79,11 +77,11 @@ import org.apache.nifi.provenance.search.SearchableField;
 import org.apache.nifi.remote.RootGroupPort;
 import org.apache.nifi.reporting.Bulletin;
 import org.apache.nifi.reporting.BulletinRepository;
+import org.apache.nifi.reporting.ReportingTask;
 import org.apache.nifi.scheduling.SchedulingStrategy;
 import org.apache.nifi.search.SearchContext;
 import org.apache.nifi.search.SearchResult;
 import org.apache.nifi.search.Searchable;
-import org.apache.nifi.web.security.user.NiFiUserUtils;
 import org.apache.nifi.services.FlowService;
 import org.apache.nifi.user.NiFiUser;
 import org.apache.nifi.util.FormatUtils;
@@ -114,6 +112,7 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.admin.service.UserService;
 import org.apache.nifi.authorization.DownloadAuthorization;
 import org.apache.nifi.processor.DataUnit;
+import org.apache.nifi.web.security.user.NiFiUserUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.security.access.AccessDeniedException;
@@ -121,7 +120,7 @@ import org.springframework.security.access.AccessDeniedException;
 /**
  *
  */
-public class ControllerFacade implements ControllerServiceProvider {
+public class ControllerFacade {
 
     private static final Logger logger = LoggerFactory.getLogger(ControllerFacade.class);
 
@@ -347,6 +346,24 @@ public class ControllerFacade implements ControllerServiceProvider {
     public Set<DocumentedTypeDTO> getFlowFileComparatorTypes() {
         return dtoFactory.fromDocumentedTypes(ExtensionManager.getExtensions(FlowFilePrioritizer.class));
     }
+    
+    /**
+     * Gets the ControllerService types that this controller supports.
+     * 
+     * @return 
+     */
+    public Set<DocumentedTypeDTO> getControllerServiceTypes() {
+        return dtoFactory.fromDocumentedTypes(ControllerService.class, ExtensionManager.getExtensions(ControllerService.class));
+    }
+    
+    /**
+     * Gets the ReportingTask types that this controller supports.
+     * 
+     * @return 
+     */
+    public Set<DocumentedTypeDTO> getReportingTaskTypes() {
+        return dtoFactory.fromDocumentedTypes(ReportingTask.class, ExtensionManager.getExtensions(ReportingTask.class));
+    }
 
     /**
      * Gets the counters for this controller.
@@ -371,56 +388,6 @@ public class ControllerFacade implements ControllerServiceProvider {
 
         return counter;
     }
-
-    /**
-     * Return the controller service for the specified identifier.
-     *
-     * @param serviceIdentifier
-     * @return
-     */
-    @Override
-    public ControllerService getControllerService(String serviceIdentifier) {
-        return flowController.getControllerService(serviceIdentifier);
-    }
-
-    @Override
-    public ControllerServiceNode createControllerService(String type, String id, boolean firstTimeAdded) {
-        return flowController.createControllerService(type, id, firstTimeAdded);
-    }
-    
-    public void removeControllerService(ControllerServiceNode serviceNode) {
-        flowController.removeControllerService(serviceNode);
-    }
-
-    @Override
-    public Set<String> getControllerServiceIdentifiers(Class<? extends ControllerService> serviceType) {
-        return flowController.getControllerServiceIdentifiers(serviceType);
-    }
-
-    @Override
-    public ControllerServiceNode getControllerServiceNode(final String id) {
-        return flowController.getControllerServiceNode(id);
-    }
-
-    @Override
-    public boolean isControllerServiceEnabled(final ControllerService service) {
-        return flowController.isControllerServiceEnabled(service);
-    }
-
-    @Override
-    public boolean isControllerServiceEnabled(final String serviceIdentifier) {
-        return flowController.isControllerServiceEnabled(serviceIdentifier);
-    }
-
-    @Override
-    public void enableControllerService(final ControllerServiceNode serviceNode) {
-        flowController.enableControllerService(serviceNode);
-    }
-    
-    @Override
-    public void disableControllerService(ControllerServiceNode serviceNode) {
-        flowController.disableControllerService(serviceNode);
-    }
     
     /**
      * Gets the status of this controller.

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/e9647717/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ControllerServiceDAO.java
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ControllerServiceDAO.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ControllerServiceDAO.java
new file mode 100644
index 0000000..c1fba0c
--- /dev/null
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ControllerServiceDAO.java
@@ -0,0 +1,110 @@
+/*
+ * 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.web.dao;
+
+import java.util.Set;
+import org.apache.nifi.controller.ScheduledState;
+
+import org.apache.nifi.controller.service.ControllerServiceNode;
+import org.apache.nifi.controller.service.ControllerServiceReference;
+import org.apache.nifi.controller.service.ControllerServiceState;
+import org.apache.nifi.web.api.dto.ControllerServiceDTO;
+
+/**
+ *
+ */
+public interface ControllerServiceDAO {
+
+    /**
+     * Determines if the specified controller service exists.
+     *
+     * @param controllerServiceId
+     * @return
+     */
+    boolean hasControllerService(String controllerServiceId);
+
+    /**
+     * Creates a controller service.
+     *
+     * @param controllerServiceDTO The controller service DTO
+     * @return The controller service
+     */
+    ControllerServiceNode createControllerService(ControllerServiceDTO controllerServiceDTO);
+
+    /**
+     * Gets the specified controller service.
+     *
+     * @param controllerServiceId The controller service id
+     * @return The controller service
+     */
+    ControllerServiceNode getControllerService(String controllerServiceId);
+
+    /**
+     * Gets all of the controller services.
+     *
+     * @return The controller services
+     */
+    Set<ControllerServiceNode> getControllerServices();
+
+    /**
+     * Updates the specified controller service.
+     *
+     * @param controllerServiceDTO The controller service DTO
+     * @return The controller service
+     */
+    ControllerServiceNode updateControllerService(ControllerServiceDTO controllerServiceDTO);
+
+    /**
+     * Updates the referencing components for the specified controller service.
+     * 
+     * @param controllerServiceId
+     * @param scheduledState
+     * @param controllerServiceState the value of state 
+     * @return the org.apache.nifi.controller.service.ControllerServiceReference 
+     */
+    ControllerServiceReference updateControllerServiceReferencingComponents(String controllerServiceId, ScheduledState scheduledState, ControllerServiceState controllerServiceState);
+    
+    /**
+     * Determines whether this controller service can be updated.
+     *
+     * @param controllerServiceDTO
+     */
+    void verifyUpdate(ControllerServiceDTO controllerServiceDTO);
+    
+    /**
+     * Determines whether the referencing component of the specified controller service can be updated.
+     * 
+     * @param controllerServiceId
+     * @param scheduledState
+     * @param controllerServiceState 
+     */
+    void verifyUpdateReferencingComponents(String controllerServiceId, ScheduledState scheduledState, ControllerServiceState controllerServiceState);
+    
+    /**
+     * Determines whether this controller service can be removed.
+     *
+     * @param controllerServiceId
+     */
+    void verifyDelete(String controllerServiceId);
+
+    /**
+     * Deletes the specified controller service.
+     *
+     * @param controllerServiceId The controller service id
+     */
+    void deleteControllerService(String controllerServiceId);
+}

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/e9647717/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ReportingTaskDAO.java
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ReportingTaskDAO.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ReportingTaskDAO.java
new file mode 100644
index 0000000..49446d3
--- /dev/null
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ReportingTaskDAO.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.dao;
+
+import java.util.Set;
+import org.apache.nifi.controller.ReportingTaskNode;
+
+import org.apache.nifi.web.api.dto.ReportingTaskDTO;
+
+/**
+ *
+ */
+public interface ReportingTaskDAO {
+
+    /**
+     * Determines if the specified reporting task exists.
+     *
+     * @param reportingTaskId
+     * @return
+     */
+    boolean hasReportingTask(String reportingTaskId);
+
+    /**
+     * Creates a reporting task.
+     *
+     * @param reportingTaskDTO The reporting task DTO
+     * @return The reporting task
+     */
+    ReportingTaskNode createReportingTask(ReportingTaskDTO reportingTaskDTO);
+
+    /**
+     * Gets the specified reporting task.
+     *
+     * @param reportingTaskId The reporting task id
+     * @return The reporting task
+     */
+    ReportingTaskNode getReportingTask(String reportingTaskId);
+
+    /**
+     * Gets all of the reporting tasks.
+     *
+     * @return The reporting tasks
+     */
+    Set<ReportingTaskNode> getReportingTasks();
+
+    /**
+     * Updates the specified reporting task.
+     *
+     * @param reportingTaskDTO The reporting task DTO
+     * @return The reporting task
+     */
+    ReportingTaskNode updateReportingTask(ReportingTaskDTO reportingTaskDTO);
+
+    /**
+     * Determines whether this reporting task can be updated.
+     *
+     * @param reportingTaskDTO
+     */
+    void verifyUpdate(ReportingTaskDTO reportingTaskDTO);
+    
+    /**
+     * Determines whether this reporting task can be removed.
+     *
+     * @param reportingTaskId
+     */
+    void verifyDelete(String reportingTaskId);
+
+    /**
+     * Deletes the specified reporting task.
+     *
+     * @param reportingTaskId The reporting task id
+     */
+    void deleteReportingTask(String reportingTaskId);
+}