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

[11/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/dao/impl/StandardControllerServiceDAO.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/impl/StandardControllerServiceDAO.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardControllerServiceDAO.java
new file mode 100644
index 0000000..14217c5
--- /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/impl/StandardControllerServiceDAO.java
@@ -0,0 +1,320 @@
+/*
+ * 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.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.nifi.controller.ScheduledState;
+import org.apache.nifi.controller.exception.ControllerServiceInstantiationException;
+
+import org.apache.nifi.controller.exception.ValidationException;
+import org.apache.nifi.controller.service.ControllerServiceNode;
+import org.apache.nifi.controller.service.ControllerServiceProvider;
+import org.apache.nifi.controller.service.ControllerServiceReference;
+import org.apache.nifi.controller.service.ControllerServiceState;
+import org.apache.nifi.web.NiFiCoreException;
+import org.apache.nifi.web.ResourceNotFoundException;
+import org.apache.nifi.web.api.dto.ControllerServiceDTO;
+import org.apache.nifi.web.dao.ControllerServiceDAO;
+
+public class StandardControllerServiceDAO extends ComponentDAO implements ControllerServiceDAO {
+
+    private ControllerServiceProvider serviceProvider;
+
+    /**
+     * Locates the specified controller service.
+     *
+     * @param controllerServiceId
+     * @return
+     */
+    private ControllerServiceNode locateControllerService(final String controllerServiceId) {
+        // get the controller service
+        final ControllerServiceNode controllerService = serviceProvider.getControllerServiceNode(controllerServiceId);
+
+        // ensure the controller service exists
+        if (controllerService == null) {
+            throw new ResourceNotFoundException(String.format("Unable to locate controller service with id '%s'.", controllerServiceId));
+        }
+
+        return controllerService;
+    }
+
+    /**
+     * Creates a controller service.
+     *
+     * @param controllerServiceDTO The controller service DTO
+     * @return The controller service
+     */
+    @Override
+    public ControllerServiceNode createControllerService(final ControllerServiceDTO controllerServiceDTO) {
+        // ensure the type is specified
+        if (controllerServiceDTO.getType() == null) {
+            throw new IllegalArgumentException("The controller service type must be specified.");
+        }
+        
+        try {
+            // create the controller service
+            final ControllerServiceNode controllerService = serviceProvider.createControllerService(controllerServiceDTO.getType(), controllerServiceDTO.getId(), true);
+
+            // ensure we can perform the update 
+            verifyUpdate(controllerService, controllerServiceDTO);
+
+            // perform the update
+            configureControllerService(controllerService, controllerServiceDTO);
+
+            return controllerService;
+        } catch (final ControllerServiceInstantiationException csie) {
+            throw new NiFiCoreException(csie.getMessage(), csie);
+        }
+    }
+
+    /**
+     * Gets the specified controller service.
+     *
+     * @param controllerServiceId The controller service id
+     * @return The controller service
+     */
+    @Override
+    public ControllerServiceNode getControllerService(final String controllerServiceId) {
+        return locateControllerService(controllerServiceId);
+    }
+
+    /**
+     * Determines if the specified controller service exists.
+     *
+     * @param controllerServiceId
+     * @return
+     */
+    @Override
+    public boolean hasControllerService(final String controllerServiceId) {
+        return serviceProvider.getControllerServiceNode(controllerServiceId) != null;
+    }
+
+    /**
+     * Gets all of the controller services.
+     *
+     * @return The controller services
+     */
+    @Override
+    public Set<ControllerServiceNode> getControllerServices() {
+        return serviceProvider.getAllControllerServices();
+    }
+
+    /**
+     * Updates the specified controller service.
+     *
+     * @param controllerServiceDTO The controller service DTO
+     * @return The controller service
+     */
+    @Override
+    public ControllerServiceNode updateControllerService(final ControllerServiceDTO controllerServiceDTO) {
+        // get the controller service
+        final ControllerServiceNode controllerService = locateControllerService(controllerServiceDTO.getId());
+        
+        // ensure we can perform the update 
+        verifyUpdate(controllerService, controllerServiceDTO);
+        
+        // perform the update
+        configureControllerService(controllerService, controllerServiceDTO);
+
+        // enable or disable as appropriate
+        if (isNotNull(controllerServiceDTO.getState())) {
+            final ControllerServiceState purposedControllerServiceState = ControllerServiceState.valueOf(controllerServiceDTO.getState());
+
+            // only attempt an action if it is changing
+            if (!purposedControllerServiceState.equals(controllerService.getState())) {
+                if (ControllerServiceState.ENABLED.equals(purposedControllerServiceState)) {
+                    serviceProvider.enableControllerService(controllerService);
+                } else if (ControllerServiceState.DISABLED.equals(purposedControllerServiceState)) {
+                    serviceProvider.disableControllerService(controllerService);
+                }
+            }
+        }
+        
+        return controllerService;
+    }
+
+    @Override
+    public ControllerServiceReference updateControllerServiceReferencingComponents(final String controllerServiceId, final ScheduledState scheduledState, final ControllerServiceState controllerServiceState) {
+        // get the controller service
+        final ControllerServiceNode controllerService = locateControllerService(controllerServiceId);
+        
+        // this request is either acting upon referncing services or schedulable components
+        if (controllerServiceState != null) {
+            if (ControllerServiceState.ENABLED.equals(controllerServiceState)) {
+                serviceProvider.enableReferencingServices(controllerService);
+            } else {
+                serviceProvider.disableReferencingServices(controllerService);
+            }
+        } else if (scheduledState != null) {
+            if (ScheduledState.RUNNING.equals(scheduledState)) {
+                serviceProvider.scheduleReferencingComponents(controllerService);
+            } else {
+                serviceProvider.unscheduleReferencingComponents(controllerService);
+            }
+        }
+        
+        return controllerService.getReferences();
+    }
+
+    /**
+     * Validates the specified configuration for the specified controller service.
+     * 
+     * @param controllerService
+     * @param controllerServiceDTO
+     * @return 
+     */
+    private List<String> validateProposedConfiguration(final ControllerServiceNode controllerService, final ControllerServiceDTO controllerServiceDTO) {
+        final List<String> validationErrors = new ArrayList<>();
+        return validationErrors;
+    }
+    
+    @Override
+    public void verifyDelete(final String controllerServiceId) {
+        final ControllerServiceNode controllerService = locateControllerService(controllerServiceId);
+        controllerService.verifyCanDelete();
+    }
+
+    @Override
+    public void verifyUpdate(final ControllerServiceDTO controllerServiceDTO) {
+        final ControllerServiceNode controllerService = locateControllerService(controllerServiceDTO.getId());
+        verifyUpdate(controllerService, controllerServiceDTO);
+    }
+
+    @Override
+    public void verifyUpdateReferencingComponents(String controllerServiceId, ScheduledState scheduledState, ControllerServiceState controllerServiceState) {
+        final ControllerServiceNode controllerService = locateControllerService(controllerServiceId);
+        
+        if (controllerServiceState != null) {
+            if (ControllerServiceState.ENABLED.equals(controllerServiceState)) {
+                serviceProvider.verifyCanEnableReferencingServices(controllerService);
+            } else {
+                serviceProvider.verifyCanDisableReferencingServices(controllerService);
+            }
+        } else if (scheduledState != null) {
+            if (ScheduledState.RUNNING.equals(scheduledState)) {
+                serviceProvider.verifyCanScheduleReferencingComponents(controllerService);
+            } else {
+                serviceProvider.verifyCanStopReferencingComponents(controllerService);
+            }
+        }
+    }
+    
+    /**
+     * Verifies the controller service can be updated.
+     * 
+     * @param controllerService
+     * @param controllerServiceDTO 
+     */
+    private void verifyUpdate(final ControllerServiceNode controllerService, final ControllerServiceDTO controllerServiceDTO) {
+        // validate the new controller service state if appropriate
+        if (isNotNull(controllerServiceDTO.getState())) {
+            try {
+                // attempt to parse the service state
+                final ControllerServiceState purposedControllerServiceState = ControllerServiceState.valueOf(controllerServiceDTO.getState());
+                
+                // ensure the state is valid
+                if (ControllerServiceState.ENABLING.equals(purposedControllerServiceState) || ControllerServiceState.DISABLING.equals(purposedControllerServiceState)) {
+                    throw new IllegalArgumentException();
+                }
+                
+                // only attempt an action if it is changing
+                if (!purposedControllerServiceState.equals(controllerService.getState())) {
+                    if (ControllerServiceState.ENABLED.equals(purposedControllerServiceState)) {
+                        controllerService.verifyCanEnable();
+                    } else if (ControllerServiceState.DISABLED.equals(purposedControllerServiceState)) {
+                        controllerService.verifyCanDisable();
+                    }
+                }
+            } catch (IllegalArgumentException iae) {
+                throw new IllegalArgumentException("Controller Service state: Value must be one of [ENABLED, DISABLED]");
+            }
+        }
+        
+        boolean modificationRequest = false;
+        if (isAnyNotNull(controllerServiceDTO.getName(),
+                controllerServiceDTO.getAnnotationData(),
+                controllerServiceDTO.getComments(),
+                controllerServiceDTO.getProperties())) {
+            modificationRequest = true;
+            
+            // validate the request
+            final List<String> requestValidation = validateProposedConfiguration(controllerService, controllerServiceDTO);
+
+            // ensure there was no validation errors
+            if (!requestValidation.isEmpty()) {
+                throw new ValidationException(requestValidation);
+            }
+        }
+        
+        if (modificationRequest) {
+            controllerService.verifyCanUpdate();
+        }
+    }
+    
+    /**
+     * Configures the specified controller service.
+     * 
+     * @param controllerService
+     * @param controllerServiceDTO 
+     */
+    private void configureControllerService(final ControllerServiceNode controllerService, final ControllerServiceDTO controllerServiceDTO) {
+        final String name = controllerServiceDTO.getName();
+        final String annotationData = controllerServiceDTO.getAnnotationData();
+        final String comments = controllerServiceDTO.getComments();
+        final Map<String, String> properties = controllerServiceDTO.getProperties();
+        
+        if (isNotNull(name)) {
+            controllerService.setName(name);
+        }
+        if (isNotNull(annotationData)) {
+            controllerService.setAnnotationData(annotationData);
+        }
+        if (isNotNull(comments)) {
+            controllerService.setComments(comments);
+        }
+        if (isNotNull(properties)) {
+            for (final Map.Entry<String, String> entry : properties.entrySet()) {
+                final String propName = entry.getKey();
+                final String propVal = entry.getValue();
+                if (isNotNull(propName) && propVal == null) {
+                    controllerService.removeProperty(propName);
+                } else if (isNotNull(propName)) {
+                    controllerService.setProperty(propName, propVal);
+                }
+            }
+        }
+    }
+    
+    /**
+     * Deletes the specified controller service.
+     *
+     * @param controllerServiceId The controller service id
+     */
+    @Override
+    public void deleteControllerService(String controllerServiceId) {
+        final ControllerServiceNode controllerService = locateControllerService(controllerServiceId);
+        serviceProvider.removeControllerService(controllerService);
+    }
+
+    /* setters */
+    public void setServiceProvider(ControllerServiceProvider serviceProvider) {
+        this.serviceProvider = serviceProvider;
+    }
+}

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/impl/StandardProcessorDAO.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/impl/StandardProcessorDAO.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardProcessorDAO.java
index 633f8e2..0c587fe 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardProcessorDAO.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardProcessorDAO.java
@@ -99,6 +99,11 @@ public class StandardProcessorDAO extends ComponentDAO implements ProcessorDAO {
         if (processorDTO.getParentGroupId() != null && !flowController.areGroupsSame(groupId, processorDTO.getParentGroupId())) {
             throw new IllegalArgumentException("Cannot specify a different Parent Group ID than the Group to which the Processor is being added.");
         }
+        
+        // ensure the type is specified
+        if (processorDTO.getType() == null) {
+            throw new IllegalArgumentException("The processor type must be specified.");
+        }
 
         // get the group to add the processor to
         ProcessGroup group = locateProcessGroup(flowController, groupId);

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/impl/StandardReportingTaskDAO.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/impl/StandardReportingTaskDAO.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardReportingTaskDAO.java
new file mode 100644
index 0000000..d9fd74c
--- /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/impl/StandardReportingTaskDAO.java
@@ -0,0 +1,365 @@
+/*
+ * 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.impl;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.regex.Matcher;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.controller.ReportingTaskNode;
+import org.apache.nifi.controller.ScheduledState;
+import org.apache.nifi.controller.exception.ProcessorLifeCycleException;
+
+import org.apache.nifi.controller.exception.ValidationException;
+import org.apache.nifi.controller.reporting.ReportingTaskInstantiationException;
+import org.apache.nifi.controller.reporting.ReportingTaskProvider;
+import org.apache.nifi.scheduling.SchedulingStrategy;
+import org.apache.nifi.util.FormatUtils;
+import org.apache.nifi.web.NiFiCoreException;
+import org.apache.nifi.web.ResourceNotFoundException;
+import org.apache.nifi.web.api.dto.ReportingTaskDTO;
+import org.apache.nifi.web.dao.ReportingTaskDAO;
+import org.quartz.CronExpression;
+
+public class StandardReportingTaskDAO extends ComponentDAO implements ReportingTaskDAO {
+
+    private ReportingTaskProvider reportingTaskProvider;
+
+    /**
+     * Locates the specified reporting task.
+     *
+     * @param reportingTaskId
+     * @return
+     */
+    private ReportingTaskNode locateReportingTask(final String reportingTaskId) {
+        // get the reporting task
+        final ReportingTaskNode reportingTask = reportingTaskProvider.getReportingTaskNode(reportingTaskId);
+
+        // ensure the reporting task exists
+        if (reportingTask == null) {
+            throw new ResourceNotFoundException(String.format("Unable to locate reporting task with id '%s'.", reportingTaskId));
+        }
+
+        return reportingTask;
+    }
+
+    /**
+     * Creates a reporting task.
+     *
+     * @param reportingTaskDTO The reporting task DTO
+     * @return The reporting task
+     */
+    @Override
+    public ReportingTaskNode createReportingTask(final ReportingTaskDTO reportingTaskDTO) {
+        // ensure the type is specified
+        if (reportingTaskDTO.getType() == null) {
+            throw new IllegalArgumentException("The reporting task type must be specified.");
+        }
+        
+        try {
+            // create the reporting task
+            final ReportingTaskNode reportingTask = reportingTaskProvider.createReportingTask(reportingTaskDTO.getType(), reportingTaskDTO.getId(), true);
+
+            // ensure we can perform the update 
+            verifyUpdate(reportingTask, reportingTaskDTO);
+
+            // perform the update
+            configureReportingTask(reportingTask, reportingTaskDTO);
+
+            return reportingTask;
+        } catch (ReportingTaskInstantiationException rtie) {
+            throw new NiFiCoreException(rtie.getMessage(), rtie);
+        }
+    }
+
+    /**
+     * Gets the specified reporting task.
+     *
+     * @param reportingTaskId The reporting task id
+     * @return The reporting task
+     */
+    @Override
+    public ReportingTaskNode getReportingTask(final String reportingTaskId) {
+        return locateReportingTask(reportingTaskId);
+    }
+
+    /**
+     * Determines if the specified reporting task exists.
+     *
+     * @param reportingTaskId
+     * @return
+     */
+    @Override
+    public boolean hasReportingTask(final String reportingTaskId) {
+        return reportingTaskProvider.getReportingTaskNode(reportingTaskId) != null;
+    }
+
+    /**
+     * Gets all of the reporting tasks.
+     *
+     * @return The reporting tasks
+     */
+    @Override
+    public Set<ReportingTaskNode> getReportingTasks() {
+        return reportingTaskProvider.getAllReportingTasks();
+    }
+
+    /**
+     * Updates the specified reporting task.
+     *
+     * @param reportingTaskDTO The reporting task DTO
+     * @return The reporting task
+     */
+    @Override
+    public ReportingTaskNode updateReportingTask(final ReportingTaskDTO reportingTaskDTO) {
+        // get the reporting task
+        final ReportingTaskNode reportingTask = locateReportingTask(reportingTaskDTO.getId());
+
+        // ensure we can perform the update 
+        verifyUpdate(reportingTask, reportingTaskDTO);
+
+        // perform the update
+        configureReportingTask(reportingTask, reportingTaskDTO);
+
+        // configure scheduled state
+        // see if an update is necessary
+        if (isNotNull(reportingTaskDTO.getState())) {
+            final ScheduledState purposedScheduledState = ScheduledState.valueOf(reportingTaskDTO.getState());
+
+            // only attempt an action if it is changing
+            if (!purposedScheduledState.equals(reportingTask.getScheduledState())) {
+                try {
+                    // perform the appropriate action
+                    switch (purposedScheduledState) {
+                        case RUNNING:
+                            reportingTaskProvider.startReportingTask(reportingTask);
+                            break;
+                        case STOPPED:
+                            switch (reportingTask.getScheduledState()) {
+                                case RUNNING:
+                                    reportingTaskProvider.stopReportingTask(reportingTask);
+                                    break;
+                                case DISABLED:
+                                    reportingTaskProvider.enableReportingTask(reportingTask);
+                                    break;
+                            }
+                            break;
+                        case DISABLED:
+                            reportingTaskProvider.disableReportingTask(reportingTask);
+                            break;
+                    }
+                } catch (IllegalStateException | ProcessorLifeCycleException ise) {
+                    throw new NiFiCoreException(ise.getMessage(), ise);
+                } catch (RejectedExecutionException ree) {
+                    throw new NiFiCoreException("Unable to schedule all tasks for the specified reporting task.", ree);
+                } catch (NullPointerException npe) {
+                    throw new NiFiCoreException("Unable to update reporting task run state.", npe);
+                } catch (Exception e) {
+                    throw new NiFiCoreException("Unable to update reporting task run state: " + e, e);
+                }
+            }
+        }
+
+        return reportingTask;
+    }
+
+    /**
+     * Validates the specified configuration for the specified reporting task.
+     *
+     * @param reportingTask
+     * @param reportingTaskDTO
+     * @return
+     */
+    private List<String> validateProposedConfiguration(final ReportingTaskNode reportingTask, final ReportingTaskDTO reportingTaskDTO) {
+        final List<String> validationErrors = new ArrayList<>();
+
+        // get the current scheduling strategy
+        SchedulingStrategy schedulingStrategy = reportingTask.getSchedulingStrategy();
+
+        // validate the new scheduling strategy if appropriate
+        if (isNotNull(reportingTaskDTO.getSchedulingStrategy())) {
+            try {
+                // this will be the new scheduling strategy so use it
+                schedulingStrategy = SchedulingStrategy.valueOf(reportingTaskDTO.getSchedulingStrategy());
+            } catch (IllegalArgumentException iae) {
+                validationErrors.add(String.format("Scheduling strategy: Value must be one of [%s]", StringUtils.join(SchedulingStrategy.values(), ", ")));
+            }
+        }
+
+        // validate the scheduling period based on the scheduling strategy
+        if (isNotNull(reportingTaskDTO.getSchedulingPeriod())) {
+            switch (schedulingStrategy) {
+                case TIMER_DRIVEN:
+                    final Matcher schedulingMatcher = FormatUtils.TIME_DURATION_PATTERN.matcher(reportingTaskDTO.getSchedulingPeriod());
+                    if (!schedulingMatcher.matches()) {
+                        validationErrors.add("Scheduling period is not a valid time duration (ie 30 sec, 5 min)");
+                    }
+                    break;
+                case CRON_DRIVEN:
+                    try {
+                        new CronExpression(reportingTaskDTO.getSchedulingPeriod());
+                    } catch (final ParseException pe) {
+                        throw new IllegalArgumentException(String.format("Scheduling Period '%s' is not a valid cron expression: %s", reportingTaskDTO.getSchedulingPeriod(), pe.getMessage()));
+                    } catch (final Exception e) {
+                        throw new IllegalArgumentException("Scheduling Period is not a valid cron expression: " + reportingTaskDTO.getSchedulingPeriod());
+                    }
+                    break;
+            }
+        }
+
+        return validationErrors;
+    }
+
+    @Override
+    public void verifyDelete(final String reportingTaskId) {
+        final ReportingTaskNode reportingTask = locateReportingTask(reportingTaskId);
+        reportingTask.verifyCanDelete();
+    }
+
+    @Override
+    public void verifyUpdate(final ReportingTaskDTO reportingTaskDTO) {
+        final ReportingTaskNode reportingTask = locateReportingTask(reportingTaskDTO.getId());
+        verifyUpdate(reportingTask, reportingTaskDTO);
+    }
+
+    /**
+     * Verifies the reporting task can be updated.
+     *
+     * @param reportingTask
+     * @param reportingTaskDTO
+     */
+    private void verifyUpdate(final ReportingTaskNode reportingTask, final ReportingTaskDTO reportingTaskDTO) {
+        // ensure the state, if specified, is valid
+        if (isNotNull(reportingTaskDTO.getState())) {
+            try {
+                final ScheduledState purposedScheduledState = ScheduledState.valueOf(reportingTaskDTO.getState());
+
+                // only attempt an action if it is changing
+                if (!purposedScheduledState.equals(reportingTask.getScheduledState())) {
+                    // perform the appropriate action
+                    switch (purposedScheduledState) {
+                        case RUNNING:
+                            reportingTask.verifyCanStart();
+                            break;
+                        case STOPPED:
+                            switch (reportingTask.getScheduledState()) {
+                                case RUNNING:
+                                    reportingTask.verifyCanStop();
+                                    break;
+                                case DISABLED:
+                                    reportingTask.verifyCanEnable();
+                                    break;
+                            }
+                            break;
+                        case DISABLED:
+                            reportingTask.verifyCanDisable();
+                            break;
+                    }
+                }
+            } catch (IllegalArgumentException iae) {
+                throw new IllegalArgumentException(String.format(
+                        "The specified reporting task state (%s) is not valid. Valid options are 'RUNNING', 'STOPPED', and 'DISABLED'.",
+                        reportingTaskDTO.getState()));
+            }
+        }
+
+        boolean modificationRequest = false;
+        if (isAnyNotNull(reportingTaskDTO.getName(),
+                reportingTaskDTO.getSchedulingStrategy(),
+                reportingTaskDTO.getSchedulingPeriod(),
+                reportingTaskDTO.getAnnotationData(),
+                reportingTaskDTO.getProperties())) {
+            modificationRequest = true;
+
+            // validate the request
+            final List<String> requestValidation = validateProposedConfiguration(reportingTask, reportingTaskDTO);
+
+            // ensure there was no validation errors
+            if (!requestValidation.isEmpty()) {
+                throw new ValidationException(requestValidation);
+            }
+        }
+
+        if (modificationRequest) {
+            reportingTask.verifyCanUpdate();
+        }
+    }
+
+    /**
+     * Configures the specified reporting task.
+     *
+     * @param reportingTask
+     * @param reportingTaskDTO
+     */
+    private void configureReportingTask(final ReportingTaskNode reportingTask, final ReportingTaskDTO reportingTaskDTO) {
+        final String name = reportingTaskDTO.getName();
+        final String schedulingStrategy = reportingTaskDTO.getSchedulingStrategy();
+        final String schedulingPeriod = reportingTaskDTO.getSchedulingPeriod();
+        final String annotationData = reportingTaskDTO.getAnnotationData();
+        final String comments = reportingTaskDTO.getComments();
+        final Map<String, String> properties = reportingTaskDTO.getProperties();
+
+        // ensure scheduling strategy is set first
+        if (isNotNull(schedulingStrategy)) {
+            reportingTask.setSchedulingStrategy(SchedulingStrategy.valueOf(schedulingStrategy));
+        }
+        
+        if (isNotNull(name)) {
+            reportingTask.setName(name);
+        }
+        if (isNotNull(schedulingPeriod)) {
+            reportingTask.setScheduldingPeriod(schedulingPeriod);
+        }
+        if (isNotNull(annotationData)) {
+            reportingTask.setAnnotationData(annotationData);
+        }
+        if (isNotNull(comments)) {
+            reportingTask.setComments(comments);
+        }
+        if (isNotNull(properties)) {
+            for (final Map.Entry<String, String> entry : properties.entrySet()) {
+                final String propName = entry.getKey();
+                final String propVal = entry.getValue();
+                if (isNotNull(propName) && propVal == null) {
+                    reportingTask.removeProperty(propName);
+                } else if (isNotNull(propName)) {
+                    reportingTask.setProperty(propName, propVal);
+                }
+            }
+        }
+    }
+
+    /**
+     * Deletes the specified reporting task.
+     *
+     * @param reportingTaskId The reporting task id
+     */
+    @Override
+    public void deleteReportingTask(String reportingTaskId) {
+        final ReportingTaskNode reportingTask = locateReportingTask(reportingTaskId);
+        reportingTaskProvider.removeReportingTask(reportingTask);
+    }
+
+    /* setters */
+    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/dao/impl/StandardSnippetDAO.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/impl/StandardSnippetDAO.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardSnippetDAO.java
index 92e3a8d..6447464 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardSnippetDAO.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardSnippetDAO.java
@@ -26,9 +26,11 @@ import org.apache.nifi.controller.ProcessorNode;
 import org.apache.nifi.controller.Snippet;
 import org.apache.nifi.controller.StandardSnippet;
 import org.apache.nifi.controller.exception.ProcessorInstantiationException;
+import org.apache.nifi.controller.service.ControllerServiceNode;
 import org.apache.nifi.groups.ProcessGroup;
 import org.apache.nifi.web.NiFiCoreException;
 import org.apache.nifi.web.ResourceNotFoundException;
+import org.apache.nifi.web.api.dto.ControllerServiceDTO;
 import org.apache.nifi.web.api.dto.FlowSnippetDTO;
 import org.apache.nifi.web.api.dto.ProcessGroupDTO;
 import org.apache.nifi.web.api.dto.ProcessorConfigDTO;
@@ -36,7 +38,6 @@ import org.apache.nifi.web.api.dto.ProcessorDTO;
 import org.apache.nifi.web.api.dto.SnippetDTO;
 import org.apache.nifi.web.dao.SnippetDAO;
 import org.apache.nifi.web.util.SnippetUtils;
-
 import org.apache.commons.lang3.StringUtils;
 
 /**
@@ -285,9 +286,13 @@ public class StandardSnippetDAO implements SnippetDAO {
         if (snippet != null) {
             // go through each processor if specified
             if (snippet.getProcessors() != null) {
-                lookupSensitiveProperties(snippet.getProcessors());
+                lookupSensitiveProcessorProperties(snippet.getProcessors());
             }
 
+            if ( snippet.getControllerServices() != null ) {
+                lookupSensitiveControllerServiceProperties(snippet.getControllerServices());
+            }
+            
             // go through each process group if specified
             if (snippet.getProcessGroups() != null) {
                 for (final ProcessGroupDTO group : snippet.getProcessGroups()) {
@@ -303,7 +308,7 @@ public class StandardSnippetDAO implements SnippetDAO {
      *
      * @param snippet
      */
-    private void lookupSensitiveProperties(final Set<ProcessorDTO> processors) {
+    private void lookupSensitiveProcessorProperties(final Set<ProcessorDTO> processors) {
         final ProcessGroup rootGroup = flowController.getGroup(flowController.getRootGroupId());
 
         // go through each processor
@@ -331,6 +336,31 @@ public class StandardSnippetDAO implements SnippetDAO {
             }
         }
     }
+    
+    private void lookupSensitiveControllerServiceProperties(final Set<ControllerServiceDTO> controllerServices) {
+        // go through each service
+        for (final ControllerServiceDTO serviceDTO : controllerServices) {
+            
+            // ensure that some property configuration have been specified
+            final Map<String, String> serviceProperties = serviceDTO.getProperties();
+            if (serviceProperties != null) {
+                // find the corresponding controller service
+                final ControllerServiceNode serviceNode = flowController.getControllerServiceNode(serviceDTO.getId());
+                if (serviceNode == null) {
+                    throw new IllegalArgumentException(String.format("Unable to create snippet because Controller Service '%s' could not be found", serviceDTO.getId()));
+                }
+
+                // look for sensitive properties get the actual value
+                for (Entry<PropertyDescriptor, String> entry : serviceNode.getProperties().entrySet()) {
+                    final PropertyDescriptor descriptor = entry.getKey();
+
+                    if (descriptor.isSensitive()) {
+                        serviceProperties.put(descriptor.getName(), entry.getValue());
+                    }
+                }
+            }
+        }
+    }
 
     /* setters */
     public void setFlowController(FlowController flowController) {

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/spring/ControllerServiceProviderFactoryBean.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/spring/ControllerServiceProviderFactoryBean.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/spring/ControllerServiceProviderFactoryBean.java
new file mode 100644
index 0000000..5c10de6
--- /dev/null
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/spring/ControllerServiceProviderFactoryBean.java
@@ -0,0 +1,68 @@
+/*
+ * 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.spring;
+
+import org.apache.nifi.cluster.manager.impl.WebClusterManager;
+import org.apache.nifi.controller.FlowController;
+import org.apache.nifi.controller.service.ControllerServiceProvider;
+import org.apache.nifi.util.NiFiProperties;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+/**
+ *
+ */
+public class ControllerServiceProviderFactoryBean implements FactoryBean, ApplicationContextAware {
+
+    private ApplicationContext context;
+    private ControllerServiceProvider controllerServiceProvider;
+    private NiFiProperties properties;
+
+    @Override
+    public Object getObject() throws Exception {
+        if (controllerServiceProvider == null) {
+            if (properties.isClusterManager()) {
+                controllerServiceProvider = context.getBean("clusterManager", WebClusterManager.class);
+            } else {
+                controllerServiceProvider = context.getBean("flowController", FlowController.class);
+            }
+        }
+
+        return controllerServiceProvider;
+    }
+
+    @Override
+    public Class getObjectType() {
+        return ControllerServiceProvider.class;
+    }
+
+    @Override
+    public boolean isSingleton() {
+        return true;
+    }
+
+    public void setProperties(NiFiProperties properties) {
+        this.properties = properties;
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext context) throws BeansException {
+        this.context = context;
+    }
+}

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/spring/OptimisticLockingManagerFactoryBean.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/spring/OptimisticLockingManagerFactoryBean.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/spring/OptimisticLockingManagerFactoryBean.java
new file mode 100644
index 0000000..8436793
--- /dev/null
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/spring/OptimisticLockingManagerFactoryBean.java
@@ -0,0 +1,67 @@
+/*
+ * 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.spring;
+
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.OptimisticLockingManager;
+import org.apache.nifi.web.StandardOptimisticLockingManager;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+/**
+ *
+ */
+public class OptimisticLockingManagerFactoryBean implements FactoryBean, ApplicationContextAware {
+
+    private ApplicationContext context;
+    private OptimisticLockingManager optimisticLockingManager;
+    private NiFiProperties properties;
+
+    @Override
+    public Object getObject() throws Exception {
+        if (optimisticLockingManager == null) {
+            if (properties.isClusterManager()) {
+                optimisticLockingManager = context.getBean("clusterManagerOptimisticLockingManager", OptimisticLockingManager.class);
+            } else {
+                optimisticLockingManager = new StandardOptimisticLockingManager();
+            }
+        }
+
+        return optimisticLockingManager;
+    }
+
+    @Override
+    public Class getObjectType() {
+        return OptimisticLockingManager.class;
+    }
+
+    @Override
+    public boolean isSingleton() {
+        return true;
+    }
+
+    public void setProperties(NiFiProperties properties) {
+        this.properties = properties;
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext context) throws BeansException {
+        this.context = context;
+    }
+}

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/spring/ReportingTaskProviderFactoryBean.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/spring/ReportingTaskProviderFactoryBean.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/spring/ReportingTaskProviderFactoryBean.java
new file mode 100644
index 0000000..d344fa6
--- /dev/null
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/spring/ReportingTaskProviderFactoryBean.java
@@ -0,0 +1,69 @@
+/*
+ * 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.spring;
+
+import org.apache.nifi.cluster.manager.impl.WebClusterManager;
+import org.apache.nifi.controller.FlowController;
+import org.apache.nifi.controller.reporting.ReportingTaskProvider;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.dao.ControllerServiceDAO;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+/**
+ *
+ */
+public class ReportingTaskProviderFactoryBean implements FactoryBean, ApplicationContextAware {
+
+    private ApplicationContext context;
+    private ReportingTaskProvider reportingTaskProvider;
+    private NiFiProperties properties;
+
+    @Override
+    public Object getObject() throws Exception {
+        if (reportingTaskProvider == null) {
+            if (properties.isClusterManager()) {
+                reportingTaskProvider = context.getBean("clusterManager", WebClusterManager.class);
+            } else {
+                reportingTaskProvider = context.getBean("flowController", FlowController.class);
+            }
+        }
+
+        return reportingTaskProvider;
+    }
+
+    @Override
+    public Class getObjectType() {
+        return ReportingTaskProvider.class;
+    }
+
+    @Override
+    public boolean isSingleton() {
+        return true;
+    }
+
+    public void setProperties(NiFiProperties properties) {
+        this.properties = properties;
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext context) throws BeansException {
+        this.context = context;
+    }
+}

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/util/Availability.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/util/Availability.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/util/Availability.java
new file mode 100644
index 0000000..29ba4f8
--- /dev/null
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/util/Availability.java
@@ -0,0 +1,34 @@
+/*
+ * 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.util;
+
+/**
+ * Where a given controller service or reporting task should run.
+ */
+public enum Availability {
+
+    /**
+     * Service or reporting task will run only on the NiFi Cluster Manager (NCM)
+     */
+    NCM,
+    
+    /**
+     * Service or reporting task will run only on NiFi Nodes (or standalone
+     * instance, if not clustered)
+     */
+    NODE;
+}

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/util/SnippetUtils.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/util/SnippetUtils.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/util/SnippetUtils.java
index 8653094..fa9bc41 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/util/SnippetUtils.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/util/SnippetUtils.java
@@ -18,6 +18,7 @@ package org.apache.nifi.web.util;
 
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
@@ -28,6 +29,7 @@ import java.util.UUID;
 
 import org.apache.nifi.cluster.context.ClusterContext;
 import org.apache.nifi.cluster.context.ClusterContextThreadLocal;
+import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.connectable.ConnectableType;
 import org.apache.nifi.connectable.Connection;
 import org.apache.nifi.connectable.Funnel;
@@ -37,17 +39,22 @@ import org.apache.nifi.controller.ProcessorNode;
 import org.apache.nifi.controller.ScheduledState;
 import org.apache.nifi.controller.Snippet;
 import org.apache.nifi.controller.label.Label;
+import org.apache.nifi.controller.service.ControllerServiceNode;
+import org.apache.nifi.controller.service.ControllerServiceState;
 import org.apache.nifi.groups.ProcessGroup;
 import org.apache.nifi.groups.RemoteProcessGroup;
 import org.apache.nifi.web.api.dto.ConnectableDTO;
 import org.apache.nifi.web.api.dto.ConnectionDTO;
+import org.apache.nifi.web.api.dto.ControllerServiceDTO;
 import org.apache.nifi.web.api.dto.DtoFactory;
 import org.apache.nifi.web.api.dto.FlowSnippetDTO;
 import org.apache.nifi.web.api.dto.FunnelDTO;
 import org.apache.nifi.web.api.dto.LabelDTO;
 import org.apache.nifi.web.api.dto.PortDTO;
 import org.apache.nifi.web.api.dto.ProcessGroupDTO;
+import org.apache.nifi.web.api.dto.ProcessorConfigDTO;
 import org.apache.nifi.web.api.dto.ProcessorDTO;
+import org.apache.nifi.web.api.dto.PropertyDescriptorDTO;
 import org.apache.nifi.web.api.dto.RemoteProcessGroupContentsDTO;
 import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO;
 import org.apache.nifi.web.api.dto.RemoteProcessGroupPortDTO;
@@ -181,11 +188,100 @@ public final class SnippetUtils {
             snippetDto.setRemoteProcessGroups(remoteProcessGroups);
         }
 
+        addControllerServicesToSnippet(snippetDto);
+        
         return snippetDto;
     }
+    
+    private void addControllerServicesToSnippet(final FlowSnippetDTO snippetDto) {
+        for ( final ProcessorDTO processorDto : snippetDto.getProcessors() ) {
+            addControllerServicesToSnippet(snippetDto, processorDto);
+        }
+        
+        for ( final ProcessGroupDTO processGroupDto : snippetDto.getProcessGroups() ) {
+            final FlowSnippetDTO childGroupDto = processGroupDto.getContents();
+            addControllerServicesToSnippet(childGroupDto);
+        }
+    }
+    
+    private void addControllerServicesToSnippet(final FlowSnippetDTO snippet, final ProcessorDTO processorDto) {
+        final ProcessorConfigDTO configDto = processorDto.getConfig();
+        if ( configDto == null ) {
+            return;
+        }
+        
+        final Map<String, PropertyDescriptorDTO> descriptors = configDto.getDescriptors();
+        final Map<String, String> properties = configDto.getProperties();
+        
+        if ( properties != null && descriptors != null ) {
+            for ( final Map.Entry<String, String> entry : properties.entrySet() ) {
+                final String propName = entry.getKey();
+                final String propValue = entry.getValue();
+                if ( propValue == null ) {
+                    continue;
+                }
+                
+                final PropertyDescriptorDTO propertyDescriptorDto = descriptors.get(propName);
+                if ( propertyDescriptorDto != null && propertyDescriptorDto.isIdentifiesControllerService() ) {
+                    final ControllerServiceNode serviceNode = flowController.getControllerServiceNode(propValue);
+                    if ( serviceNode != null ) {
+                        addControllerServicesToSnippet(snippet, serviceNode);
+                    }
+                }
+            }
+        }
+    }
+    
+    private void addControllerServicesToSnippet(final FlowSnippetDTO snippet, final ControllerServiceNode serviceNode) {
+        if ( isServicePresent(serviceNode.getIdentifier(), snippet.getControllerServices()) ) {
+            return;
+        }
+        
+        final ControllerServiceDTO serviceNodeDto = dtoFactory.createControllerServiceDto(serviceNode);
+        Set<ControllerServiceDTO> existingServiceDtos = snippet.getControllerServices();
+        if ( existingServiceDtos == null ) {
+            existingServiceDtos = new HashSet<>();
+            snippet.setControllerServices(existingServiceDtos);
+        }
+        existingServiceDtos.add(serviceNodeDto);
+
+        for ( final Map.Entry<PropertyDescriptor, String> entry : serviceNode.getProperties().entrySet() ) {
+            final PropertyDescriptor descriptor = entry.getKey();
+            final String propertyValue = entry.getValue();
+            
+            if ( descriptor.getControllerServiceDefinition() != null ) {
+                final ControllerServiceNode referencedNode = flowController.getControllerServiceNode(propertyValue);
+                if ( referencedNode == null ) {
+                    throw new IllegalStateException("Controller Service with ID " + propertyValue + " is referenced in template but cannot be found");
+                }
+                
+                final String referencedNodeId = referencedNode.getIdentifier();
+                
+                final boolean alreadyPresent = isServicePresent(referencedNodeId, snippet.getControllerServices());
+                if ( !alreadyPresent ) {
+                    addControllerServicesToSnippet(snippet, referencedNode);
+                }
+            }
+        }
+    }
 
+    private boolean isServicePresent(final String serviceId, final Collection<ControllerServiceDTO> services) {
+        if ( services == null ) {
+            return false;
+        }
+        
+        for ( final ControllerServiceDTO existingService : services ) {
+            if ( serviceId.equals(existingService.getId()) ) {
+                return true;
+            }
+        }
+        
+        return false;
+    }
+    
+    
     public FlowSnippetDTO copy(final FlowSnippetDTO snippetContents, final ProcessGroup group) {
-        final FlowSnippetDTO snippetCopy = copyContentsForGroup(snippetContents, group.getIdentifier(), null);
+        final FlowSnippetDTO snippetCopy = copyContentsForGroup(snippetContents, group.getIdentifier(), null, null);
         resolveNameConflicts(snippetCopy, group);
         return snippetCopy;
     }
@@ -240,10 +336,49 @@ public final class SnippetUtils {
         }
     }
 
-    private FlowSnippetDTO copyContentsForGroup(final FlowSnippetDTO snippetContents, final String groupId, final Map<String, ConnectableDTO> parentConnectableMap) {
+    private FlowSnippetDTO copyContentsForGroup(final FlowSnippetDTO snippetContents, final String groupId, final Map<String, ConnectableDTO> parentConnectableMap, Map<String, String> serviceIdMap) {
         final FlowSnippetDTO snippetContentsCopy = new FlowSnippetDTO();
 
         //
+        // Copy the Controller Services
+        //
+        if ( serviceIdMap == null ) {
+            serviceIdMap = new HashMap<>();
+            final Set<ControllerServiceDTO> services = new HashSet<>();
+            if ( snippetContents.getControllerServices() != null ) {
+                for (final ControllerServiceDTO serviceDTO : snippetContents.getControllerServices() ) {
+                    final ControllerServiceDTO service = dtoFactory.copy(serviceDTO);
+                    service.setId(generateId(serviceDTO.getId()));
+                    service.setState(ControllerServiceState.DISABLED.name());
+                    services.add(service);
+                    
+                    // Map old service ID to new service ID so that we can make sure that we reference the new ones.
+                    serviceIdMap.put(serviceDTO.getId(), service.getId());
+                }
+            }
+            
+            // if there is any controller service that maps to another controller service, update the id's
+            for ( final ControllerServiceDTO serviceDTO : services ) {
+                final Map<String, String> properties = serviceDTO.getProperties();
+                final Map<String, PropertyDescriptorDTO> descriptors = serviceDTO.getDescriptors();
+                if ( properties != null && descriptors != null ) {
+                    for ( final PropertyDescriptorDTO descriptor : descriptors.values() ) {
+                        if ( descriptor.isIdentifiesControllerService() ) {
+                            final String currentServiceId = properties.get(descriptor.getName());
+                            if ( currentServiceId == null ) {
+                                continue;
+                            }
+                            
+                            final String newServiceId = serviceIdMap.get(currentServiceId);
+                            properties.put(descriptor.getName(), newServiceId);
+                        }
+                    }
+                }
+            }
+            snippetContentsCopy.setControllerServices(services);
+        }
+        
+        //
         // Copy the labels
         //
         final Set<LabelDTO> labels = new HashSet<>();
@@ -332,6 +467,9 @@ public final class SnippetUtils {
         }
         snippetContentsCopy.setProcessors(processors);
 
+        // if there is any controller service that maps to another controller service, update the id's
+        updateControllerServiceIdentifiers(snippetContentsCopy, serviceIdMap);
+        
         // 
         // Copy ProcessGroups
         //
@@ -344,7 +482,7 @@ public final class SnippetUtils {
                 cp.setParentGroupId(groupId);
 
                 // copy the contents of this group - we do not copy via the dto factory since we want to specify new ids
-                final FlowSnippetDTO contentsCopy = copyContentsForGroup(groupDTO.getContents(), cp.getId(), connectableMap);
+                final FlowSnippetDTO contentsCopy = copyContentsForGroup(groupDTO.getContents(), cp.getId(), connectableMap, serviceIdMap);
                 cp.setContents(contentsCopy);
                 groups.add(cp);
             }
@@ -396,6 +534,43 @@ public final class SnippetUtils {
 
         return snippetContentsCopy;
     }
+    
+    
+    private void updateControllerServiceIdentifiers(final FlowSnippetDTO snippet, final Map<String, String> serviceIdMap) {
+        final Set<ProcessorDTO> processors = snippet.getProcessors();
+        if ( processors != null ) {
+            for ( final ProcessorDTO processor : processors ) {
+                updateControllerServiceIdentifiers(processor.getConfig(), serviceIdMap);
+            }
+        }
+        
+        for ( final ProcessGroupDTO processGroupDto : snippet.getProcessGroups() ) {
+            updateControllerServiceIdentifiers(processGroupDto.getContents(), serviceIdMap);
+        }
+    }
+    
+    private void updateControllerServiceIdentifiers(final ProcessorConfigDTO configDto, final Map<String, String> serviceIdMap) {
+        if ( configDto == null ) {
+            return;
+        }
+        
+        final Map<String, String> properties = configDto.getProperties();
+        final Map<String, PropertyDescriptorDTO> descriptors = configDto.getDescriptors();
+        if ( properties != null && descriptors != null ) {
+            for ( final PropertyDescriptorDTO descriptor : descriptors.values() ) {
+                if ( descriptor.isIdentifiesControllerService() ) {
+                    final String currentServiceId = properties.get(descriptor.getName());
+                    if ( currentServiceId == null ) {
+                        continue;
+                    }
+                    
+                    final String newServiceId = serviceIdMap.get(currentServiceId);
+                    properties.put(descriptor.getName(), newServiceId);
+                }
+            }
+        }
+    }
+    
 
     /**
      * Generates a new id for the current id that is specified. If no seed is

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/resources/nifi-web-api-context.xml
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
index a822442..bf4f245 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
@@ -24,11 +24,17 @@
     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
     http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">
 
+    <!-- controller service / reporting task -->
+    <bean id="controllerServiceProvider" class="org.apache.nifi.web.spring.ControllerServiceProviderFactoryBean" depends-on="clusterManager flowController">
+        <property name="properties" ref="nifiProperties"/>
+    </bean>
+    <bean id="reportingTaskProvider" class="org.apache.nifi.web.spring.ReportingTaskProviderFactoryBean" depends-on="clusterManager flowController">
+        <property name="properties" ref="nifiProperties"/>
+    </bean>
+    
     <!-- optimistic locking manager -->
-    <bean id="optimisticLockingManager" class="org.apache.nifi.web.ClusterAwareOptimisticLockingManager">
-        <constructor-arg>
-            <bean class="org.apache.nifi.web.StandardOptimisticLockingManager" />
-        </constructor-arg>
+    <bean id="webOptimisticLockingManager" class="org.apache.nifi.web.spring.OptimisticLockingManagerFactoryBean" depends-on="clusterManagerOptimisticLockingManager">
+        <property name="properties" ref="nifiProperties"/>
     </bean>
     
     <!-- content access -->
@@ -40,8 +46,7 @@
 
     <!-- dto factory -->
     <bean id="dtoFactory" class="org.apache.nifi.web.api.dto.DtoFactory">
-        <property name="properties" ref="nifiProperties"/>
-        <property name="controllerServiceLookup" ref="flowController" />
+        <property name="controllerServiceLookup" ref="controllerServiceProvider" />
     </bean>
 
     <!-- snippet utils -->
@@ -75,6 +80,12 @@
     <bean id="processorDAO" class="org.apache.nifi.web.dao.impl.StandardProcessorDAO">
         <property name="flowController" ref="flowController"/>
     </bean>
+    <bean id="controllerServiceDAO" class="org.apache.nifi.web.dao.impl.StandardControllerServiceDAO">
+        <property name="serviceProvider" ref="controllerServiceProvider"/>
+    </bean>
+    <bean id="reportingTaskDAO" class="org.apache.nifi.web.dao.impl.StandardReportingTaskDAO">
+        <property name="reportingTaskProvider" ref="reportingTaskProvider"/>
+    </bean>
     <bean id="templateDAO" class="org.apache.nifi.web.dao.impl.StandardTemplateDAO">
         <property name="flowController" ref="flowController"/>
         <property name="snippetUtils" ref="snippetUtils"/>
@@ -101,22 +112,35 @@
         <property name="labelDAO" ref="labelDAO"/>
         <property name="funnelDAO" ref="funnelDAO"/>
         <property name="connectionDAO" ref="connectionDAO"/>
+        <property name="controllerServiceDAO" ref="controllerServiceDAO"/>
+        <property name="reportingTaskDAO" ref="reportingTaskDAO"/>
         <property name="templateDAO" ref="templateDAO"/>
         <property name="snippetDAO" ref="snippetDAO"/>
         <property name="auditService" ref="auditService"/>
         <property name="userService" ref="userService"/>
         <property name="snippetUtils" ref="snippetUtils"/>
-        <property name="optimisticLockingManager" ref="optimisticLockingManager"/>
+        <property name="optimisticLockingManager" ref="webOptimisticLockingManager"/>
         <property name="dtoFactory" ref="dtoFactory"/>
         <property name="clusterManager" ref="clusterManager"/>
     </bean>
 
+    <!-- depecrated -->
     <bean id="nifiWebContext" class="org.apache.nifi.web.StandardNiFiWebContext">
         <property name="serviceFacade" ref="serviceFacade"/>
-        <property name="controllerFacade" ref="controllerFacade"/>
         <property name="properties" ref="nifiProperties"/>
         <property name="clusterManager" ref="clusterManager"/>
         <property name="auditService" ref="auditService"/>
+        <property name="controllerServiceLookup" ref="controllerServiceProvider"/>
+    </bean>
+    
+    <!-- component ui extension configuration context -->
+    <bean id="nifiWebConfigurationContext" class="org.apache.nifi.web.StandardNiFiWebConfigurationContext">
+        <property name="serviceFacade" ref="serviceFacade"/>
+        <property name="properties" ref="nifiProperties"/>
+        <property name="clusterManager" ref="clusterManager"/>
+        <property name="auditService" ref="auditService"/>
+        <property name="controllerServiceLookup" ref="controllerServiceProvider"/>
+        <property name="reportingTaskProvider" ref="reportingTaskProvider"/>
     </bean>
 
     <!-- rest endpoints -->
@@ -133,6 +157,16 @@
         <property name="properties" ref="nifiProperties"/>
         <property name="clusterManager" ref="clusterManager"/>
     </bean>
+    <bean id="controllerServiceResource" class="org.apache.nifi.web.api.ControllerServiceResource" scope="singleton">
+        <property name="serviceFacade" ref="serviceFacade"/>
+        <property name="properties" ref="nifiProperties"/>
+        <property name="clusterManager" ref="clusterManager"/>
+    </bean>
+    <bean id="reportingTaskResource" class="org.apache.nifi.web.api.ReportingTaskResource" scope="singleton">
+        <property name="serviceFacade" ref="serviceFacade"/>
+        <property name="properties" ref="nifiProperties"/>
+        <property name="clusterManager" ref="clusterManager"/>
+    </bean>
     <bean id="processGroupResource" class="org.apache.nifi.web.api.ProcessGroupResource" scope="prototype">
         <property name="serviceFacade" ref="serviceFacade"/>
         <property name="properties" ref="nifiProperties"/>
@@ -303,6 +337,16 @@
         <property name="processorAuditor" ref="processorAuditor"/>
         <property name="relationshipAuditor" ref="relationshipAuditor"/>
     </bean>
+    <bean id="controllerServiceAuditor" class="org.apache.nifi.audit.ControllerServiceAuditor">
+        <property name="serviceFacade" ref="serviceFacade"/>
+        <property name="auditService" ref="auditService"/>
+        <property name="processGroupDAO" ref="processGroupDAO"/>
+    </bean>
+    <bean id="reportingTaskAuditor" class="org.apache.nifi.audit.ReportingTaskAuditor">
+        <property name="serviceFacade" ref="serviceFacade"/>
+        <property name="auditService" ref="auditService"/>
+        <property name="processGroupDAO" ref="processGroupDAO"/>
+    </bean>
     
     <!-- NiFi locking -->
     <bean id="serviceFacadeLock" class="org.apache.nifi.web.NiFiServiceFacadeLock"/>

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/test/java/org/apache/nifi/integration/util/NiFiTestServer.java
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestServer.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestServer.java
index dd9bb73..1f28609 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestServer.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestServer.java
@@ -18,12 +18,14 @@ package org.apache.nifi.integration.util;
 
 import com.sun.jersey.api.client.Client;
 import java.io.File;
+import java.util.Collections;
 import javax.servlet.ServletContext;
 import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.web.util.WebUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.framework.security.util.SslContextFactory;
 import org.apache.nifi.services.FlowService;
+import org.apache.nifi.ui.extension.UiExtensionMapping;
 import org.eclipse.jetty.server.HttpConfiguration;
 import org.eclipse.jetty.server.HttpConnectionFactory;
 import org.eclipse.jetty.server.SecureRequestCustomizer;
@@ -160,6 +162,9 @@ public class NiFiTestServer {
      */
     public void startServer() throws Exception {
         jetty.start();
+        
+        // ensure the ui extensions are set
+        webappContext.getServletContext().setAttribute("nifi-ui-extensions", new UiExtensionMapping(Collections.EMPTY_MAP));
     }
 
     /**

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-optimistic-locking/pom.xml
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/pom.xml b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/pom.xml
index e9781f8..a9f0c3e 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/pom.xml
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/pom.xml
@@ -27,5 +27,17 @@
             <groupId>org.apache.nifi</groupId>
             <artifactId>nifi-api</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-administration</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-web-security</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-framework-cluster-web</artifactId>
+        </dependency>
     </dependencies>
 </project>

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-optimistic-locking/src/main/java/org/apache/nifi/web/ConfigurationRequest.java
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/src/main/java/org/apache/nifi/web/ConfigurationRequest.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/src/main/java/org/apache/nifi/web/ConfigurationRequest.java
new file mode 100644
index 0000000..939c3f0
--- /dev/null
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/src/main/java/org/apache/nifi/web/ConfigurationRequest.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+/**
+ * Represents a request to configure. The implementations execute method will
+ * perform the configuration action. It will return type T which will be
+ * encapsulated in a ConfigurationSnapshot.
+ * 
+ * @param <T>
+ */
+public interface ConfigurationRequest<T> {
+
+    /**
+     * Executes a configuration action and returns the updated resulting configuration.
+     * 
+     * @return The resulting configuration
+     */
+    T execute();
+}

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-optimistic-locking/src/main/java/org/apache/nifi/web/ConfigurationSnapshot.java
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/src/main/java/org/apache/nifi/web/ConfigurationSnapshot.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/src/main/java/org/apache/nifi/web/ConfigurationSnapshot.java
index 6ad683c..8817d69 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/src/main/java/org/apache/nifi/web/ConfigurationSnapshot.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/src/main/java/org/apache/nifi/web/ConfigurationSnapshot.java
@@ -22,36 +22,36 @@ package org.apache.nifi.web;
  */
 public class ConfigurationSnapshot<T> {
 
-    private Long revision;
+    private Long version;
     private T configuration;
 
     /**
      * Creates a new ConfigurationSnapshot.
      *
-     * @param revision The model revision
+     * @param version The revision version
      */
-    public ConfigurationSnapshot(Long revision) {
-        this(revision, null);
+    public ConfigurationSnapshot(Long version) {
+        this(version, null);
     }
 
     /**
      * Creates a new ConfigurationSnapshot.
      *
-     * @param revision The model revision
+     * @param version The revision version
      * @param configuration The configuration
      */
-    public ConfigurationSnapshot(Long revision, T configuration) {
-        this.revision = revision;
+    public ConfigurationSnapshot(Long version, T configuration) {
+        this.version = version;
         this.configuration = configuration;
     }
 
     /**
-     * Get the new model revision.
+     * Get the revision version.
      *
-     * @return The model revision
+     * @return The revision version
      */
-    public Long getRevision() {
-        return revision;
+    public Long getVersion() {
+        return version;
     }
 
     /**

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-optimistic-locking/src/main/java/org/apache/nifi/web/FlowModification.java
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/src/main/java/org/apache/nifi/web/FlowModification.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/src/main/java/org/apache/nifi/web/FlowModification.java
new file mode 100644
index 0000000..f6bccb1
--- /dev/null
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/src/main/java/org/apache/nifi/web/FlowModification.java
@@ -0,0 +1,57 @@
+/*
+ * 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;
+
+/**
+ * Records a flow modification. This includes the resulting revision and the
+ * user that performed the modification.
+ */
+public class FlowModification {
+
+    private final Revision revision;
+    private final String lastModifier;
+
+    /**
+     * Creates a new FlowModification.
+     * 
+     * @param revision
+     * @param lastModifier 
+     */
+    public FlowModification(Revision revision, String lastModifier) {
+        this.revision = revision;
+        this.lastModifier = lastModifier;
+    }
+
+    /**
+     * Get the revision.
+     * 
+     * @return 
+     */
+    public Revision getRevision() {
+        return revision;
+    }
+
+    /**
+     * Get the last modifier.
+     * 
+     * @return 
+     */
+    public String getLastModifier() {
+        return lastModifier;
+    }
+    
+}

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-optimistic-locking/src/main/java/org/apache/nifi/web/OptimisticLockingManager.java
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/src/main/java/org/apache/nifi/web/OptimisticLockingManager.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/src/main/java/org/apache/nifi/web/OptimisticLockingManager.java
index b045247..4c54b7c 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/src/main/java/org/apache/nifi/web/OptimisticLockingManager.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/src/main/java/org/apache/nifi/web/OptimisticLockingManager.java
@@ -26,70 +26,28 @@ package org.apache.nifi.web;
 public interface OptimisticLockingManager {
 
     /**
-     * Checks the specified revision against the current revision. If the check
-     * succeeds, then the current revision's version is incremented and the
-     * current revision's client ID is set to the given revision's client ID.
-     *
-     * If the given revision's version is null, then the revision's client ID
-     * must match for the current revision's client ID for the check to succeed.
-     *
-     * If the versions and the clientIds do not match, then an
-     * InvalidRevisionException.
-     *
-     * @param revision the revision to check
-     *
-     * @return the current revision
-     *
-     * @throws InvalidRevisionException if the given revision does not match the
-     * current revision
+     * Attempts to execute the specified configuration request using the specified revision within a lock.
+     * 
+     * @param <T>
+     * @param revision
+     * @param configurationRequest
+     * @return 
      */
-    Revision checkRevision(Revision revision) throws InvalidRevisionException;
-
-    /**
-     * Returns true if the given revision matches the current revision.
-     *
-     * @param revision a revision
-     * @return true if given revision is current; false otherwise.
-     */
-    boolean isCurrent(Revision revision);
-
+    <T> ConfigurationSnapshot<T> configureFlow(Revision revision, ConfigurationRequest<T> configurationRequest);
+    
     /**
-     * @return the current revision
+     * Updates the revision using the specified revision within a lock.
+     * 
+     * @param updateRevision 
      */
-    Revision getRevision();
+    void setRevision(UpdateRevision updateRevision);
 
     /**
-     * Sets the current revision.
-     *
-     * @param revision a revision
+     * Returns the last flow modification. This is a combination of the revision and the user
+     * who performed the modification.
+     * 
+     * @return the last modification
      */
-    void setRevision(Revision revision);
+    FlowModification getLastModification();
 
-    /**
-     * Increments the current revision's version.
-     *
-     * @return the current revision
-     */
-    Revision incrementRevision();
-
-    /**
-     * Increments the current revision's version and sets the current revision's
-     * client ID to the given client ID.
-     *
-     * @param clientId a client ID
-     * @return the current revision
-     */
-    Revision incrementRevision(String clientId);
-
-    /**
-     * @return the last modifier.
-     */
-    String getLastModifier();
-
-    /**
-     * Sets the last modifier.
-     *
-     * @param lastModifier the last modifier
-     */
-    void setLastModifier(String lastModifier);
 }