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:49 UTC

[14/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/StandardNiFiWebConfigurationContext.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/StandardNiFiWebConfigurationContext.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java
new file mode 100644
index 0000000..8d51a58
--- /dev/null
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java
@@ -0,0 +1,736 @@
+/*
+ * 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;
+
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+import org.apache.nifi.action.Action;
+import org.apache.nifi.action.Component;
+import org.apache.nifi.action.Operation;
+import org.apache.nifi.action.component.details.ExtensionDetails;
+import org.apache.nifi.action.details.ConfigureDetails;
+import org.apache.nifi.admin.service.AuditService;
+import org.apache.nifi.cluster.manager.NodeResponse;
+import org.apache.nifi.cluster.manager.impl.WebClusterManager;
+import org.apache.nifi.controller.ControllerService;
+import org.apache.nifi.web.security.user.NiFiUserDetails;
+import org.apache.nifi.web.security.user.NiFiUserUtils;
+import org.apache.nifi.user.NiFiUser;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.api.dto.ProcessorConfigDTO;
+import org.apache.nifi.web.api.dto.ProcessorDTO;
+import org.apache.nifi.web.api.dto.RevisionDTO;
+import org.apache.nifi.web.api.entity.ProcessorEntity;
+import org.apache.nifi.web.util.WebUtils;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import com.sun.jersey.core.util.MultivaluedMapImpl;
+import org.apache.nifi.controller.ControllerServiceLookup;
+import org.apache.nifi.controller.reporting.ReportingTaskProvider;
+import org.apache.nifi.web.api.dto.ControllerServiceDTO;
+import org.apache.nifi.web.api.dto.ReportingTaskDTO;
+import org.apache.nifi.web.api.entity.ControllerServiceEntity;
+import org.apache.nifi.web.api.entity.ReportingTaskEntity;
+import org.apache.nifi.web.util.ClientResponseUtils;
+
+/**
+ * Implements the NiFiWebConfigurationContext interface to support a context in both
+ * standalone and clustered environments.
+ */
+public class StandardNiFiWebConfigurationContext implements NiFiWebConfigurationContext {
+
+    private static final Logger logger = LoggerFactory.getLogger(StandardNiFiWebConfigurationContext.class);
+    public static final String CLIENT_ID_PARAM = "clientId";
+    public static final String REVISION_PARAM = "revision";
+    public static final String VERBOSE_PARAM = "verbose";
+
+    private NiFiProperties properties;
+    private NiFiServiceFacade serviceFacade;
+    private WebClusterManager clusterManager;
+    private ControllerServiceLookup controllerServiceLookup;
+    private ReportingTaskProvider reportingTaskProvider;
+    private AuditService auditService;
+
+    @Override
+    public ControllerService getControllerService(String serviceIdentifier) {
+        return controllerServiceLookup.getControllerService(serviceIdentifier);
+    }
+
+    @Override
+    @PreAuthorize("hasAnyRole('ROLE_DFM')")
+    public void saveActions(final NiFiWebRequestContext requestContext, final Collection<ConfigurationAction> configurationActions) {
+        Objects.requireNonNull(configurationActions, "Actions cannot be null.");
+
+        // ensure the path could be 
+        if (requestContext.getExtensionType() == null) {
+            throw new IllegalArgumentException("The UI extension type must be specified.");
+        }
+        
+        Component componentType = null;
+        switch (requestContext.getExtensionType()) {
+            case ProcessorConfiguration:
+                componentType = Component.Processor;
+                break;
+            case ControllerServiceConfiguration:
+                componentType = Component.ControllerService;
+                break;
+            case ReportingTaskConfiguration:
+                componentType = Component.ReportingTask;
+                break;
+        }
+
+        if (componentType == null) {
+            throw new IllegalArgumentException("UI extension type must support Processor, ControllerService, or ReportingTask configuration.");
+        }
+        
+        // - when running standalone or cluster ncm - actions from custom UIs are stored locally
+        // - clustered nodes do not serve custom UIs directly to users so they should never be invoking this method
+        final Date now = new Date();
+        final Collection<Action> actions = new HashSet<>(configurationActions.size());
+        for (final ConfigurationAction configurationAction : configurationActions) {
+            final ExtensionDetails extensionDetails = new ExtensionDetails();
+            extensionDetails.setType(configurationAction.getType());
+
+            final ConfigureDetails configureDetails = new ConfigureDetails();
+            configureDetails.setName(configurationAction.getName());
+            configureDetails.setPreviousValue(configurationAction.getPreviousValue());
+            configureDetails.setValue(configurationAction.getValue());
+
+            final Action action = new Action();
+            action.setTimestamp(now);
+            action.setSourceId(configurationAction.getId());
+            action.setSourceName(configurationAction.getName());
+            action.setSourceType(componentType);
+            action.setOperation(Operation.Configure);
+            action.setUserDn(getCurrentUserDn());
+            action.setUserName(getCurrentUserName());
+            action.setComponentDetails(extensionDetails);
+            action.setActionDetails(configureDetails);
+            actions.add(action);
+        }
+
+        if (!actions.isEmpty()) {
+            try {
+                // record the operations
+                auditService.addActions(actions);
+            } catch (Throwable t) {
+                logger.warn("Unable to record actions: " + t.getMessage());
+                if (logger.isDebugEnabled()) {
+                    logger.warn(StringUtils.EMPTY, t);
+                }
+            }
+        }
+    }
+
+    @Override
+    public String getCurrentUserDn() {
+        String userDn = NiFiUser.ANONYMOUS_USER_DN;
+
+        final NiFiUser user = NiFiUserUtils.getNiFiUser();
+        if (user != null) {
+            userDn = user.getDn();
+        }
+
+        return userDn;
+    }
+
+    @Override
+    public String getCurrentUserName() {
+        String userName = NiFiUser.ANONYMOUS_USER_DN;
+
+        final NiFiUser user = NiFiUserUtils.getNiFiUser();
+        if (user != null) {
+            userName = user.getUserName();
+        }
+
+        return userName;
+    }
+
+    @Override
+    public ComponentDetails getComponentDetails(final NiFiWebRequestContext requestContext) throws ResourceNotFoundException, ClusterRequestException {
+        final String id = requestContext.getId();
+
+        if (StringUtils.isBlank(id)) {
+            throw new ResourceNotFoundException(String.format("Configuration request context config did not have a component ID."));
+        }
+
+        // ensure the path could be 
+        if (requestContext.getExtensionType() == null) {
+            throw new IllegalArgumentException("The UI extension type must be specified.");
+        }
+        
+        // get the component facade for interacting directly with that type of object
+        ComponentFacade componentFacade = null;
+        switch (requestContext.getExtensionType()) {
+            case ProcessorConfiguration:
+                componentFacade = new ProcessorFacade();
+                break;
+            case ControllerServiceConfiguration: 
+                componentFacade = new ControllerServiceFacade();
+                break;
+            case ReportingTaskConfiguration:
+                componentFacade = new ReportingTaskFacade();
+                break;
+        }
+        
+        if (componentFacade == null) {
+            throw new IllegalArgumentException("UI extension type must support Processor, ControllerService, or ReportingTask configuration.");
+        }
+        
+        return componentFacade.getComponentDetails(requestContext);
+    }
+
+    @Override
+    @PreAuthorize("hasAnyRole('ROLE_DFM')")
+    public ComponentDetails setAnnotationData(final NiFiWebConfigurationRequestContext requestContext, final String annotationData)
+            throws ResourceNotFoundException, InvalidRevisionException, ClusterRequestException {
+
+        final String id = requestContext.getId();
+
+        if (StringUtils.isBlank(id)) {
+            throw new ResourceNotFoundException(String.format("Configuration request context did not have a component ID."));
+        }
+        
+        // ensure the path could be 
+        if (requestContext.getExtensionType() == null) {
+            throw new IllegalArgumentException("The UI extension type must be specified.");
+        }
+
+        // get the component facade for interacting directly with that type of object
+        ComponentFacade componentFacade = null;
+        switch (requestContext.getExtensionType()) {
+            case ProcessorConfiguration:
+                componentFacade = new ProcessorFacade();
+                break;
+            case ControllerServiceConfiguration: 
+                componentFacade = new ControllerServiceFacade();
+                break;
+            case ReportingTaskConfiguration:
+                componentFacade = new ReportingTaskFacade();
+                break;
+        }
+        
+        if (componentFacade == null) {
+            throw new IllegalArgumentException("UI extension type must support Processor, ControllerService, or ReportingTask configuration.");
+        }
+        
+        return componentFacade.setAnnotationData(requestContext, annotationData);
+    }
+
+    /**
+     * Facade over accessing different types of NiFi components.
+     */
+    private interface ComponentFacade {
+        /**
+         * Gets the component details using the specified request context.
+         * 
+         * @param requestContext
+         * @return 
+         */
+        ComponentDetails getComponentDetails(NiFiWebRequestContext requestContext);
+        
+        /**
+         * Sets the annotation data using the specified request context.
+         * 
+         * @param requestContext
+         * @param annotationData
+         * @return 
+         */
+        ComponentDetails setAnnotationData(NiFiWebConfigurationRequestContext requestContext, String annotationData);
+    }
+    
+    /**
+     * Interprets the request/response with the underlying Processor model.
+     */
+    private class ProcessorFacade implements ComponentFacade {
+        @Override
+        public ComponentDetails getComponentDetails(final NiFiWebRequestContext requestContext) {
+            final String id = requestContext.getId();
+            
+            final ProcessorDTO processor;
+            if (properties.isClusterManager()) {
+                // create the request URL
+                URI requestUrl;
+                try {
+                    String path = "/nifi-api/cluster/processors/" + URLEncoder.encode(id, "UTF-8");
+                    requestUrl = new URI(requestContext.getScheme(), null, "localhost", 0, path, null, null);
+                } catch (final URISyntaxException | UnsupportedEncodingException use) {
+                    throw new ClusterRequestException(use);
+                }
+
+                // set the request parameters
+                MultivaluedMap<String, String> parameters = new MultivaluedMapImpl();
+                parameters.add(VERBOSE_PARAM, "true");
+
+                // replicate request
+                NodeResponse nodeResponse = clusterManager.applyRequest(HttpMethod.GET, requestUrl, parameters, getHeaders(requestContext));
+
+                // check for issues replicating request
+                checkResponse(nodeResponse, id);
+
+                // return processor
+                ProcessorEntity entity = (ProcessorEntity) nodeResponse.getUpdatedEntity();
+                if (entity == null) {
+                    entity = nodeResponse.getClientResponse().getEntity(ProcessorEntity.class);
+                }
+                processor = entity.getProcessor();
+            } else {
+                processor = serviceFacade.getProcessor(id);
+            }
+
+            // return the processor info
+            return getComponentConfiguration(processor);
+        }
+
+        @Override
+        public ComponentDetails setAnnotationData(final NiFiWebConfigurationRequestContext requestContext, final String annotationData) {
+            final Revision revision = requestContext.getRevision();
+            final String id = requestContext.getId();
+            
+            final ProcessorDTO processor;
+            if (properties.isClusterManager()) {
+                // create the request URL
+                URI requestUrl;
+                try {
+                    String path = "/nifi-api/cluster/processors/" + URLEncoder.encode(id, "UTF-8");
+                    requestUrl = new URI(requestContext.getScheme(), null, "localhost", 0, path, null, null);
+                } catch (final URISyntaxException | UnsupportedEncodingException use) {
+                    throw new ClusterRequestException(use);
+                }
+
+                // create the revision
+                RevisionDTO revisionDto = new RevisionDTO();
+                revisionDto.setClientId(revision.getClientId());
+                revisionDto.setVersion(revision.getVersion());
+
+                // create the processor entity
+                ProcessorEntity processorEntity = new ProcessorEntity();
+                processorEntity.setRevision(revisionDto);
+
+                // create the processor dto
+                ProcessorDTO processorDto = new ProcessorDTO();
+                processorEntity.setProcessor(processorDto);
+                processorDto.setId(id);
+
+                // create the processor configuration with the given annotation data
+                ProcessorConfigDTO configDto = new ProcessorConfigDTO();
+                processorDto.setConfig(configDto);
+                configDto.setAnnotationData(annotationData);
+
+                // set the content type to json
+                final Map<String, String> headers = getHeaders(requestContext);
+                headers.put("Content-Type", "application/json");
+
+                // replicate request
+                NodeResponse nodeResponse = clusterManager.applyRequest(HttpMethod.PUT, requestUrl, processorEntity, headers);
+
+                // check for issues replicating request
+                checkResponse(nodeResponse, id);
+                
+                // return processor
+                ProcessorEntity entity = (ProcessorEntity) nodeResponse.getUpdatedEntity();
+                if (entity == null) {
+                    entity = nodeResponse.getClientResponse().getEntity(ProcessorEntity.class);
+                }
+                processor = entity.getProcessor();
+            } else {
+                final ConfigurationSnapshot<ProcessorDTO> response = serviceFacade.setProcessorAnnotationData(revision, id, annotationData);
+                processor = response.getConfiguration();
+            }
+            
+            // return the processor info
+            return getComponentConfiguration(processor);
+        }
+        
+        private ComponentDetails getComponentConfiguration(final ProcessorDTO processor) {
+            final ProcessorConfigDTO processorConfig = processor.getConfig();
+            return new ComponentDetails.Builder()
+                    .id(processor.getId())
+                    .name(processor.getName())
+                    .type(processor.getType())
+                    .state(processor.getState())
+                    .annotationData(processorConfig.getAnnotationData())
+                    .properties(processorConfig.getProperties())
+                    .validateErrors(processor.getValidationErrors()).build();
+        }
+    }
+    
+    /**
+     * Interprets the request/response with the underlying ControllerService model.
+     */
+    private class ControllerServiceFacade implements ComponentFacade {
+        @Override
+        public ComponentDetails getComponentDetails(final NiFiWebRequestContext requestContext) {
+            final String id = requestContext.getId();
+            final ControllerServiceDTO controllerService;
+            
+            // if the lookup has the service that means we are either a node or
+            // the ncm and the service is available there only
+            if (controllerServiceLookup.getControllerService(id) != null) {
+                controllerService = serviceFacade.getControllerService(id);
+            } else {
+                // if this is a standalone instance the service should have been found above... there should
+                // no cluster to replicate the request to
+                if (!properties.isClusterManager()) {
+                    throw new ResourceNotFoundException(String.format("Controller service[%s] could not be found on this NiFi.", id));
+                }
+                
+                // create the request URL
+                URI requestUrl;
+                try {
+                    String path = "/nifi-api/controller/controller-services/node/" + URLEncoder.encode(id, "UTF-8");
+                    requestUrl = new URI(requestContext.getScheme(), null, "localhost", 0, path, null, null);
+                } catch (final URISyntaxException | UnsupportedEncodingException use) {
+                    throw new ClusterRequestException(use);
+                }
+
+                // set the request parameters
+                MultivaluedMap<String, String> parameters = new MultivaluedMapImpl();
+
+                // replicate request
+                NodeResponse nodeResponse = clusterManager.applyRequest(HttpMethod.GET, requestUrl, parameters, getHeaders(requestContext));
+
+                // check for issues replicating request
+                checkResponse(nodeResponse, id);
+
+                // return controller service
+                ControllerServiceEntity entity = (ControllerServiceEntity) nodeResponse.getUpdatedEntity();
+                if (entity == null) {
+                    entity = nodeResponse.getClientResponse().getEntity(ControllerServiceEntity.class);
+                }
+                controllerService = entity.getControllerService();
+            }
+
+            // return the controller service info
+            return getComponentConfiguration(controllerService);
+        }
+
+        @Override
+        public ComponentDetails setAnnotationData(final NiFiWebConfigurationRequestContext requestContext, final String annotationData) {
+            final Revision revision = requestContext.getRevision();
+            final String id = requestContext.getId();
+            
+            final ControllerServiceDTO controllerService;
+            if (controllerServiceLookup.getControllerService(id) != null) {
+                final ControllerServiceDTO controllerServiceDto = new ControllerServiceDTO();
+                controllerServiceDto.setId(id);
+                controllerServiceDto.setAnnotationData(annotationData);
+                
+                final ConfigurationSnapshot<ControllerServiceDTO> response = serviceFacade.updateControllerService(revision, controllerServiceDto);
+                controllerService = response.getConfiguration();
+            } else {
+                // if this is a standalone instance the service should have been found above... there should
+                // no cluster to replicate the request to
+                if (!properties.isClusterManager()) {
+                    throw new ResourceNotFoundException(String.format("Controller service[%s] could not be found on this NiFi.", id));
+                }
+                
+                // since this PUT request can be interpreted as a request to create a controller service
+                // we need to be sure that this service exists on the node before the request is replicated.
+                // this is done by attempting to get the details. if the service doesn't exist it will
+                // throw a ResourceNotFoundException
+                getComponentDetails(requestContext);
+                
+                // create the request URL
+                URI requestUrl;
+                try {
+                    String path = "/nifi-api/controller/controller-services/node/" + URLEncoder.encode(id, "UTF-8");
+                    requestUrl = new URI(requestContext.getScheme(), null, "localhost", 0, path, null, null);
+                } catch (final URISyntaxException | UnsupportedEncodingException use) {
+                    throw new ClusterRequestException(use);
+                }
+
+                // create the revision
+                RevisionDTO revisionDto = new RevisionDTO();
+                revisionDto.setClientId(revision.getClientId());
+                revisionDto.setVersion(revision.getVersion());
+
+                // create the controller service entity
+                ControllerServiceEntity controllerServiceEntity = new ControllerServiceEntity();
+                controllerServiceEntity.setRevision(revisionDto);
+
+                // create the controller service dto
+                ControllerServiceDTO controllerServiceDto = new ControllerServiceDTO();
+                controllerServiceEntity.setControllerService(controllerServiceDto);
+                controllerServiceDto.setId(id);
+                controllerServiceDto.setAnnotationData(annotationData);
+
+                // set the content type to json
+                final Map<String, String> headers = getHeaders(requestContext);
+                headers.put("Content-Type", "application/json");
+
+                // replicate request
+                NodeResponse nodeResponse = clusterManager.applyRequest(HttpMethod.PUT, requestUrl, controllerServiceEntity, headers);
+
+                // check for issues replicating request
+                checkResponse(nodeResponse, id);
+                
+                // return controller service
+                ControllerServiceEntity entity = (ControllerServiceEntity) nodeResponse.getUpdatedEntity();
+                if (entity == null) {
+                    entity = nodeResponse.getClientResponse().getEntity(ControllerServiceEntity.class);
+                }
+                controllerService = entity.getControllerService();
+            }
+            
+            // return the controller service info
+            return getComponentConfiguration(controllerService);
+        }
+        
+        private ComponentDetails getComponentConfiguration(final ControllerServiceDTO controllerService) {
+            return new ComponentDetails.Builder()
+                    .id(controllerService.getId())
+                    .name(controllerService.getName())
+                    .type(controllerService.getType())
+                    .state(controllerService.getState())
+                    .annotationData(controllerService.getAnnotationData())
+                    .properties(controllerService.getProperties())
+                    .validateErrors(controllerService.getValidationErrors()).build();
+        }
+    }
+    
+    /**
+     * Interprets the request/response with the underlying ControllerService model.
+     */
+    private class ReportingTaskFacade implements ComponentFacade {
+        @Override
+        public ComponentDetails getComponentDetails(final NiFiWebRequestContext requestContext) {
+            final String id = requestContext.getId();
+            final ReportingTaskDTO reportingTask;
+            
+            // if the provider has the service that means we are either a node or
+            // the ncm and the service is available there only
+            if (reportingTaskProvider.getReportingTaskNode(id) != null) {
+                reportingTask = serviceFacade.getReportingTask(id);
+            } else {
+                // if this is a standalone instance the task should have been found above... there should
+                // no cluster to replicate the request to
+                if (!properties.isClusterManager()) {
+                    throw new ResourceNotFoundException(String.format("Reporting task[%s] could not be found on this NiFi.", id));
+                }
+                
+                // create the request URL
+                URI requestUrl;
+                try {
+                    String path = "/nifi-api/controller/reporting-tasks/node/" + URLEncoder.encode(id, "UTF-8");
+                    requestUrl = new URI(requestContext.getScheme(), null, "localhost", 0, path, null, null);
+                } catch (final URISyntaxException | UnsupportedEncodingException use) {
+                    throw new ClusterRequestException(use);
+                }
+
+                // set the request parameters
+                MultivaluedMap<String, String> parameters = new MultivaluedMapImpl();
+
+                // replicate request
+                NodeResponse nodeResponse = clusterManager.applyRequest(HttpMethod.GET, requestUrl, parameters, getHeaders(requestContext));
+
+                // check for issues replicating request
+                checkResponse(nodeResponse, id);
+
+                // return reporting task
+                ReportingTaskEntity entity = (ReportingTaskEntity) nodeResponse.getUpdatedEntity();
+                if (entity == null) {
+                    entity = nodeResponse.getClientResponse().getEntity(ReportingTaskEntity.class);
+                }
+                reportingTask = entity.getReportingTask();
+            }
+
+            // return the reporting task info
+            return getComponentConfiguration(reportingTask);
+        }
+
+        @Override
+        public ComponentDetails setAnnotationData(final NiFiWebConfigurationRequestContext requestContext, final String annotationData) {
+            final Revision revision = requestContext.getRevision();
+            final String id = requestContext.getId();
+            
+            final ReportingTaskDTO reportingTask;
+            if (reportingTaskProvider.getReportingTaskNode(id) != null) {
+                final ReportingTaskDTO reportingTaskDto = new ReportingTaskDTO();
+                reportingTaskDto.setId(id);
+                reportingTaskDto.setAnnotationData(annotationData);
+                
+                final ConfigurationSnapshot<ReportingTaskDTO> response = serviceFacade.updateReportingTask(revision, reportingTaskDto);
+                reportingTask = response.getConfiguration();
+            } else {
+                // if this is a standalone instance the task should have been found above... there should
+                // no cluster to replicate the request to
+                if (!properties.isClusterManager()) {
+                    throw new ResourceNotFoundException(String.format("Reporting task[%s] could not be found on this NiFi.", id));
+                }
+                
+                // since this PUT request can be interpreted as a request to create a reporting task
+                // we need to be sure that this task exists on the node before the request is replicated.
+                // this is done by attempting to get the details. if the service doesn't exist it will
+                // throw a ResourceNotFoundException
+                getComponentDetails(requestContext);
+                
+                // create the request URL
+                URI requestUrl;
+                try {
+                    String path = "/nifi-api/controller/reporting-tasks/node/" + URLEncoder.encode(id, "UTF-8");
+                    requestUrl = new URI(requestContext.getScheme(), null, "localhost", 0, path, null, null);
+                } catch (final URISyntaxException | UnsupportedEncodingException use) {
+                    throw new ClusterRequestException(use);
+                }
+
+                // create the revision
+                RevisionDTO revisionDto = new RevisionDTO();
+                revisionDto.setClientId(revision.getClientId());
+                revisionDto.setVersion(revision.getVersion());
+
+                // create the reporting task entity
+                ReportingTaskEntity reportingTaskEntity = new ReportingTaskEntity();
+                reportingTaskEntity.setRevision(revisionDto);
+
+                // create the reporting task dto
+                ReportingTaskDTO reportingTaskDto = new ReportingTaskDTO();
+                reportingTaskEntity.setReportingTask(reportingTaskDto);
+                reportingTaskDto.setId(id);
+                reportingTaskDto.setAnnotationData(annotationData);
+
+                // set the content type to json
+                final Map<String, String> headers = getHeaders(requestContext);
+                headers.put("Content-Type", "application/json");
+
+                // replicate request
+                NodeResponse nodeResponse = clusterManager.applyRequest(HttpMethod.PUT, requestUrl, reportingTaskEntity, headers);
+
+                // check for issues replicating request
+                checkResponse(nodeResponse, id);
+                
+                // return reporting task
+                ReportingTaskEntity entity = (ReportingTaskEntity) nodeResponse.getUpdatedEntity();
+                if (entity == null) {
+                    entity = nodeResponse.getClientResponse().getEntity(ReportingTaskEntity.class);
+                }
+                reportingTask = entity.getReportingTask();
+            }
+            
+            // return the processor info
+            return getComponentConfiguration(reportingTask);
+        }
+        
+        private ComponentDetails getComponentConfiguration(final ReportingTaskDTO reportingTask) {
+            return new ComponentDetails.Builder()
+                    .id(reportingTask.getId())
+                    .name(reportingTask.getName())
+                    .type(reportingTask.getType())
+                    .state(reportingTask.getState())
+                    .annotationData(reportingTask.getAnnotationData())
+                    .properties(reportingTask.getProperties())
+                    .validateErrors(reportingTask.getValidationErrors()).build();
+        }
+    }
+    
+    /**
+     * Gets the headers for the request to replicate to each node while
+     * clustered.
+     *
+     * @param config
+     * @return
+     */
+    private Map<String, String> getHeaders(final NiFiWebRequestContext config) {
+        final Map<String, String> headers = new HashMap<>();
+        headers.put("Accept", "application/json,application/xml");
+        if (StringUtils.isNotBlank(config.getProxiedEntitiesChain())) {
+            headers.put("X-ProxiedEntitiesChain", config.getProxiedEntitiesChain());
+        }
+
+        // add the user's authorities (if any) to the headers
+        final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        if (authentication != null) {
+            final Object userDetailsObj = authentication.getPrincipal();
+            if (userDetailsObj instanceof NiFiUserDetails) {
+                // serialize user details object
+                final String hexEncodedUserDetails = WebUtils.serializeObjectToHex((Serializable) userDetailsObj);
+
+                // put serialized user details in header
+                headers.put("X-ProxiedEntityUserDetails", hexEncodedUserDetails);
+            }
+        }
+        return headers;
+    }
+    
+    /**
+     * Checks the specified response and drains the stream appropriately.
+     * 
+     * @param nodeResponse
+     * @param revision
+     * @param id 
+     */
+    private void checkResponse(final NodeResponse nodeResponse, final String id) {
+        if (nodeResponse.hasThrowable()) {
+            ClientResponseUtils.drainClientResponse(nodeResponse.getClientResponse());
+            throw new ClusterRequestException(nodeResponse.getThrowable());
+        } else if (nodeResponse.getClientResponse().getStatus() == Response.Status.CONFLICT.getStatusCode()) {
+            ClientResponseUtils.drainClientResponse(nodeResponse.getClientResponse());
+            throw new InvalidRevisionException(String.format("Conflict: the flow may have been updated by another user."));
+        } else if (nodeResponse.getClientResponse().getStatus() == Response.Status.NOT_FOUND.getStatusCode()) {
+            ClientResponseUtils.drainClientResponse(nodeResponse.getClientResponse());
+            throw new ResourceNotFoundException("Unable to find component with id: " + id);
+        } else if (nodeResponse.getClientResponse().getStatus() != Response.Status.OK.getStatusCode()) {
+            ClientResponseUtils.drainClientResponse(nodeResponse.getClientResponse());
+            throw new ClusterRequestException("Method resulted in an unsuccessful HTTP response code: " + nodeResponse.getClientResponse().getStatus());
+        }
+    }
+
+    public void setClusterManager(WebClusterManager clusterManager) {
+        this.clusterManager = clusterManager;
+    }
+
+    public void setProperties(NiFiProperties properties) {
+        this.properties = properties;
+    }
+
+    public void setServiceFacade(NiFiServiceFacade serviceFacade) {
+        this.serviceFacade = serviceFacade;
+    }
+
+    public void setAuditService(AuditService auditService) {
+        this.auditService = auditService;
+    }
+
+    public void setControllerServiceLookup(ControllerServiceLookup controllerServiceLookup) {
+        this.controllerServiceLookup = controllerServiceLookup;
+    }
+
+    public void setReportingTaskProvider(ReportingTaskProvider reportingTaskProvider) {
+        this.reportingTaskProvider = reportingTaskProvider;
+    }
+
+}

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/StandardNiFiWebContext.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/StandardNiFiWebContext.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebContext.java
index d9fa9e3..eaf457e 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebContext.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebContext.java
@@ -35,7 +35,7 @@ import javax.ws.rs.core.Response;
 import org.apache.nifi.action.Action;
 import org.apache.nifi.action.Component;
 import org.apache.nifi.action.Operation;
-import org.apache.nifi.action.component.details.ProcessorDetails;
+import org.apache.nifi.action.component.details.ExtensionDetails;
 import org.apache.nifi.action.details.ConfigureDetails;
 import org.apache.nifi.admin.service.AuditService;
 import org.apache.nifi.cluster.manager.NodeResponse;
@@ -49,7 +49,6 @@ import org.apache.nifi.web.api.dto.ProcessorConfigDTO;
 import org.apache.nifi.web.api.dto.ProcessorDTO;
 import org.apache.nifi.web.api.dto.RevisionDTO;
 import org.apache.nifi.web.api.entity.ProcessorEntity;
-import org.apache.nifi.web.controller.ControllerFacade;
 import org.apache.nifi.web.util.WebUtils;
 
 import org.apache.commons.lang3.StringUtils;
@@ -60,12 +59,14 @@ import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContextHolder;
 
 import com.sun.jersey.core.util.MultivaluedMapImpl;
+import org.apache.nifi.controller.ControllerServiceLookup;
 import org.apache.nifi.web.util.ClientResponseUtils;
 
 /**
  * Implements the NiFiWebContext interface to support a context in both
  * standalone and clustered environments.
  */
+@Deprecated
 public class StandardNiFiWebContext implements NiFiWebContext {
 
     private static final Logger logger = LoggerFactory.getLogger(StandardNiFiWebContext.class);
@@ -76,16 +77,12 @@ public class StandardNiFiWebContext implements NiFiWebContext {
     private NiFiProperties properties;
     private NiFiServiceFacade serviceFacade;
     private WebClusterManager clusterManager;
-    private ControllerFacade controllerFacade;
+    private ControllerServiceLookup controllerServiceLookup;
     private AuditService auditService;
 
     @Override
     public ControllerService getControllerService(String serviceIdentifier) {
-        if (properties.isClusterManager()) {
-            return clusterManager.getControllerService(serviceIdentifier);
-        } else {
-            return controllerFacade.getControllerService(serviceIdentifier);
-        }
+        return controllerServiceLookup.getControllerService(serviceIdentifier);
     }
 
     @Override
@@ -98,7 +95,7 @@ public class StandardNiFiWebContext implements NiFiWebContext {
         final Date now = new Date();
         final Collection<Action> actions = new HashSet<>(processorActions.size());
         for (final ProcessorConfigurationAction processorAction : processorActions) {
-            final ProcessorDetails processorDetails = new ProcessorDetails();
+            final ExtensionDetails processorDetails = new ExtensionDetails();
             processorDetails.setType(processorAction.getProcessorType());
 
             final ConfigureDetails configureDetails = new ConfigureDetails();
@@ -199,7 +196,10 @@ public class StandardNiFiWebContext implements NiFiWebContext {
             }
 
             // return processor
-            final ProcessorEntity entity = nodeResponse.getClientResponse().getEntity(ProcessorEntity.class);
+            ProcessorEntity entity = (ProcessorEntity) nodeResponse.getUpdatedEntity();
+            if (entity == null) {
+                entity = nodeResponse.getClientResponse().getEntity(ProcessorEntity.class);
+            }
             processor = entity.getProcessor();
         } else {
             processor = serviceFacade.getProcessor(processorId);
@@ -325,12 +325,12 @@ public class StandardNiFiWebContext implements NiFiWebContext {
         this.serviceFacade = serviceFacade;
     }
 
-    public void setControllerFacade(ControllerFacade controllerFacade) {
-        this.controllerFacade = controllerFacade;
-    }
-
     public void setAuditService(AuditService auditService) {
         this.auditService = auditService;
     }
 
+    public void setControllerServiceLookup(ControllerServiceLookup controllerServiceLookup) {
+        this.controllerServiceLookup = controllerServiceLookup;
+    }
+
 }

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/ApplicationResource.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/ApplicationResource.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java
index 1b9ae7d..787fffa 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java
@@ -178,53 +178,55 @@ public abstract class ApplicationResource {
 
         // get cluster context from threadlocal
         ClusterContext clusterCtx = ClusterContextThreadLocal.getContext();
+        if (clusterCtx != null) {
+            
+            // serialize cluster context
+            String serializedClusterContext = WebUtils.serializeObjectToHex(clusterCtx);
+            if (serializedClusterContext.length() > CLUSTER_CONTEXT_HEADER_VALUE_MAX_BYTES) {
+                /*
+                 * Actions is the only field that can vary in size. If we have no
+                 * actions and we exceeded the header size, then basic assumptions
+                 * about the cluster context have been violated.
+                 */
+                if (clusterCtx.getActions().isEmpty()) {
+                    throw new IllegalStateException(
+                            String.format("Serialized Cluster context size '%d' is too big for response header", serializedClusterContext.length()));
+                }
 
-        // serialize cluster context
-        String serializedClusterContext = WebUtils.serializeObjectToHex(clusterCtx);
-        if (serializedClusterContext.length() > CLUSTER_CONTEXT_HEADER_VALUE_MAX_BYTES) {
-            /*
-             * Actions is the only field that can vary in size. If we have no
-             * actions and we exceeded the header size, then basic assumptions
-             * about the cluster context have been violated.
-             */
-            if (clusterCtx.getActions().isEmpty()) {
-                throw new IllegalStateException(
-                        String.format("Serialized Cluster context size '%d' is too big for response header", serializedClusterContext.length()));
-            }
+                // use the first action as the prototype for creating the "batch" action
+                Action prototypeAction = clusterCtx.getActions().get(0);
 
-            // use the first action as the prototype for creating the "batch" action
-            Action prototypeAction = clusterCtx.getActions().get(0);
+                // log the batched actions
+                StringBuilder loggedActions = new StringBuilder();
+                createBatchedActionLogStatement(loggedActions, clusterCtx.getActions());
+                logger.info(loggedActions.toString());
 
-            // log the batched actions
-            StringBuilder loggedActions = new StringBuilder();
-            createBatchedActionLogStatement(loggedActions, clusterCtx.getActions());
-            logger.info(loggedActions.toString());
+                // remove current actions and replace with batch action
+                clusterCtx.getActions().clear();
 
-            // remove current actions and replace with batch action
-            clusterCtx.getActions().clear();
+                // create the batch action
+                Action batchAction = new Action();
+                batchAction.setOperation(Operation.Batch);
 
-            // create the batch action
-            Action batchAction = new Action();
-            batchAction.setOperation(Operation.Batch);
+                // copy values from prototype action 
+                batchAction.setTimestamp(prototypeAction.getTimestamp());
+                batchAction.setUserDn(prototypeAction.getUserDn());
+                batchAction.setUserName(prototypeAction.getUserName());
+                batchAction.setSourceId(prototypeAction.getSourceId());
+                batchAction.setSourceName(prototypeAction.getSourceName());
+                batchAction.setSourceType(prototypeAction.getSourceType());
 
-            // copy values from prototype action 
-            batchAction.setTimestamp(prototypeAction.getTimestamp());
-            batchAction.setUserDn(prototypeAction.getUserDn());
-            batchAction.setUserName(prototypeAction.getUserName());
-            batchAction.setSourceId(prototypeAction.getSourceId());
-            batchAction.setSourceName(prototypeAction.getSourceName());
-            batchAction.setSourceType(prototypeAction.getSourceType());
+                // add batch action
+                clusterCtx.getActions().add(batchAction);
 
-            // add batch action
-            clusterCtx.getActions().add(batchAction);
+                // create the final serialized copy of the cluster context
+                serializedClusterContext = WebUtils.serializeObjectToHex(clusterCtx);
+            }
 
-            // create the final serialized copy of the cluster context
-            serializedClusterContext = WebUtils.serializeObjectToHex(clusterCtx);
+            // put serialized cluster context in response header
+            response.header(WebClusterManager.CLUSTER_CONTEXT_HTTP_HEADER, serializedClusterContext);
         }
 
-        // put serialized cluster context in response header
-        response.header(WebClusterManager.CLUSTER_CONTEXT_HTTP_HEADER, serializedClusterContext);
-
         return response;
     }
 

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/ClusterResource.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/ClusterResource.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ClusterResource.java
index e87f388..3a74782 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ClusterResource.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ClusterResource.java
@@ -379,7 +379,7 @@ public class ClusterResource extends ApplicationResource {
             // update the revision
             RevisionDTO updatedRevision = new RevisionDTO();
             updatedRevision.setClientId(revision.getClientId());
-            updatedRevision.setVersion(controllerResponse.getRevision());
+            updatedRevision.setVersion(controllerResponse.getVersion());
 
             // generate the response entity
             final ProcessorEntity entity = new ProcessorEntity();

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/ConnectionResource.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/ConnectionResource.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectionResource.java
index a941444..5d233f7 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectionResource.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectionResource.java
@@ -450,7 +450,7 @@ public class ConnectionResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO updatedRevision = new RevisionDTO();
         updatedRevision.setClientId(revision.getClientId());
-        updatedRevision.setVersion(controllerResponse.getRevision());
+        updatedRevision.setVersion(controllerResponse.getVersion());
 
         // create the response entity
         ConnectionEntity entity = new ConnectionEntity();
@@ -684,7 +684,7 @@ public class ConnectionResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO updatedRevision = new RevisionDTO();
         updatedRevision.setClientId(revision.getClientId());
-        updatedRevision.setVersion(controllerResponse.getRevision());
+        updatedRevision.setVersion(controllerResponse.getVersion());
 
         // create the response entity
         ConnectionEntity entity = new ConnectionEntity();
@@ -742,7 +742,7 @@ public class ConnectionResource extends ApplicationResource {
         // create the revision
         final RevisionDTO updatedRevision = new RevisionDTO();
         updatedRevision.setClientId(clientId.getClientId());
-        updatedRevision.setVersion(controllerResponse.getRevision());
+        updatedRevision.setVersion(controllerResponse.getVersion());
 
         // create the response entity
         final ConnectionEntity entity = new ConnectionEntity();

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/ControllerResource.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/ControllerResource.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
index 3afe0e1..98f17d5 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
@@ -72,6 +72,8 @@ import org.apache.nifi.web.api.request.ClientIdParameter;
 import org.apache.nifi.web.api.request.IntegerParameter;
 import org.apache.nifi.web.api.request.LongParameter;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.web.api.entity.ControllerServiceTypesEntity;
+import org.apache.nifi.web.api.entity.ReportingTaskTypesEntity;
 import org.codehaus.enunciate.jaxrs.TypeHint;
 import org.springframework.security.access.prepost.PreAuthorize;
 
@@ -149,7 +151,7 @@ public class ControllerResource extends ApplicationResource {
     }
 
     /**
-     * Locates the Template sub-resource.
+     * Locates the Snippets sub-resource.
      *
      * @return
      */
@@ -157,6 +159,26 @@ public class ControllerResource extends ApplicationResource {
     public SnippetResource getSnippetResource() {
         return resourceContext.getResource(SnippetResource.class);
     }
+    
+    /**
+     * Locates the Controller Services sub-resource.
+     *
+     * @return
+     */
+    @Path("/controller-services")
+    public ControllerServiceResource getControllerServiceResource() {
+        return resourceContext.getResource(ControllerServiceResource.class);
+    }
+    
+    /**
+     * Locates the Reporting Tasks sub-resource.
+     *
+     * @return
+     */
+    @Path("/reporting-tasks")
+    public ReportingTaskResource getReportingTaskResource() {
+        return resourceContext.getResource(ReportingTaskResource.class);
+    }
 
     /**
      * Locates the Group sub-resource.
@@ -303,7 +325,7 @@ public class ControllerResource extends ApplicationResource {
         // create the revision
         final RevisionDTO updatedRevision = new RevisionDTO();
         updatedRevision.setClientId(clientId.getClientId());
-        updatedRevision.setVersion(controllerResponse.getRevision());
+        updatedRevision.setVersion(controllerResponse.getVersion());
 
         // create the response entity
         final ProcessGroupEntity controllerEntity = new ProcessGroupEntity();
@@ -325,11 +347,6 @@ public class ControllerResource extends ApplicationResource {
     @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
     @TypeHint(Entity.class)
     public Response getRevision() {
-        // replicate if cluster manager
-        if (properties.isClusterManager()) {
-            return clusterManager.applyRequest(HttpMethod.GET, getAbsolutePath(), getRequestParameters(true), getHeaders()).getResponse();
-        }
-
         // create the current revision
         final RevisionDTO revision = serviceFacade.getRevision();
 
@@ -595,7 +612,7 @@ public class ControllerResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO updatedRevision = new RevisionDTO();
         updatedRevision.setClientId(revision.getClientId());
-        updatedRevision.setVersion(controllerResponse.getRevision());
+        updatedRevision.setVersion(controllerResponse.getVersion());
 
         // create the response entity
         final ControllerConfigurationEntity entity = new ControllerConfigurationEntity();
@@ -713,6 +730,72 @@ public class ControllerResource extends ApplicationResource {
         // generate the response
         return clusterContext(generateOkResponse(entity)).build();
     }
+    
+    /**
+     * Retrieves the types of controller services that this NiFi supports.
+     *
+     * @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.
+     * @return A controllerServicesTypesEntity.
+     */
+    @GET
+    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Path("/controller-service-types")
+    @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
+    @TypeHint(ControllerServiceTypesEntity.class)
+    public Response getControllerServiceTypes(@QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId) {
+
+        // replicate if cluster manager
+        if (properties.isClusterManager()) {
+            return clusterManager.applyRequest(HttpMethod.GET, getAbsolutePath(), getRequestParameters(true), getHeaders()).getResponse();
+        }
+
+        // create the revision
+        final RevisionDTO revision = new RevisionDTO();
+        revision.setClientId(clientId.getClientId());
+
+        // create response entity
+        final ControllerServiceTypesEntity entity = new ControllerServiceTypesEntity();
+        entity.setRevision(revision);
+        entity.setControllerServiceTypes(serviceFacade.getControllerServiceTypes());
+
+        // generate the response
+        return clusterContext(generateOkResponse(entity)).build();
+    }
+    
+    /**
+     * Retrieves the types of reporting tasks that this NiFi supports.
+     *
+     * @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.
+     * @return A controllerServicesTypesEntity.
+     */
+    @GET
+    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Path("/reporting-task-types")
+    @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
+    @TypeHint(ReportingTaskTypesEntity.class)
+    public Response getReportingTaskTypes(@QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId) {
+
+        // replicate if cluster manager
+        if (properties.isClusterManager()) {
+            return clusterManager.applyRequest(HttpMethod.GET, getAbsolutePath(), getRequestParameters(true), getHeaders()).getResponse();
+        }
+
+        // create the revision
+        final RevisionDTO revision = new RevisionDTO();
+        revision.setClientId(clientId.getClientId());
+
+        // create response entity
+        final ReportingTaskTypesEntity entity = new ReportingTaskTypesEntity();
+        entity.setRevision(revision);
+        entity.setReportingTaskTypes(serviceFacade.getReportingTaskTypes());
+
+        // generate the response
+        return clusterContext(generateOkResponse(entity)).build();
+    }
 
     /**
      * Retrieves the types of prioritizers that this NiFi supports.