You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by mc...@apache.org on 2015/03/31 23:07:57 UTC

[13/25] incubator-nifi git commit: Squashed commit of the following:

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/e9647717/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.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/ControllerServiceResource.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java
new file mode 100644
index 0000000..1711f3c
--- /dev/null
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java
@@ -0,0 +1,803 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.api;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import org.apache.nifi.cluster.manager.impl.WebClusterManager;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.ConfigurationSnapshot;
+import org.apache.nifi.web.NiFiServiceFacade;
+import org.apache.nifi.web.Revision;
+import org.apache.nifi.web.api.dto.RevisionDTO;
+import org.apache.nifi.web.api.request.ClientIdParameter;
+import org.apache.nifi.web.api.request.LongParameter;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.controller.ScheduledState;
+import org.apache.nifi.controller.service.ControllerServiceState;
+import org.apache.nifi.ui.extension.UiExtension;
+import org.apache.nifi.ui.extension.UiExtensionMapping;
+import org.apache.nifi.web.UiExtensionType;
+import static org.apache.nifi.web.api.ApplicationResource.CLIENT_ID;
+import org.apache.nifi.web.api.dto.ControllerServiceDTO;
+import org.apache.nifi.web.api.dto.ControllerServiceReferencingComponentDTO;
+import org.apache.nifi.web.api.dto.PropertyDescriptorDTO;
+import org.apache.nifi.web.api.entity.ControllerServiceEntity;
+import org.apache.nifi.web.api.entity.ControllerServiceReferencingComponentsEntity;
+import org.apache.nifi.web.api.entity.ControllerServicesEntity;
+import org.apache.nifi.web.api.entity.PropertyDescriptorEntity;
+import org.apache.nifi.web.util.Availability;
+import org.codehaus.enunciate.jaxrs.TypeHint;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.access.prepost.PreAuthorize;
+
+/**
+ * RESTful endpoint for managing a Controller Service.
+ */
+public class ControllerServiceResource extends ApplicationResource {
+
+    private static final Logger logger = LoggerFactory.getLogger(ControllerServiceResource.class);
+
+    private NiFiServiceFacade serviceFacade;
+    private WebClusterManager clusterManager;
+    private NiFiProperties properties;
+
+    @Context
+    private ServletContext servletContext;
+    
+    /**
+     * Populates the uri for the specified controller service.
+     * 
+     * @param controllerServices
+     * @return 
+     */
+    private Set<ControllerServiceDTO> populateRemainingControllerServicesContent(final String availability, final Set<ControllerServiceDTO> controllerServices) {
+        for (ControllerServiceDTO controllerService : controllerServices) {
+            populateRemainingControllerServiceContent(availability, controllerService);
+        }
+        return controllerServices;
+    }
+    
+    /**
+     * Populates the uri for the specified controller service.
+     */
+    private ControllerServiceDTO populateRemainingControllerServiceContent(final String availability, final ControllerServiceDTO controllerService) {
+        // populate the controller service href
+        controllerService.setUri(generateResourceUri("controller", "controller-services", availability, controllerService.getId()));
+        controllerService.setAvailability(availability);
+        
+        // see if this processor has any ui extensions
+        final UiExtensionMapping uiExtensionMapping = (UiExtensionMapping) servletContext.getAttribute("nifi-ui-extensions");
+        if (uiExtensionMapping.hasUiExtension(controllerService.getType())) {
+            final List<UiExtension> uiExtensions = uiExtensionMapping.getUiExtension(controllerService.getType());
+            for (final UiExtension uiExtension : uiExtensions) {
+                if (UiExtensionType.ControllerServiceConfiguration.equals(uiExtension.getExtensionType())) {
+                    controllerService.setCustomUiUrl(uiExtension.getContextPath() + "/configure");
+                }
+            }
+        }
+        
+        return controllerService;
+    }
+
+    /**
+     * Parses the availability and ensure that the specified availability makes sense for the
+     * given NiFi instance.
+     * 
+     * @param availability
+     * @return 
+     */
+    private Availability parseAvailability(final String availability) {
+        final Availability avail;
+        try {
+            avail = Availability.valueOf(availability.toUpperCase());
+        } catch (IllegalArgumentException iae) {
+            throw new IllegalArgumentException(String.format("Availability: Value must be one of [%s]", StringUtils.join(Availability.values(), ", ")));
+        }
+        
+        // ensure this nifi is an NCM is specifying NCM availability
+        if (!properties.isClusterManager() && Availability.NCM.equals(avail)) {
+            throw new IllegalArgumentException("Availability of NCM is only applicable when the NiFi instance is the cluster manager.");
+        }
+        
+        return avail;
+    }
+    
+    /**
+     * Retrieves all the of controller services in this NiFi.
+     *
+     * @param clientId Optional client id. If the client id is not specified, a
+     * new one will be generated. This value (whether specified or generated) is
+     * included in the response.
+     * @param availability Whether the controller service is available on the NCM only (ncm) or on the 
+     * nodes only (node). If this instance is not clustered all services should use the node availability.
+     * @return A controllerServicesEntity.
+     */
+    @GET
+    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Path("/{availability}")
+    @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
+    @TypeHint(ControllerServicesEntity.class)
+    public Response getControllerServices(@QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId, @PathParam("availability") String availability) {
+        final Availability avail = parseAvailability(availability);
+        
+        // replicate if cluster manager
+        if (properties.isClusterManager() && Availability.NODE.equals(avail)) {
+            return clusterManager.applyRequest(HttpMethod.GET, getAbsolutePath(), getRequestParameters(true), getHeaders()).getResponse();
+        }
+
+        // get all the controller services
+        final Set<ControllerServiceDTO> controllerServices = populateRemainingControllerServicesContent(availability, serviceFacade.getControllerServices());
+        
+        // create the revision
+        final RevisionDTO revision = new RevisionDTO();
+        revision.setClientId(clientId.getClientId());
+
+        // create the response entity
+        final ControllerServicesEntity entity = new ControllerServicesEntity();
+        entity.setRevision(revision);
+        entity.setControllerServices(controllerServices);
+
+        // generate the response
+        return clusterContext(generateOkResponse(entity)).build();
+    }
+
+    /**
+     * Creates a new controller service.
+     *
+     * @param httpServletRequest
+     * @param version The revision is used to verify the client is working with
+     * the latest version of the flow.
+     * @param clientId Optional client id. If the client id is not specified, a
+     * new one will be generated. This value (whether specified or generated) is
+     * included in the response.
+     * @param availability Whether the controller service is available on the NCM only (ncm) or on the 
+     * nodes only (node). If this instance is not clustered all services should use the node availability.
+     * @param type The type of controller service to create.
+     * @return A controllerServiceEntity.
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Path("/{availability}")
+    @PreAuthorize("hasRole('ROLE_DFM')")
+    @TypeHint(ControllerServiceEntity.class)
+    public Response createControllerService(
+            @Context HttpServletRequest httpServletRequest,
+            @FormParam(VERSION) LongParameter version,
+            @FormParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId,
+            @PathParam("availability") String availability, 
+            @FormParam("type") String type) {
+        
+        // create the controller service DTO
+        final ControllerServiceDTO controllerServiceDTO = new ControllerServiceDTO();
+        controllerServiceDTO.setType(type);
+
+        // create the revision
+        final RevisionDTO revision = new RevisionDTO();
+        revision.setClientId(clientId.getClientId());
+        if (version != null) {
+            revision.setVersion(version.getLong());
+        }
+
+        // create the controller service entity
+        final ControllerServiceEntity controllerServiceEntity = new ControllerServiceEntity();
+        controllerServiceEntity.setRevision(revision);
+        controllerServiceEntity.setControllerService(controllerServiceDTO);
+
+        return createControllerService(httpServletRequest, availability, controllerServiceEntity);
+    }
+
+    /**
+     * Creates a new Controller Service.
+     *
+     * @param httpServletRequest
+     * @param availability Whether the controller service is available on the NCM only (ncm) or on the 
+     * nodes only (node). If this instance is not clustered all services should use the node availability.
+     * @param controllerServiceEntity A controllerServiceEntity.
+     * @return A controllerServiceEntity.
+     */
+    @POST
+    @Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Path("/{availability}")
+    @PreAuthorize("hasRole('ROLE_DFM')")
+    @TypeHint(ControllerServiceEntity.class)
+    public Response createControllerService(
+            @Context HttpServletRequest httpServletRequest,
+            @PathParam("availability") String availability, 
+            ControllerServiceEntity controllerServiceEntity) {
+        
+        final Availability avail = parseAvailability(availability);
+
+        if (controllerServiceEntity == null || controllerServiceEntity.getControllerService()== null) {
+            throw new IllegalArgumentException("Controller service details must be specified.");
+        }
+
+        if (controllerServiceEntity.getRevision() == null) {
+            throw new IllegalArgumentException("Revision must be specified.");
+        }
+        
+        if (controllerServiceEntity.getControllerService().getId() != null) {
+            throw new IllegalArgumentException("Controller service ID cannot be specified.");
+        }
+
+        if (StringUtils.isBlank(controllerServiceEntity.getControllerService().getType())) {
+            throw new IllegalArgumentException("The type of controller service to create must be specified.");
+        }
+        
+        // get the revision
+        final RevisionDTO revision = controllerServiceEntity.getRevision();
+        
+        // if cluster manager, convert POST to PUT (to maintain same ID across nodes) and replicate
+        if (properties.isClusterManager() && Availability.NODE.equals(avail)) {
+            // create ID for resource
+            final String id = UUID.randomUUID().toString();
+
+            // set ID for resource
+            controllerServiceEntity.getControllerService().setId(id);
+
+            // convert POST request to PUT request to force entity ID to be the same across nodes
+            URI putUri = null;
+            try {
+                putUri = new URI(getAbsolutePath().toString() + "/" + id);
+            } catch (final URISyntaxException e) {
+                throw new WebApplicationException(e);
+            }
+
+            // change content type to JSON for serializing entity
+            final Map<String, String> headersToOverride = new HashMap<>();
+            headersToOverride.put("content-type", MediaType.APPLICATION_JSON);
+
+            // replicate put request
+            return (Response) clusterManager.applyRequest(HttpMethod.PUT, putUri, updateClientId(controllerServiceEntity), getHeaders(headersToOverride)).getResponse();
+        }
+
+        // handle expects request (usually from the cluster manager)
+        final String expects = httpServletRequest.getHeader(WebClusterManager.NCM_EXPECTS_HTTP_HEADER);
+        if (expects != null) {
+            return generateContinueResponse().build();
+        }
+
+        // create the controller service and generate the json
+        final ConfigurationSnapshot<ControllerServiceDTO> controllerResponse = serviceFacade.createControllerService(
+                new Revision(revision.getVersion(), revision.getClientId()), controllerServiceEntity.getControllerService());
+        final ControllerServiceDTO controllerService = controllerResponse.getConfiguration();
+
+        // get the updated revision
+        final RevisionDTO updatedRevision = new RevisionDTO();
+        updatedRevision.setClientId(revision.getClientId());
+        updatedRevision.setVersion(controllerResponse.getVersion());
+
+        // build the response entity
+        final ControllerServiceEntity entity = new ControllerServiceEntity();
+        entity.setRevision(updatedRevision);
+        entity.setControllerService(populateRemainingControllerServiceContent(availability, controllerService));
+
+        // build the response
+        return clusterContext(generateCreatedResponse(URI.create(controllerService.getUri()), entity)).build();
+    }
+
+    /**
+     * Retrieves the specified controller service.
+     *
+     * @param clientId Optional client id. If the client id is not specified, a
+     * new one will be generated. This value (whether specified or generated) is
+     * included in the response.
+     * @param availability Whether the controller service is available on the NCM only (ncm) or on the 
+     * nodes only (node). If this instance is not clustered all services should use the node availability.
+     * @param id The id of the controller service to retrieve
+     * @return A controllerServiceEntity.
+     */
+    @GET
+    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Path("/{availability}/{id}")
+    @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
+    @TypeHint(ControllerServiceEntity.class)
+    public Response getControllerService(@QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId, 
+            @PathParam("availability") String availability, @PathParam("id") String id) {
+
+        final Availability avail = parseAvailability(availability);
+        
+        // replicate if cluster manager
+        if (properties.isClusterManager() && Availability.NODE.equals(avail)) {
+            return clusterManager.applyRequest(HttpMethod.GET, getAbsolutePath(), getRequestParameters(true), getHeaders()).getResponse();
+        }
+
+        // get the controller service
+        final ControllerServiceDTO controllerService = serviceFacade.getControllerService(id);
+
+        // create the revision
+        final RevisionDTO revision = new RevisionDTO();
+        revision.setClientId(clientId.getClientId());
+
+        // create the response entity
+        final ControllerServiceEntity entity = new ControllerServiceEntity();
+        entity.setRevision(revision);
+        entity.setControllerService(populateRemainingControllerServiceContent(availability, controllerService));
+
+        return clusterContext(generateOkResponse(entity)).build();
+    }
+    
+    /**
+     * Returns the descriptor for the specified property.
+     * 
+     * @param clientId Optional client id. If the client id is not specified, a
+     * new one will be generated. This value (whether specified or generated) is
+     * included in the response.
+     * @param availability
+     * @param id The id of the controller service.
+     * @param propertyName The property
+     * @return a propertyDescriptorEntity
+     */
+    @GET
+    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Path("/{availability}/{id}/descriptors")
+    @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
+    @TypeHint(PropertyDescriptorEntity.class)
+    public Response getPropertyDescriptor(
+            @QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId, 
+            @PathParam("availability") String availability, @PathParam("id") String id, 
+            @QueryParam("propertyName") String propertyName) {
+        
+        final Availability avail = parseAvailability(availability);
+        
+        // ensure the property name is specified
+        if (propertyName == null) {
+            throw new IllegalArgumentException("The property name must be specified.");
+        }
+        
+        // replicate if cluster manager and service is on node
+        if (properties.isClusterManager() && Availability.NODE.equals(avail)) {
+            return clusterManager.applyRequest(HttpMethod.GET, getAbsolutePath(), getRequestParameters(true), getHeaders()).getResponse();
+        }
+        
+        // get the property descriptor
+        final PropertyDescriptorDTO descriptor = serviceFacade.getControllerServicePropertyDescriptor(id, propertyName);
+        
+        // create the revision
+        final RevisionDTO revision = new RevisionDTO();
+        revision.setClientId(clientId.getClientId());
+        
+        // generate the response entity
+        final PropertyDescriptorEntity entity = new PropertyDescriptorEntity();
+        entity.setRevision(revision);
+        entity.setPropertyDescriptor(descriptor);
+        
+        // generate the response
+        return clusterContext(generateOkResponse(entity)).build();
+    }
+    
+    /**
+     * Retrieves the references of the specified controller service.
+     *
+     * @param clientId Optional client id. If the client id is not specified, a
+     * new one will be generated. This value (whether specified or generated) is
+     * included in the response.
+     * @param availability Whether the controller service is available on the NCM only (ncm) or on the 
+     * nodes only (node). If this instance is not clustered all services should use the node availability.
+     * @param id The id of the controller service to retrieve
+     * @return A controllerServiceEntity.
+     */
+    @GET
+    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Path("/{availability}/{id}/references")
+    @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
+    @TypeHint(ControllerServiceEntity.class)
+    public Response getControllerServiceReferences(
+            @QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId,
+            @PathParam("availability") String availability, @PathParam("id") String id) {
+
+        final Availability avail = parseAvailability(availability);
+        
+        // replicate if cluster manager
+        if (properties.isClusterManager() && Availability.NODE.equals(avail)) {
+            return clusterManager.applyRequest(HttpMethod.GET, getAbsolutePath(), getRequestParameters(true), getHeaders()).getResponse();
+        }
+
+        // get the controller service
+        final Set<ControllerServiceReferencingComponentDTO> controllerServiceReferences = serviceFacade.getControllerServiceReferencingComponents(id);
+
+        // create the revision
+        final RevisionDTO revision = new RevisionDTO();
+        revision.setClientId(clientId.getClientId());
+
+        // create the response entity
+        final ControllerServiceReferencingComponentsEntity entity = new ControllerServiceReferencingComponentsEntity();
+        entity.setRevision(revision);
+        entity.setControllerServiceReferencingComponents(controllerServiceReferences);
+
+        return clusterContext(generateOkResponse(entity)).build();
+    }
+    
+    /**
+     * Updates the references of the specified controller service.
+     *
+     * @param httpServletRequest
+     * @param version The revision is used to verify the client is working with
+     * the latest version of the flow.
+     * @param clientId Optional client id. If the client id is not specified, a
+     * new one will be generated. This value (whether specified or generated) is
+     * included in the response.
+     * @param availability Whether the controller service is available on the NCM only (ncm) or on the 
+     * nodes only (node). If this instance is not clustered all services should use the node availability.
+     * @param id The id of the controller service to retrieve
+     * @param state Sets the state of referencing components. A value of RUNNING or STOPPED will update
+     * referencing schedulable components (Processors and Reporting Tasks). A value of ENABLED or
+     * DISABLED will update referencing controller services.
+     * @return A controllerServiceEntity.
+     */
+    @PUT
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Path("/{availability}/{id}/references")
+    @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
+    @TypeHint(ControllerServiceEntity.class)
+    public Response updateControllerServiceReferences(
+            @Context HttpServletRequest httpServletRequest,
+            @FormParam(VERSION) LongParameter version,
+            @FormParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId,
+            @PathParam("availability") String availability, @PathParam("id") String id,
+            @FormParam("state") @DefaultValue(StringUtils.EMPTY) String state) {
+
+        // parse the state to determine the desired action
+        
+        // need to consider controller service state first as it shares a state with
+        // scheduled state (disabled) which is applicable for referencing services
+        // but not referencing schedulable components
+        ControllerServiceState controllerServiceState = null;
+        try {
+            controllerServiceState = ControllerServiceState.valueOf(state);
+        } catch (final IllegalArgumentException iae) {
+            // ignore
+        }
+        
+        ScheduledState scheduledState = null;
+        try {
+            scheduledState = ScheduledState.valueOf(state);
+        } catch (final IllegalArgumentException iae) {
+            // ignore
+        }
+        
+        // ensure an action has been specified
+        if (scheduledState == null && controllerServiceState == null) {
+            throw new IllegalArgumentException("Must specify the updated state. To update referencing Processors "
+                    + "and Reporting Tasks the state should be RUNNING or STOPPED. To update the referencing Controller Services the "
+                    + "state should be ENABLED or DISABLED.");
+        }
+        
+        // ensure the controller service state is not ENABLING or DISABLING
+        if (controllerServiceState != null && (ControllerServiceState.ENABLING.equals(controllerServiceState) || ControllerServiceState.DISABLING.equals(controllerServiceState))) {
+            throw new IllegalArgumentException("Cannot set the referencing services to ENABLING or DISABLING");
+        }
+        
+        // determine the availability
+        final Availability avail = parseAvailability(availability);
+        
+        // replicate if cluster manager
+        if (properties.isClusterManager() && Availability.NODE.equals(avail)) {
+            return clusterManager.applyRequest(HttpMethod.PUT, getAbsolutePath(), getRequestParameters(true), getHeaders()).getResponse();
+        }
+
+        // handle expects request (usually from the cluster manager)
+        final String expects = httpServletRequest.getHeader(WebClusterManager.NCM_EXPECTS_HTTP_HEADER);
+        if (expects != null) {
+            serviceFacade.verifyUpdateControllerServiceReferencingComponents(id, scheduledState, controllerServiceState);
+            return generateContinueResponse().build();
+        }
+        
+        // determine the specified version
+        Long clientVersion = null;
+        if (version != null) {
+            clientVersion = version.getLong();
+        }
+        
+        // get the controller service
+        final ConfigurationSnapshot<Set<ControllerServiceReferencingComponentDTO>> response = 
+                serviceFacade.updateControllerServiceReferencingComponents(new Revision(clientVersion, clientId.getClientId()), id, scheduledState, controllerServiceState);
+
+        // create the revision
+        final RevisionDTO revision = new RevisionDTO();
+        revision.setClientId(clientId.getClientId());
+        revision.setVersion(response.getVersion());
+
+        // create the response entity
+        final ControllerServiceReferencingComponentsEntity entity = new ControllerServiceReferencingComponentsEntity();
+        entity.setRevision(revision);
+        entity.setControllerServiceReferencingComponents(response.getConfiguration());
+
+        return clusterContext(generateOkResponse(entity)).build();
+    }
+
+    /**
+     * Updates the specified controller service.
+     *
+     * @param httpServletRequest
+     * @param version The revision is used to verify the client is working with
+     * the latest version of the flow.
+     * @param clientId Optional client id. If the client id is not specified, a
+     * new one will be generated. This value (whether specified or generated) is
+     * included in the response.
+     * @param availability Whether the controller service is available on the NCM only (ncm) or on the 
+     * nodes only (node). If this instance is not clustered all services should use the node availability.
+     * @param id The id of the controller service to update.
+     * @param name The name of the controller service
+     * @param annotationData The annotation data for the controller service
+     * @param comments The comments for the controller service
+     * @param state The state of this controller service. Should be ENABLED or DISABLED.
+     * @param markedForDeletion Array of property names whose value should be removed.
+     * @param formParams Additionally, the processor properties and styles are
+     * specified in the form parameters. Because the property names and styles
+     * differ from processor to processor they are specified in a map-like
+     * fashion:
+     * <br>
+     * <ul>
+     * <li>properties[required.file.path]=/path/to/file</li>
+     * <li>properties[required.hostname]=localhost</li>
+     * <li>properties[required.port]=80</li>
+     * <li>properties[optional.file.path]=/path/to/file</li>
+     * <li>properties[optional.hostname]=localhost</li>
+     * <li>properties[optional.port]=80</li>
+     * <li>properties[user.defined.pattern]=^.*?s.*$</li>
+     * </ul>
+     * @return A controllerServiceEntity.
+     */
+    @PUT
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Path("/{availability}/{id}")
+    @PreAuthorize("hasRole('ROLE_DFM')")
+    @TypeHint(ControllerServiceEntity.class)
+    public Response updateControllerService(
+            @Context HttpServletRequest httpServletRequest,
+            @FormParam(VERSION) LongParameter version,
+            @FormParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId,
+            @PathParam("availability") String availability, @PathParam("id") String id, @FormParam("name") String name,
+            @FormParam("annotationData") String annotationData, @FormParam("comments") String comments,
+            @FormParam("state") String state, @FormParam("markedForDeletion[]") List<String> markedForDeletion,
+            MultivaluedMap<String, String> formParams) {
+
+        // create collections for holding the controller service properties
+        final Map<String, String> updatedProperties = new LinkedHashMap<>();
+        
+        // go through each parameter and look for processor properties
+        for (String parameterName : formParams.keySet()) {
+            if (StringUtils.isNotBlank(parameterName)) {
+                // see if the parameter name starts with an expected parameter type...
+                // if so, store the parameter name and value in the corresponding collection
+                if (parameterName.startsWith("properties")) {
+                    final int startIndex = StringUtils.indexOf(parameterName, "[");
+                    final int endIndex = StringUtils.lastIndexOf(parameterName, "]");
+                    if (startIndex != -1 && endIndex != -1) {
+                        final String propertyName = StringUtils.substring(parameterName, startIndex + 1, endIndex);
+                        updatedProperties.put(propertyName, formParams.getFirst(parameterName));
+                    }
+                }
+            }
+        }
+        
+        // set the properties to remove
+        for (String propertyToDelete : markedForDeletion) {
+            updatedProperties.put(propertyToDelete, null);
+        }
+        
+        // create the controller service DTO
+        final ControllerServiceDTO controllerServiceDTO = new ControllerServiceDTO();
+        controllerServiceDTO.setId(id);
+        controllerServiceDTO.setName(name);
+        controllerServiceDTO.setAnnotationData(annotationData);
+        controllerServiceDTO.setComments(comments);
+        controllerServiceDTO.setState(state);
+
+        // only set the properties when appropriate
+        if (!updatedProperties.isEmpty()) {
+            controllerServiceDTO.setProperties(updatedProperties);
+        }
+        
+        // create the revision
+        final RevisionDTO revision = new RevisionDTO();
+        revision.setClientId(clientId.getClientId());
+        if (version != null) {
+            revision.setVersion(version.getLong());
+        }
+
+        // create the controller service entity
+        final ControllerServiceEntity controllerServiceEntity = new ControllerServiceEntity();
+        controllerServiceEntity.setRevision(revision);
+        controllerServiceEntity.setControllerService(controllerServiceDTO);
+
+        // update the controller service
+        return updateControllerService(httpServletRequest, availability, id, controllerServiceEntity);
+    }
+
+    /**
+     * Updates the specified a new Controller Service.
+     *
+     * @param httpServletRequest
+     * @param availability Whether the controller service is available on the NCM only (ncm) or on the 
+     * nodes only (node). If this instance is not clustered all services should use the node availability.
+     * @param id The id of the controller service to update.
+     * @param controllerServiceEntity A controllerServiceEntity.
+     * @return A controllerServiceEntity.
+     */
+    @PUT
+    @Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Path("/{availability}/{id}")
+    @PreAuthorize("hasRole('ROLE_DFM')")
+    @TypeHint(ControllerServiceEntity.class)
+    public Response updateControllerService(
+            @Context HttpServletRequest httpServletRequest,
+            @PathParam("availability") String availability, 
+            @PathParam("id") String id,
+            ControllerServiceEntity controllerServiceEntity) {
+
+        final Availability avail = parseAvailability(availability);
+        
+        if (controllerServiceEntity == null || controllerServiceEntity.getControllerService()== null) {
+            throw new IllegalArgumentException("Controller service details must be specified.");
+        }
+
+        if (controllerServiceEntity.getRevision() == null) {
+            throw new IllegalArgumentException("Revision must be specified.");
+        }
+
+        // ensure the ids are the same
+        final ControllerServiceDTO requestControllerServiceDTO = controllerServiceEntity.getControllerService();
+        if (!id.equals(requestControllerServiceDTO.getId())) {
+            throw new IllegalArgumentException(String.format("The controller service id (%s) in the request body does not equal the "
+                    + "controller service id of the requested resource (%s).", requestControllerServiceDTO.getId(), id));
+        }
+
+        // replicate if cluster manager
+        if (properties.isClusterManager() && Availability.NODE.equals(avail)) {
+            // change content type to JSON for serializing entity
+            final Map<String, String> headersToOverride = new HashMap<>();
+            headersToOverride.put("content-type", MediaType.APPLICATION_JSON);
+
+            // replicate the request
+            return clusterManager.applyRequest(HttpMethod.PUT, getAbsolutePath(), updateClientId(controllerServiceEntity), getHeaders(headersToOverride)).getResponse();
+        }
+        
+        // handle expects request (usually from the cluster manager)
+        final String expects = httpServletRequest.getHeader(WebClusterManager.NCM_EXPECTS_HTTP_HEADER);
+        if (expects != null) {
+            serviceFacade.verifyUpdateControllerService(requestControllerServiceDTO);
+            return generateContinueResponse().build();
+        }
+
+        // update the controller service
+        final RevisionDTO revision = controllerServiceEntity.getRevision();
+        final ConfigurationSnapshot<ControllerServiceDTO> controllerResponse = serviceFacade.updateControllerService(
+                new Revision(revision.getVersion(), revision.getClientId()), requestControllerServiceDTO);
+
+        // get the results
+        final ControllerServiceDTO responseControllerServiceDTO = controllerResponse.getConfiguration();
+
+        // get the updated revision
+        final RevisionDTO updatedRevision = new RevisionDTO();
+        updatedRevision.setClientId(revision.getClientId());
+        updatedRevision.setVersion(controllerResponse.getVersion());
+
+        // build the response entity
+        final ControllerServiceEntity entity = new ControllerServiceEntity();
+        entity.setRevision(updatedRevision);
+        entity.setControllerService(populateRemainingControllerServiceContent(availability, responseControllerServiceDTO));
+
+        return clusterContext(generateOkResponse(entity)).build();
+    }
+
+    /**
+     * Removes the specified controller service.
+     *
+     * @param httpServletRequest
+     * @param version The revision is used to verify the client is working with
+     * the latest version of the flow.
+     * @param clientId Optional client id. If the client id is not specified, a
+     * new one will be generated. This value (whether specified or generated) is
+     * included in the response.
+     * @param availability Whether the controller service is available on the NCM only (ncm) or on the 
+     * nodes only (node). If this instance is not clustered all services should use the node availability.
+     * @param id The id of the controller service to remove.
+     * @return A entity containing the client id and an updated revision.
+     */
+    @DELETE
+    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Path("/{availability}/{id}")
+    @PreAuthorize("hasRole('ROLE_DFM')")
+    @TypeHint(ControllerServiceEntity.class)
+    public Response removeControllerService(
+            @Context HttpServletRequest httpServletRequest,
+            @QueryParam(VERSION) LongParameter version,
+            @QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId,
+            @PathParam("availability") String availability, @PathParam("id") String id) {
+
+        final Availability avail = parseAvailability(availability);
+        
+        // replicate if cluster manager
+        if (properties.isClusterManager() && Availability.NODE.equals(avail)) {
+            return clusterManager.applyRequest(HttpMethod.DELETE, getAbsolutePath(), getRequestParameters(true), getHeaders()).getResponse();
+        }
+
+        // handle expects request (usually from the cluster manager)
+        final String expects = httpServletRequest.getHeader(WebClusterManager.NCM_EXPECTS_HTTP_HEADER);
+        if (expects != null) {
+            serviceFacade.verifyDeleteControllerService(id);
+            return generateContinueResponse().build();
+        }
+
+        // determine the specified version
+        Long clientVersion = null;
+        if (version != null) {
+            clientVersion = version.getLong();
+        }
+
+        // delete the specified controller service
+        final ConfigurationSnapshot<Void> controllerResponse = serviceFacade.deleteControllerService(new Revision(clientVersion, clientId.getClientId()), id);
+
+        // get the updated revision
+        final RevisionDTO revision = new RevisionDTO();
+        revision.setClientId(clientId.getClientId());
+        revision.setVersion(controllerResponse.getVersion());
+
+        // build the response entity
+        final ControllerServiceEntity entity = new ControllerServiceEntity();
+        entity.setRevision(revision);
+
+        return clusterContext(generateOkResponse(entity)).build();
+    }
+
+    // setters
+    
+    public void setServiceFacade(NiFiServiceFacade serviceFacade) {
+        this.serviceFacade = serviceFacade;
+    }
+
+    public void setClusterManager(WebClusterManager clusterManager) {
+        this.clusterManager = clusterManager;
+    }
+
+    public void setProperties(NiFiProperties properties) {
+        this.properties = properties;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/e9647717/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FunnelResource.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/FunnelResource.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FunnelResource.java
index 4406c2e..3492de2 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FunnelResource.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FunnelResource.java
@@ -244,7 +244,7 @@ public class FunnelResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO updatedRevision = new RevisionDTO();
         updatedRevision.setClientId(revision.getClientId());
-        updatedRevision.setVersion(controllerResponse.getRevision());
+        updatedRevision.setVersion(controllerResponse.getVersion());
 
         // build the response entity
         final FunnelEntity entity = new FunnelEntity();
@@ -408,7 +408,7 @@ public class FunnelResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO updatedRevision = new RevisionDTO();
         updatedRevision.setClientId(revision.getClientId());
-        updatedRevision.setVersion(controllerResponse.getRevision());
+        updatedRevision.setVersion(controllerResponse.getVersion());
 
         // build the response entity
         final FunnelEntity entity = new FunnelEntity();
@@ -465,7 +465,7 @@ public class FunnelResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO revision = new RevisionDTO();
         revision.setClientId(clientId.getClientId());
-        revision.setVersion(controllerResponse.getRevision());
+        revision.setVersion(controllerResponse.getVersion());
 
         // build the response entity
         final FunnelEntity entity = new FunnelEntity();

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/HistoryResource.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/HistoryResource.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/HistoryResource.java
index 38806eb..0f60f52 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/HistoryResource.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/HistoryResource.java
@@ -37,7 +37,7 @@ import org.apache.nifi.web.api.dto.RevisionDTO;
 import org.apache.nifi.web.api.dto.action.ActionDTO;
 import org.apache.nifi.web.api.dto.action.HistoryDTO;
 import org.apache.nifi.web.api.dto.action.HistoryQueryDTO;
-import org.apache.nifi.web.api.entity.ProcessorHistoryEntity;
+import org.apache.nifi.web.api.entity.ComponentHistoryEntity;
 import org.codehaus.enunciate.jaxrs.TypeHint;
 import org.springframework.security.access.prepost.PreAuthorize;
 
@@ -245,7 +245,7 @@ public class HistoryResource extends ApplicationResource {
     @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
     @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
     @Path("/processors/{processorId}")
-    @TypeHint(ProcessorHistoryEntity.class)
+    @TypeHint(ComponentHistoryEntity.class)
     public Response getProcessorHistory(
             @QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId,
             @PathParam("processorId") final String processorId) {
@@ -255,14 +255,76 @@ public class HistoryResource extends ApplicationResource {
         revision.setClientId(clientId.getClientId());
 
         // create the response entity
-        final ProcessorHistoryEntity entity = new ProcessorHistoryEntity();
+        final ComponentHistoryEntity entity = new ComponentHistoryEntity();
         entity.setRevision(revision);
-        entity.setProcessorHistory(serviceFacade.getProcessorHistory(processorId));
+        entity.setComponentHistory(serviceFacade.getComponentHistory(processorId));
 
         // generate the response
         return generateOkResponse(entity).build();
     }
+    
+    /**
+     * Gets the actions for the specified controller service.
+     *
+     * @param clientId Optional client id. If the client id is not specified, a
+     * new one will be generated. This value (whether specified or generated) is
+     * included in the response.
+     * @param controllerServiceId The id of the controller service.
+     * @return An componentHistoryEntity.
+     */
+    @GET
+    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
+    @Path("/controller-services/{controllerServiceId}")
+    @TypeHint(ComponentHistoryEntity.class)
+    public Response getControllerServiceHistory(
+            @QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId,
+            @PathParam("controllerServiceId") final String controllerServiceId) {
 
+        // create the revision
+        final RevisionDTO revision = new RevisionDTO();
+        revision.setClientId(clientId.getClientId());
+
+        // create the response entity
+        final ComponentHistoryEntity entity = new ComponentHistoryEntity();
+        entity.setRevision(revision);
+        entity.setComponentHistory(serviceFacade.getComponentHistory(controllerServiceId));
+
+        // generate the response
+        return generateOkResponse(entity).build();
+    }
+    
+    /**
+     * Gets the actions for the specified reporting task.
+     *
+     * @param clientId Optional client id. If the client id is not specified, a
+     * new one will be generated. This value (whether specified or generated) is
+     * included in the response.
+     * @param reportingTaskId The id of the reporting task.
+     * @return An componentHistoryEntity.
+     */
+    @GET
+    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
+    @Path("/reporting-tasks/{reportingTaskId}")
+    @TypeHint(ComponentHistoryEntity.class)
+    public Response getReportingTaskHistory(
+            @QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId,
+            @PathParam("reportingTaskId") final String reportingTaskId) {
+
+        // create the revision
+        final RevisionDTO revision = new RevisionDTO();
+        revision.setClientId(clientId.getClientId());
+
+        // create the response entity
+        final ComponentHistoryEntity entity = new ComponentHistoryEntity();
+        entity.setRevision(revision);
+        entity.setComponentHistory(serviceFacade.getComponentHistory(reportingTaskId));
+
+        // generate the response
+        return generateOkResponse(entity).build();
+    }
+    
     /* setters */
     public void setServiceFacade(NiFiServiceFacade serviceFacade) {
         this.serviceFacade = serviceFacade;

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/InputPortResource.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/InputPortResource.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/InputPortResource.java
index 58c3c9e..f3a6326 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/InputPortResource.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/InputPortResource.java
@@ -251,7 +251,7 @@ public class InputPortResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO updatedRevision = new RevisionDTO();
         updatedRevision.setClientId(revision.getClientId());
-        updatedRevision.setVersion(controllerResponse.getRevision());
+        updatedRevision.setVersion(controllerResponse.getVersion());
 
         // build the response entity
         final InputPortEntity entity = new InputPortEntity();
@@ -446,7 +446,7 @@ public class InputPortResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO updatedRevision = new RevisionDTO();
         updatedRevision.setClientId(revision.getClientId());
-        updatedRevision.setVersion(controllerResponse.getRevision());
+        updatedRevision.setVersion(controllerResponse.getVersion());
 
         // build the response entity
         final InputPortEntity entity = new InputPortEntity();
@@ -503,7 +503,7 @@ public class InputPortResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO revision = new RevisionDTO();
         revision.setClientId(clientId.getClientId());
-        revision.setVersion(controllerResponse.getRevision());
+        revision.setVersion(controllerResponse.getVersion());
 
         // build the response entity
         final InputPortEntity entity = new InputPortEntity();

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/LabelResource.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/LabelResource.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/LabelResource.java
index 9a61cfc..6435671 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/LabelResource.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/LabelResource.java
@@ -260,7 +260,7 @@ public class LabelResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO updatedRevision = new RevisionDTO();
         updatedRevision.setClientId(revision.getClientId());
-        updatedRevision.setVersion(controllerResponse.getRevision());
+        updatedRevision.setVersion(controllerResponse.getVersion());
 
         // build the response entity
         final LabelEntity entity = new LabelEntity();
@@ -463,7 +463,7 @@ public class LabelResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO updatedRevision = new RevisionDTO();
         updatedRevision.setClientId(revision.getClientId());
-        updatedRevision.setVersion(controllerResponse.getRevision());
+        updatedRevision.setVersion(controllerResponse.getVersion());
 
         // build the response entity
         final LabelEntity entity = new LabelEntity();
@@ -519,7 +519,7 @@ public class LabelResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO revision = new RevisionDTO();
         revision.setClientId(clientId.getClientId());
-        revision.setVersion(controllerResponse.getRevision());
+        revision.setVersion(controllerResponse.getVersion());
 
         // build the response entity
         final LabelEntity entity = new LabelEntity();

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/OutputPortResource.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/OutputPortResource.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/OutputPortResource.java
index 224ab18..a9dce5f 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/OutputPortResource.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/OutputPortResource.java
@@ -251,7 +251,7 @@ public class OutputPortResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO updatedRevision = new RevisionDTO();
         updatedRevision.setClientId(revision.getClientId());
-        updatedRevision.setVersion(controllerResponse.getRevision());
+        updatedRevision.setVersion(controllerResponse.getVersion());
 
         // build the response entity
         final OutputPortEntity entity = new OutputPortEntity();
@@ -447,7 +447,7 @@ public class OutputPortResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO updatedRevision = new RevisionDTO();
         updatedRevision.setClientId(revision.getClientId());
-        updatedRevision.setVersion(controllerResponse.getRevision());
+        updatedRevision.setVersion(controllerResponse.getVersion());
 
         // build the response entity
         final OutputPortEntity entity = new OutputPortEntity();
@@ -504,7 +504,7 @@ public class OutputPortResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO revision = new RevisionDTO();
         revision.setClientId(clientId.getClientId());
-        revision.setVersion(controllerResponse.getRevision());
+        revision.setVersion(controllerResponse.getVersion());
 
         // build the response entity
         final OutputPortEntity entity = new OutputPortEntity();

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/ProcessGroupResource.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/ProcessGroupResource.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessGroupResource.java
index 6439bda..1bf3f77 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessGroupResource.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessGroupResource.java
@@ -287,7 +287,7 @@ public class ProcessGroupResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO revision = new RevisionDTO();
         revision.setClientId(clientId.getClientId());
-        revision.setVersion(controllerResponse.getRevision());
+        revision.setVersion(controllerResponse.getVersion());
 
         // create the response entity
         final ProcessGroupEntity processGroupEntity = new ProcessGroupEntity();
@@ -365,7 +365,7 @@ public class ProcessGroupResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO revision = new RevisionDTO();
         revision.setClientId(clientId.getClientId());
-        revision.setVersion(controllerResponse.getRevision());
+        revision.setVersion(controllerResponse.getVersion());
 
         // create the response entity
         final FlowSnippetEntity entity = new FlowSnippetEntity();
@@ -441,7 +441,7 @@ public class ProcessGroupResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO revision = new RevisionDTO();
         revision.setClientId(clientId.getClientId());
-        revision.setVersion(response.getRevision());
+        revision.setVersion(response.getVersion());
 
         // create the response entity
         final FlowSnippetEntity entity = new FlowSnippetEntity();
@@ -559,7 +559,7 @@ public class ProcessGroupResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO updatedRevision = new RevisionDTO();
         updatedRevision.setClientId(revision.getClientId());
-        updatedRevision.setVersion(response.getRevision());
+        updatedRevision.setVersion(response.getVersion());
 
         // create the response entity
         final ProcessGroupEntity entity = new ProcessGroupEntity();
@@ -616,7 +616,7 @@ public class ProcessGroupResource extends ApplicationResource {
         // create the revision
         final RevisionDTO revision = new RevisionDTO();
         revision.setClientId(clientId.getClientId());
-        revision.setVersion(controllerResponse.getRevision());
+        revision.setVersion(controllerResponse.getVersion());
 
         // create the response entity
         final ProcessGroupEntity processGroupEntity = new ProcessGroupEntity();
@@ -795,7 +795,7 @@ public class ProcessGroupResource 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 ProcessGroupEntity entity = new ProcessGroupEntity();
@@ -931,7 +931,7 @@ public class ProcessGroupResource extends ApplicationResource {
         // create the revision
         final RevisionDTO updatedRevision = new RevisionDTO();
         updatedRevision.setClientId(revision.getClientId());
-        updatedRevision.setVersion(response.getRevision());
+        updatedRevision.setVersion(response.getVersion());
 
         // create the response entity
         final ProcessGroupEntity entity = new ProcessGroupEntity();
@@ -989,7 +989,7 @@ public class ProcessGroupResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO revision = new RevisionDTO();
         revision.setClientId(clientId.getClientId());
-        revision.setVersion(controllerResponse.getRevision());
+        revision.setVersion(controllerResponse.getVersion());
 
         // create the response entity
         final ProcessGroupEntity entity = new ProcessGroupEntity();

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/ProcessorResource.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/ProcessorResource.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessorResource.java
index b11c40a..31ab10b 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessorResource.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessorResource.java
@@ -70,6 +70,11 @@ 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.ui.extension.UiExtension;
+import org.apache.nifi.ui.extension.UiExtensionMapping;
+import org.apache.nifi.web.UiExtensionType;
+import org.apache.nifi.web.api.dto.PropertyDescriptorDTO;
+import org.apache.nifi.web.api.entity.PropertyDescriptorEntity;
 import org.codehaus.enunciate.jaxrs.TypeHint;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -118,9 +123,21 @@ public class ProcessorResource extends ApplicationResource {
         // get the config details and see if there is a custom ui for this processor type
         ProcessorConfigDTO config = processor.getConfig();
         if (config != null) {
+            // consider legacy custom ui fist
             String customUiUrl = servletContext.getInitParameter(processor.getType());
             if (StringUtils.isNotBlank(customUiUrl)) {
                 config.setCustomUiUrl(customUiUrl);
+            } else {
+                // see if this processor has any ui extensions
+                final UiExtensionMapping uiExtensionMapping = (UiExtensionMapping) servletContext.getAttribute("nifi-ui-extensions");
+                if (uiExtensionMapping.hasUiExtension(processor.getType())) {
+                    final List<UiExtension> uiExtensions = uiExtensionMapping.getUiExtension(processor.getType());
+                    for (final UiExtension uiExtension : uiExtensions) {
+                        if (UiExtensionType.ProcessorConfiguration.equals(uiExtension.getExtensionType())) {
+                            config.setCustomUiUrl(uiExtension.getContextPath() + "/configure");
+                        }
+                    }
+                }
             }
         }
 
@@ -245,6 +262,10 @@ public class ProcessorResource extends ApplicationResource {
         if (processorEntity.getProcessor().getId() != null) {
             throw new IllegalArgumentException("Processor ID cannot be specified.");
         }
+        
+        if (StringUtils.isBlank(processorEntity.getProcessor().getType())) {
+            throw new IllegalArgumentException("The type of processor to create must be specified.");
+        }
 
         // if cluster manager, convert POST to PUT (to maintain same ID across nodes) and replicate
         if (properties.isClusterManager()) {
@@ -288,7 +309,7 @@ public class ProcessorResource extends ApplicationResource {
         // get the updated revision
         final 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();
@@ -373,6 +394,51 @@ public class ProcessorResource extends ApplicationResource {
         // generate the response
         return clusterContext(generateOkResponse(entity)).build();
     }
+    
+    /**
+     * Returns the descriptor for the specified property.
+     * 
+     * @param clientId Optional client id. If the client id is not specified, a
+     * new one will be generated. This value (whether specified or generated) is
+     * included in the response.
+     * @param id The id of the processor
+     * @param propertyName The property
+     * @return a propertyDescriptorEntity
+     */
+    @GET
+    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Path("/{id}/descriptors")
+    @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
+    @TypeHint(PropertyDescriptorEntity.class)
+    public Response getPropertyDescriptor(
+            @QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId, 
+            @PathParam("id") String id, @QueryParam("propertyName") String propertyName) {
+        
+        // ensure the property name is specified
+        if (propertyName == null) {
+            throw new IllegalArgumentException("The property name must be specified.");
+        }
+        
+        // replicate if cluster manager
+        if (properties.isClusterManager()) {
+            return clusterManager.applyRequest(HttpMethod.GET, getAbsolutePath(), getRequestParameters(true), getHeaders()).getResponse();
+        }
+        
+        // get the property descriptor
+        final PropertyDescriptorDTO descriptor = serviceFacade.getProcessorPropertyDescriptor(groupId, id, propertyName);
+        
+        // create the revision
+        final RevisionDTO revision = new RevisionDTO();
+        revision.setClientId(clientId.getClientId());
+        
+        // generate the response entity
+        final PropertyDescriptorEntity entity = new PropertyDescriptorEntity();
+        entity.setRevision(revision);
+        entity.setPropertyDescriptor(descriptor);
+        
+        // generate the response
+        return clusterContext(generateOkResponse(entity)).build();
+    }
 
     /**
      * Updates the specified processor with the specified values.
@@ -607,7 +673,7 @@ public class ProcessorResource extends ApplicationResource {
         // get the updated revision
         final 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();
@@ -664,7 +730,7 @@ public class ProcessorResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO updatedRevision = new RevisionDTO();
         updatedRevision.setClientId(clientId.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/RemoteProcessGroupResource.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/RemoteProcessGroupResource.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/RemoteProcessGroupResource.java
index 27fa292..4e15c36 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/RemoteProcessGroupResource.java
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/RemoteProcessGroupResource.java
@@ -396,7 +396,7 @@ public class RemoteProcessGroupResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO updatedRevision = new RevisionDTO();
         updatedRevision.setClientId(revision.getClientId());
-        updatedRevision.setVersion(controllerResponse.getRevision());
+        updatedRevision.setVersion(controllerResponse.getVersion());
 
         // build the response entity
         final RemoteProcessGroupEntity entity = new RemoteProcessGroupEntity();
@@ -452,7 +452,7 @@ public class RemoteProcessGroupResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO revision = new RevisionDTO();
         revision.setClientId(clientId.getClientId());
-        revision.setVersion(controllerResponse.getRevision());
+        revision.setVersion(controllerResponse.getVersion());
 
         // create the response entity
         final RemoteProcessGroupEntity entity = new RemoteProcessGroupEntity();
@@ -586,7 +586,7 @@ public class RemoteProcessGroupResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO updatedRevision = new RevisionDTO();
         updatedRevision.setClientId(revision.getClientId());
-        updatedRevision.setVersion(controllerResponse.getRevision());
+        updatedRevision.setVersion(controllerResponse.getVersion());
 
         // build the response entity
         final RemoteProcessGroupPortEntity entity = new RemoteProcessGroupPortEntity();
@@ -720,7 +720,7 @@ public class RemoteProcessGroupResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO updatedRevision = new RevisionDTO();
         updatedRevision.setClientId(revision.getClientId());
-        updatedRevision.setVersion(controllerResponse.getRevision());
+        updatedRevision.setVersion(controllerResponse.getVersion());
 
         // build the response entity
         RemoteProcessGroupPortEntity entity = new RemoteProcessGroupPortEntity();
@@ -890,7 +890,7 @@ public class RemoteProcessGroupResource extends ApplicationResource {
         // get the updated revision
         final RevisionDTO updatedRevision = new RevisionDTO();
         updatedRevision.setClientId(revision.getClientId());
-        updatedRevision.setVersion(controllerResponse.getRevision());
+        updatedRevision.setVersion(controllerResponse.getVersion());
 
         // build the response entity
         final RemoteProcessGroupEntity entity = new RemoteProcessGroupEntity();