/*
* 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 com.sun.jersey.api.core.ResourceContext;
import com.sun.jersey.multipart.FormDataParam;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;
import com.wordnik.swagger.annotations.ApiParam;
import com.wordnik.swagger.annotations.ApiResponse;
import com.wordnik.swagger.annotations.ApiResponses;
import com.wordnik.swagger.annotations.Authorization;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.authorization.AuthorizableLookup;
import org.apache.nifi.authorization.AuthorizeControllerServiceReference;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.ComponentAuthorizable;
import org.apache.nifi.authorization.ProcessGroupAuthorizable;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.SnippetAuthorizable;
import org.apache.nifi.authorization.TemplateContentsAuthorizable;
import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserUtils;
import org.apache.nifi.bundle.BundleCoordinate;
import org.apache.nifi.connectable.ConnectableType;
import org.apache.nifi.controller.serialization.FlowEncodingVersion;
import org.apache.nifi.remote.util.SiteToSiteRestApiClient;
import org.apache.nifi.util.BundleUtils;
import org.apache.nifi.web.NiFiServiceFacade;
import org.apache.nifi.web.ResourceNotFoundException;
import org.apache.nifi.web.Revision;
import org.apache.nifi.web.api.dto.BundleDTO;
import org.apache.nifi.web.api.dto.ConnectionDTO;
import org.apache.nifi.web.api.dto.ControllerServiceDTO;
import org.apache.nifi.web.api.dto.FlowSnippetDTO;
import org.apache.nifi.web.api.dto.PositionDTO;
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.RemoteProcessGroupDTO;
import org.apache.nifi.web.api.dto.TemplateDTO;
import org.apache.nifi.web.api.dto.flow.FlowDTO;
import org.apache.nifi.web.api.entity.ConnectionEntity;
import org.apache.nifi.web.api.entity.ConnectionsEntity;
import org.apache.nifi.web.api.entity.ControllerServiceEntity;
import org.apache.nifi.web.api.entity.CopySnippetRequestEntity;
import org.apache.nifi.web.api.entity.CreateTemplateRequestEntity;
import org.apache.nifi.web.api.entity.FlowEntity;
import org.apache.nifi.web.api.entity.FlowSnippetEntity;
import org.apache.nifi.web.api.entity.FunnelEntity;
import org.apache.nifi.web.api.entity.FunnelsEntity;
import org.apache.nifi.web.api.entity.InputPortsEntity;
import org.apache.nifi.web.api.entity.InstantiateTemplateRequestEntity;
import org.apache.nifi.web.api.entity.LabelEntity;
import org.apache.nifi.web.api.entity.LabelsEntity;
import org.apache.nifi.web.api.entity.OutputPortsEntity;
import org.apache.nifi.web.api.entity.PortEntity;
import org.apache.nifi.web.api.entity.ProcessGroupEntity;
import org.apache.nifi.web.api.entity.ProcessGroupsEntity;
import org.apache.nifi.web.api.entity.ProcessorEntity;
import org.apache.nifi.web.api.entity.ProcessorsEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupsEntity;
import org.apache.nifi.web.api.entity.TemplateEntity;
import org.apache.nifi.web.api.request.ClientIdParameter;
import org.apache.nifi.web.api.request.LongParameter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
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.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;
import java.io.InputStream;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
/**
* RESTful endpoint for managing a Group.
*/
@Path("/process-groups")
@Api(
value = "/process-groups",
description = "Endpoint for managing a Process Group."
)
public class ProcessGroupResource extends ApplicationResource {
private static final Logger logger = LoggerFactory.getLogger(ProcessGroupResource.class);
@Context
private ResourceContext resourceContext;
private NiFiServiceFacade serviceFacade;
private Authorizer authorizer;
private ProcessorResource processorResource;
private InputPortResource inputPortResource;
private OutputPortResource outputPortResource;
private FunnelResource funnelResource;
private LabelResource labelResource;
private RemoteProcessGroupResource remoteProcessGroupResource;
private ConnectionResource connectionResource;
private TemplateResource templateResource;
private ControllerServiceResource controllerServiceResource;
/**
* Populates the remaining fields in the specified process groups.
*
* @param processGroupEntities groups
* @return group dto
*/
public Set<ProcessGroupEntity> populateRemainingProcessGroupEntitiesContent(Set<ProcessGroupEntity> processGroupEntities) {
for (ProcessGroupEntity processGroupEntity : processGroupEntities) {
populateRemainingProcessGroupEntityContent(processGroupEntity);
}
return processGroupEntities;
}
/**
* Populates the remaining fields in the specified process group.
*
* @param processGroupEntity group
* @return group dto
*/
public ProcessGroupEntity populateRemainingProcessGroupEntityContent(ProcessGroupEntity processGroupEntity) {
processGroupEntity.setUri(generateResourceUri("process-groups", processGroupEntity.getId()));
return processGroupEntity;
}
/**
* Populates the remaining content of the specified snippet.
*/
private FlowDTO populateRemainingSnippetContent(FlowDTO flow) {
processorResource.populateRemainingProcessorEntitiesContent(flow.getProcessors());
connectionResource.populateRemainingConnectionEntitiesContent(flow.getConnections());
inputPortResource.populateRemainingInputPortEntitiesContent(flow.getInputPorts());
outputPortResource.populateRemainingOutputPortEntitiesContent(flow.getOutputPorts());
remoteProcessGroupResource.populateRemainingRemoteProcessGroupEntitiesContent(flow.getRemoteProcessGroups());
funnelResource.populateRemainingFunnelEntitiesContent(flow.getFunnels());
labelResource.populateRemainingLabelEntitiesContent(flow.getLabels());
// go through each process group child and populate its uri
if (flow.getProcessGroups() != null) {
populateRemainingProcessGroupEntitiesContent(flow.getProcessGroups());
}
return flow;
}
/**
* Retrieves the contents of the specified group.
*
* @param groupId The id of the process group.
* @return A processGroupEntity.
*/
@GET
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}")
@ApiOperation(
value = "Gets a process group",
response = ProcessGroupEntity.class,
authorizations = {
@Authorization(value = "Read - /process-groups/{uuid}", type = "")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response getProcessGroup(
@ApiParam(
value = "The process group id.",
required = false
)
@PathParam("id") final String groupId) {
if (isReplicateRequest()) {
return replicate(HttpMethod.GET);
}
// authorize access
serviceFacade.authorizeAccess(lookup -> {
final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable();
processGroup.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
});
// get this process group contents
final ProcessGroupEntity entity = serviceFacade.getProcessGroup(groupId);
populateRemainingProcessGroupEntityContent(entity);
if (entity.getComponent() != null) {
entity.getComponent().setContents(null);
}
return clusterContext(generateOkResponse(entity)).build();
}
/**
* Updates the specified process group.
*
* @param httpServletRequest request
* @param id The id of the process group.
* @param requestProcessGroupEntity A processGroupEntity.
* @return A processGroupEntity.
*/
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}")
@ApiOperation(
value = "Updates a process group",
response = ProcessGroupEntity.class,
authorizations = {
@Authorization(value = "Write - /process-groups/{uuid}", type = "")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response updateProcessGroup(
@Context final HttpServletRequest httpServletRequest,
@ApiParam(
value = "The process group id.",
required = true
)
@PathParam("id") final String id,
@ApiParam(
value = "The process group configuration details.",
required = true
) final ProcessGroupEntity requestProcessGroupEntity) {
if (requestProcessGroupEntity == null || requestProcessGroupEntity.getComponent() == null) {
throw new IllegalArgumentException("Process group details must be specified.");
}
if (requestProcessGroupEntity.getRevision() == null) {
throw new IllegalArgumentException("Revision must be specified.");
}
// ensure the same id is being used
final ProcessGroupDTO requestProcessGroupDTO = requestProcessGroupEntity.getComponent();
if (!id.equals(requestProcessGroupDTO.getId())) {
throw new IllegalArgumentException(String.format("The process group id (%s) in the request body does "
+ "not equal the process group id of the requested resource (%s).", requestProcessGroupDTO.getId(), id));
}
final PositionDTO proposedPosition = requestProcessGroupDTO.getPosition();
if (proposedPosition != null) {
if (proposedPosition.getX() == null || proposedPosition.getY() == null) {
throw new IllegalArgumentException("The x and y coordinate of the proposed position must be specified.");
}
}
if (isReplicateRequest()) {
return replicate(HttpMethod.PUT, requestProcessGroupEntity);
}
// handle expects request (usually from the cluster manager)
final Revision requestRevision = getRevision(requestProcessGroupEntity, id);
return withWriteLock(
serviceFacade,
requestProcessGroupEntity,
requestRevision,
lookup -> {
Authorizable authorizable = lookup.getProcessGroup(id).getAuthorizable();
authorizable.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
},
null,
(revision, processGroupEntity) -> {
// update the process group
final ProcessGroupEntity entity = serviceFacade.updateProcessGroup(revision, processGroupEntity.getComponent());
populateRemainingProcessGroupEntityContent(entity);
return clusterContext(generateOkResponse(entity)).build();
}
);
}
/**
* Removes the specified process group reference.
*
* @param httpServletRequest request
* @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 id The id of the process group to be removed.
* @return A processGroupEntity.
*/
@DELETE
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}")
@ApiOperation(
value = "Deletes a process group",
response = ProcessGroupEntity.class,
authorizations = {
@Authorization(value = "Write - /process-groups/{uuid}", type = ""),
@Authorization(value = "Write - Parent Process Group - /process-groups/{uuid}", type = ""),
@Authorization(value = "Read - any referenced Controller Services by any encapsulated components - /controller-services/{uuid}", type = ""),
@Authorization(value = "Write - /{component-type}/{uuid} - For all encapsulated components", type = "")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response removeProcessGroup(
@Context final HttpServletRequest httpServletRequest,
@ApiParam(
value = "The revision is used to verify the client is working with the latest version of the flow.",
required = false
)
@QueryParam(VERSION) final LongParameter version,
@ApiParam(
value = "If the client id is not specified, new one will be generated. This value (whether specified or generated) is included in the response.",
required = false
)
@QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) final ClientIdParameter clientId,
@ApiParam(
value = "The process group id.",
required = true
)
@PathParam("id") final String id) {
// replicate if cluster manager
if (isReplicateRequest()) {
return replicate(HttpMethod.DELETE);
}
final ProcessGroupEntity requestProcessGroupEntity = new ProcessGroupEntity();
requestProcessGroupEntity.setId(id);
// handle expects request (usually from the cluster manager)
final Revision requestRevision = new Revision(version == null ? null : version.getLong(), clientId.getClientId(), id);
return withWriteLock(
serviceFacade,
requestProcessGroupEntity,
requestRevision,
lookup -> {
final ProcessGroupAuthorizable processGroupAuthorizable = lookup.getProcessGroup(id);
// ensure write to this process group and all encapsulated components including templates and controller services. additionally, ensure
// read to any referenced services by encapsulated components
authorizeProcessGroup(processGroupAuthorizable, authorizer, lookup, RequestAction.WRITE, true, true, true, false);
// ensure write permission to the parent process group, if applicable... if this is the root group the
// request will fail later but still need to handle authorization here
final Authorizable parentAuthorizable = processGroupAuthorizable.getAuthorizable().getParentAuthorizable();
if (parentAuthorizable != null) {
parentAuthorizable.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
}
},
() -> serviceFacade.verifyDeleteProcessGroup(id),
(revision, processGroupEntity) -> {
// delete the process group
final ProcessGroupEntity entity = serviceFacade.deleteProcessGroup(revision, processGroupEntity.getId());
// create the response
return clusterContext(generateOkResponse(entity)).build();
}
);
}
/**
* Adds the specified process group.
*
* @param httpServletRequest request
* @param groupId The group id
* @param requestProcessGroupEntity A processGroupEntity
* @return A processGroupEntity
*/
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}/process-groups")
@ApiOperation(
value = "Creates a process group",
response = ProcessGroupEntity.class,
authorizations = {
@Authorization(value = "Write - /process-groups/{uuid}", type = "")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response createProcessGroup(
@Context final HttpServletRequest httpServletRequest,
@ApiParam(
value = "The process group id.",
required = true
)
@PathParam("id") final String groupId,
@ApiParam(
value = "The process group configuration details.",
required = true
) final ProcessGroupEntity requestProcessGroupEntity) {
if (requestProcessGroupEntity == null || requestProcessGroupEntity.getComponent() == null) {
throw new IllegalArgumentException("Process group details must be specified.");
}
if (requestProcessGroupEntity.getRevision() == null || (requestProcessGroupEntity.getRevision().getVersion() == null || requestProcessGroupEntity.getRevision().getVersion() != 0)) {
throw new IllegalArgumentException("A revision of 0 must be specified when creating a new Process group.");
}
if (requestProcessGroupEntity.getComponent().getId() != null) {
throw new IllegalArgumentException("Process group ID cannot be specified.");
}
final PositionDTO proposedPosition = requestProcessGroupEntity.getComponent().getPosition();
if (proposedPosition != null) {
if (proposedPosition.getX() == null || proposedPosition.getY() == null) {
throw new IllegalArgumentException("The x and y coordinate of the proposed position must be specified.");
}
}
if (requestProcessGroupEntity.getComponent().getParentGroupId() != null && !groupId.equals(requestProcessGroupEntity.getComponent().getParentGroupId())) {
throw new IllegalArgumentException(String.format("If specified, the parent process group id %s must be the same as specified in the URI %s",
requestProcessGroupEntity.getComponent().getParentGroupId(), groupId));
}
requestProcessGroupEntity.getComponent().setParentGroupId(groupId);
if (isReplicateRequest()) {
return replicate(HttpMethod.POST, requestProcessGroupEntity);
}
return withWriteLock(
serviceFacade,
requestProcessGroupEntity,
lookup -> {
final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable();
processGroup.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
},
null,
processGroupGroupEntity -> {
// set the processor id as appropriate
processGroupGroupEntity.getComponent().setId(generateUuid());
// create the process group contents
final Revision revision = getRevision(processGroupGroupEntity, processGroupGroupEntity.getComponent().getId());
final ProcessGroupEntity entity = serviceFacade.createProcessGroup(revision, groupId, processGroupGroupEntity.getComponent());
populateRemainingProcessGroupEntityContent(entity);
// generate a 201 created response
String uri = entity.getUri();
return clusterContext(generateCreatedResponse(URI.create(uri), entity)).build();
}
);
}
/**
* Retrieves all the processors in this NiFi.
*
* @return A processorsEntity.
*/
@GET
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}/process-groups")
@ApiOperation(
value = "Gets all process groups",
response = ProcessorsEntity.class,
authorizations = {
@Authorization(value = "Read - /process-groups/{uuid}", type = "")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response getProcessGroups(
@ApiParam(
value = "The process group id.",
required = true
)
@PathParam("id") final String groupId) {
if (isReplicateRequest()) {
return replicate(HttpMethod.GET);
}
// authorize access
serviceFacade.authorizeAccess(lookup -> {
final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable();
processGroup.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
});
// get the process groups
final Set<ProcessGroupEntity> entities = serviceFacade.getProcessGroups(groupId);
// always prune the contents
for (final ProcessGroupEntity entity : entities) {
if (entity.getComponent() != null) {
entity.getComponent().setContents(null);
}
}
// create the response entity
final ProcessGroupsEntity entity = new ProcessGroupsEntity();
entity.setProcessGroups(populateRemainingProcessGroupEntitiesContent(entities));
// generate the response
return clusterContext(generateOkResponse(entity)).build();
}
// ----------
// processors
// ----------
/**
* Creates a new processor.
*
* @param httpServletRequest request
* @param groupId The group id
* @param requestProcessorEntity A processorEntity.
* @return A processorEntity.
*/
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}/processors")
@ApiOperation(
value = "Creates a new processor",
response = ProcessorEntity.class,
authorizations = {
@Authorization(value = "Write - /process-groups/{uuid}", type = ""),
@Authorization(value = "Read - any referenced Controller Services - /controller-services/{uuid}", type = ""),
@Authorization(value = "Write - if the Processor is restricted - /restricted-components", type = "")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response createProcessor(
@Context final HttpServletRequest httpServletRequest,
@ApiParam(
value = "The process group id.",
required = true
)
@PathParam("id") final String groupId,
@ApiParam(
value = "The processor configuration details.",
required = true
) final ProcessorEntity requestProcessorEntity) {
if (requestProcessorEntity == null || requestProcessorEntity.getComponent() == null) {
throw new IllegalArgumentException("Processor details must be specified.");
}
if (requestProcessorEntity.getRevision() == null || (requestProcessorEntity.getRevision().getVersion() == null || requestProcessorEntity.getRevision().getVersion() != 0)) {
throw new IllegalArgumentException("A revision of 0 must be specified when creating a new Processor.");
}
final ProcessorDTO requestProcessor = requestProcessorEntity.getComponent();
if (requestProcessor.getId() != null) {
throw new IllegalArgumentException("Processor ID cannot be specified.");
}
if (StringUtils.isBlank(requestProcessor.getType())) {
throw new IllegalArgumentException("The type of processor to create must be specified.");
}
final PositionDTO proposedPosition = requestProcessor.getPosition();
if (proposedPosition != null) {
if (proposedPosition.getX() == null || proposedPosition.getY() == null) {
throw new IllegalArgumentException("The x and y coordinate of the proposed position must be specified.");
}
}
if (requestProcessor.getParentGroupId() != null && !groupId.equals(requestProcessor.getParentGroupId())) {
throw new IllegalArgumentException(String.format("If specified, the parent process group id %s must be the same as specified in the URI %s",
requestProcessor.getParentGroupId(), groupId));
}
requestProcessor.setParentGroupId(groupId);
if (isReplicateRequest()) {
return replicate(HttpMethod.POST, requestProcessorEntity);
}
return withWriteLock(
serviceFacade,
requestProcessorEntity,
lookup -> {
final NiFiUser user = NiFiUserUtils.getNiFiUser();
final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable();
processGroup.authorize(authorizer, RequestAction.WRITE, user);
ComponentAuthorizable authorizable = null;
try {
authorizable = lookup.getConfigurableComponent(requestProcessor.getType(), requestProcessor.getBundle());
if (authorizable.isRestricted()) {
lookup.getRestrictedComponents().authorize(authorizer, RequestAction.WRITE, user);
}
final ProcessorConfigDTO config = requestProcessor.getConfig();
if (config != null && config.getProperties() != null) {
AuthorizeControllerServiceReference.authorizeControllerServiceReferences(config.getProperties(), authorizable, authorizer, lookup);
}
} finally {
if (authorizable != null) {
authorizable.cleanUpResources();
}
}
},
() -> serviceFacade.verifyCreateProcessor(requestProcessor),
processorEntity -> {
final ProcessorDTO processor = processorEntity.getComponent();
// set the processor id as appropriate
processor.setId(generateUuid());
// create the new processor
final Revision revision = getRevision(processorEntity, processor.getId());
final ProcessorEntity entity = serviceFacade.createProcessor(revision, groupId, processor);
processorResource.populateRemainingProcessorEntityContent(entity);
// generate a 201 created response
String uri = entity.getUri();
return clusterContext(generateCreatedResponse(URI.create(uri), entity)).build();
}
);
}
/**
* Retrieves all the processors in this NiFi.
*
* @param groupId group id
* @return A processorsEntity.
*/
@GET
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}/processors")
@ApiOperation(
value = "Gets all processors",
response = ProcessorsEntity.class,
authorizations = {
@Authorization(value = "Read - /process-groups/{uuid}", type = "")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response getProcessors(
@ApiParam(
value = "The process group id.",
required = true
)
@PathParam("id") final String groupId) {
if (isReplicateRequest()) {
return replicate(HttpMethod.GET);
}
// authorize access
serviceFacade.authorizeAccess(lookup -> {
final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable();
processGroup.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
});
// get the processors
final Set<ProcessorEntity> processors = serviceFacade.getProcessors(groupId);
// create the response entity
final ProcessorsEntity entity = new ProcessorsEntity();
entity.setProcessors(processorResource.populateRemainingProcessorEntitiesContent(processors));
// generate the response
return clusterContext(generateOkResponse(entity)).build();
}
// -----------
// input ports
// -----------
/**
* Creates a new input port.
*
* @param httpServletRequest request
* @param groupId The group id
* @param requestPortEntity A inputPortEntity.
* @return A inputPortEntity.
*/
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}/input-ports")
@ApiOperation(
value = "Creates an input port",
response = PortEntity.class,
authorizations = {
@Authorization(value = "Write - /process-groups/{uuid}", type = "")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response createInputPort(
@Context final HttpServletRequest httpServletRequest,
@ApiParam(
value = "The process group id.",
required = true
)
@PathParam("id") final String groupId,
@ApiParam(
value = "The input port configuration details.",
required = true
) final PortEntity requestPortEntity) {
if (requestPortEntity == null || requestPortEntity.getComponent() == null) {
throw new IllegalArgumentException("Port details must be specified.");
}
if (requestPortEntity.getRevision() == null || (requestPortEntity.getRevision().getVersion() == null || requestPortEntity.getRevision().getVersion() != 0)) {
throw new IllegalArgumentException("A revision of 0 must be specified when creating a new Input port.");
}
if (requestPortEntity.getComponent().getId() != null) {
throw new IllegalArgumentException("Input port ID cannot be specified.");
}
final PositionDTO proposedPosition = requestPortEntity.getComponent().getPosition();
if (proposedPosition != null) {
if (proposedPosition.getX() == null || proposedPosition.getY() == null) {
throw new IllegalArgumentException("The x and y coordinate of the proposed position must be specified.");
}
}
if (requestPortEntity.getComponent().getParentGroupId() != null && !groupId.equals(requestPortEntity.getComponent().getParentGroupId())) {
throw new IllegalArgumentException(String.format("If specified, the parent process group id %s must be the same as specified in the URI %s",
requestPortEntity.getComponent().getParentGroupId(), groupId));
}
requestPortEntity.getComponent().setParentGroupId(groupId);
if (isReplicateRequest()) {
return replicate(HttpMethod.POST, requestPortEntity);
}
return withWriteLock(
serviceFacade,
requestPortEntity,
lookup -> {
final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable();
processGroup.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
},
null,
portEntity -> {
// set the processor id as appropriate
portEntity.getComponent().setId(generateUuid());
// create the input port and generate the json
final Revision revision = getRevision(portEntity, portEntity.getComponent().getId());
final PortEntity entity = serviceFacade.createInputPort(revision, groupId, portEntity.getComponent());
inputPortResource.populateRemainingInputPortEntityContent(entity);
// build the response
return clusterContext(generateCreatedResponse(URI.create(entity.getUri()), entity)).build();
}
);
}
/**
* Retrieves all the of input ports in this NiFi.
*
* @return A inputPortsEntity.
*/
@GET
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}/input-ports")
@ApiOperation(
value = "Gets all input ports",
response = InputPortsEntity.class,
authorizations = {
@Authorization(value = "Read - /process-groups/{uuid}", type = "")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response getInputPorts(
@ApiParam(
value = "The process group id.",
required = true
)
@PathParam("id") final String groupId) {
if (isReplicateRequest()) {
return replicate(HttpMethod.GET);
}
// authorize access
serviceFacade.authorizeAccess(lookup -> {
final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable();
processGroup.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
});
// get all the input ports
final Set<PortEntity> inputPorts = serviceFacade.getInputPorts(groupId);
final InputPortsEntity entity = new InputPortsEntity();
entity.setInputPorts(inputPortResource.populateRemainingInputPortEntitiesContent(inputPorts));
// generate the response
return clusterContext(generateOkResponse(entity)).build();
}
// ------------
// output ports
// ------------
/**
* Creates a new output port.
*
* @param httpServletRequest request
* @param groupId The group id
* @param requestPortEntity A outputPortEntity.
* @return A outputPortEntity.
*/
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}/output-ports")
@ApiOperation(
value = "Creates an output port",
response = PortEntity.class,
authorizations = {
@Authorization(value = "Write - /process-groups/{uuid}", type = "")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response createOutputPort(
@Context final HttpServletRequest httpServletRequest,
@ApiParam(
value = "The process group id.",
required = true
)
@PathParam("id") final String groupId,
@ApiParam(
value = "The output port configuration.",
required = true
) final PortEntity requestPortEntity) {
if (requestPortEntity == null || requestPortEntity.getComponent() == null) {
throw new IllegalArgumentException("Port details must be specified.");
}
if (requestPortEntity.getRevision() == null || (requestPortEntity.getRevision().getVersion() == null || requestPortEntity.getRevision().getVersion() != 0)) {
throw new IllegalArgumentException("A revision of 0 must be specified when creating a new Output port.");
}
if (requestPortEntity.getComponent().getId() != null) {
throw new IllegalArgumentException("Output port ID cannot be specified.");
}
final PositionDTO proposedPosition = requestPortEntity.getComponent().getPosition();
if (proposedPosition != null) {
if (proposedPosition.getX() == null || proposedPosition.getY() == null) {
throw new IllegalArgumentException("The x and y coordinate of the proposed position must be specified.");
}
}
if (requestPortEntity.getComponent().getParentGroupId() != null && !groupId.equals(requestPortEntity.getComponent().getParentGroupId())) {
throw new IllegalArgumentException(String.format("If specified, the parent process group id %s must be the same as specified in the URI %s",
requestPortEntity.getComponent().getParentGroupId(), groupId));
}
requestPortEntity.getComponent().setParentGroupId(groupId);
if (isReplicateRequest()) {
return replicate(HttpMethod.POST, requestPortEntity);
}
return withWriteLock(
serviceFacade,
requestPortEntity,
lookup -> {
final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable();
processGroup.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
},
null,
portEntity -> {
// set the processor id as appropriate
portEntity.getComponent().setId(generateUuid());
// create the output port and generate the json
final Revision revision = getRevision(portEntity, portEntity.getComponent().getId());
final PortEntity entity = serviceFacade.createOutputPort(revision, groupId, portEntity.getComponent());
outputPortResource.populateRemainingOutputPortEntityContent(entity);
// build the response
return clusterContext(generateCreatedResponse(URI.create(entity.getUri()), entity)).build();
}
);
}
/**
* Retrieves all the of output ports in this NiFi.
*
* @return A outputPortsEntity.
*/
@GET
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}/output-ports")
@ApiOperation(
value = "Gets all output ports",
response = OutputPortsEntity.class,
authorizations = {
@Authorization(value = "Read - /process-groups/{uuid}", type = "")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response getOutputPorts(
@ApiParam(
value = "The process group id.",
required = true
)
@PathParam("id") final String groupId) {
if (isReplicateRequest()) {
return replicate(HttpMethod.GET);
}
// authorize access
serviceFacade.authorizeAccess(lookup -> {
final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable();
processGroup.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
});
// get all the output ports
final Set<PortEntity> outputPorts = serviceFacade.getOutputPorts(groupId);
// create the response entity
final OutputPortsEntity entity = new OutputPortsEntity();
entity.setOutputPorts(outputPortResource.populateRemainingOutputPortEntitiesContent(outputPorts));
// generate the response
return clusterContext(generateOkResponse(entity)).build();
}
// -------
// funnels
// -------
/**
* Creates a new Funnel.
*
* @param httpServletRequest request
* @param groupId The group id
* @param requestFunnelEntity A funnelEntity.
* @return A funnelEntity.
*/
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}/funnels")
@ApiOperation(
value = "Creates a funnel",
response = FunnelEntity.class,
authorizations = {
@Authorization(value = "Write - /process-groups/{uuid}", type = "")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response createFunnel(
@Context final HttpServletRequest httpServletRequest,
@ApiParam(
value = "The process group id.",
required = true
)
@PathParam("id") final String groupId,
@ApiParam(
value = "The funnel configuration details.",
required = true
) final FunnelEntity requestFunnelEntity) {
if (requestFunnelEntity == null || requestFunnelEntity.getComponent() == null) {
throw new IllegalArgumentException("Funnel details must be specified.");
}
if (requestFunnelEntity.getRevision() == null || (requestFunnelEntity.getRevision().getVersion() == null || requestFunnelEntity.getRevision().getVersion() != 0)) {
throw new IllegalArgumentException("A revision of 0 must be specified when creating a new Funnel.");
}
if (requestFunnelEntity.getComponent().getId() != null) {
throw new IllegalArgumentException("Funnel ID cannot be specified.");
}
final PositionDTO proposedPosition = requestFunnelEntity.getComponent().getPosition();
if (proposedPosition != null) {
if (proposedPosition.getX() == null || proposedPosition.getY() == null) {
throw new IllegalArgumentException("The x and y coordinate of the proposed position must be specified.");
}
}
if (requestFunnelEntity.getComponent().getParentGroupId() != null && !groupId.equals(requestFunnelEntity.getComponent().getParentGroupId())) {
throw new IllegalArgumentException(String.format("If specified, the parent process group id %s must be the same as specified in the URI %s",
requestFunnelEntity.getComponent().getParentGroupId(), groupId));
}
requestFunnelEntity.getComponent().setParentGroupId(groupId);
if (isReplicateRequest()) {
return replicate(HttpMethod.POST, requestFunnelEntity);
}
return withWriteLock(
serviceFacade,
requestFunnelEntity,
lookup -> {
final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable();
processGroup.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
},
null,
funnelEntity -> {
// set the processor id as appropriate
funnelEntity.getComponent().setId(generateUuid());
// create the funnel and generate the json
final Revision revision = getRevision(funnelEntity, funnelEntity.getComponent().getId());
final FunnelEntity entity = serviceFacade.createFunnel(revision, groupId, funnelEntity.getComponent());
funnelResource.populateRemainingFunnelEntityContent(entity);
// build the response
return clusterContext(generateCreatedResponse(URI.create(entity.getUri()), entity)).build();
}
);
}
/**
* Retrieves all the of funnels in this NiFi.
*
* @return A funnelsEntity.
*/
@GET
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}/funnels")
@ApiOperation(
value = "Gets all funnels",
response = FunnelsEntity.class,
authorizations = {
@Authorization(value = "Read - /process-groups/{uuid}", type = "")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response getFunnels(
@ApiParam(
value = "The process group id.",
required = true
)
@PathParam("id") final String groupId) {
if (isReplicateRequest()) {
return replicate(HttpMethod.GET);
}
// authorize access
serviceFacade.authorizeAccess(lookup -> {
final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable();
processGroup.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
});
// get all the funnels
final Set<FunnelEntity> funnels = serviceFacade.getFunnels(groupId);
// create the response entity
final FunnelsEntity entity = new FunnelsEntity();
entity.setFunnels(funnelResource.populateRemainingFunnelEntitiesContent(funnels));
// generate the response
return clusterContext(generateOkResponse(entity)).build();
}
// ------
// labels
// ------
/**
* Creates a new Label.
*
* @param httpServletRequest request
* @param groupId The group id
* @param requestLabelEntity A labelEntity.
* @return A labelEntity.
*/
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}/labels")
@ApiOperation(
value = "Creates a label",
response = LabelEntity.class,
authorizations = {
@Authorization(value = "Write - /process-groups/{uuid}", type = "")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response createLabel(
@Context final HttpServletRequest httpServletRequest,
@ApiParam(
value = "The process group id.",
required = true
)
@PathParam("id") final String groupId,
@ApiParam(
value = "The label configuration details.",
required = true
) final LabelEntity requestLabelEntity) {
if (requestLabelEntity == null || requestLabelEntity.getComponent() == null) {
throw new IllegalArgumentException("Label details must be specified.");
}
if (requestLabelEntity.getRevision() == null || (requestLabelEntity.getRevision().getVersion() == null || requestLabelEntity.getRevision().getVersion() != 0)) {
throw new IllegalArgumentException("A revision of 0 must be specified when creating a new Label.");
}
if (requestLabelEntity.getComponent().getId() != null) {
throw new IllegalArgumentException("Label ID cannot be specified.");
}
final PositionDTO proposedPosition = requestLabelEntity.getComponent().getPosition();
if (proposedPosition != null) {
if (proposedPosition.getX() == null || proposedPosition.getY() == null) {
throw new IllegalArgumentException("The x and y coordinate of the proposed position must be specified.");
}
}
if (requestLabelEntity.getComponent().getParentGroupId() != null && !groupId.equals(requestLabelEntity.getComponent().getParentGroupId())) {
throw new IllegalArgumentException(String.format("If specified, the parent process group id %s must be the same as specified in the URI %s",
requestLabelEntity.getComponent().getParentGroupId(), groupId));
}
requestLabelEntity.getComponent().setParentGroupId(groupId);
if (isReplicateRequest()) {
return replicate(HttpMethod.POST, requestLabelEntity);
}
return withWriteLock(
serviceFacade,
requestLabelEntity,
lookup -> {
final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable();
processGroup.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
},
null,
labelEntity -> {
// set the processor id as appropriate
labelEntity.getComponent().setId(generateUuid());
// create the label and generate the json
final Revision revision = getRevision(labelEntity, labelEntity.getComponent().getId());
final LabelEntity entity = serviceFacade.createLabel(revision, groupId, labelEntity.getComponent());
labelResource.populateRemainingLabelEntityContent(entity);
// build the response
return clusterContext(generateCreatedResponse(URI.create(entity.getUri()), entity)).build();
}
);
}
/**
* Retrieves all the of labels in this NiFi.
*
* @return A labelsEntity.
*/
@GET
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}/labels")
@ApiOperation(
value = "Gets all labels",
response = LabelsEntity.class,
authorizations = {
@Authorization(value = "Read - /process-groups/{uuid}", type = "")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response getLabels(
@ApiParam(
value = "The process group id.",
required = true
)
@PathParam("id") final String groupId) {
if (isReplicateRequest()) {
return replicate(HttpMethod.GET);
}
// authorize access
serviceFacade.authorizeAccess(lookup -> {
final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable();
processGroup.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
});
// get all the labels
final Set<LabelEntity> labels = serviceFacade.getLabels(groupId);
// create the response entity
final LabelsEntity entity = new LabelsEntity();
entity.setLabels(labelResource.populateRemainingLabelEntitiesContent(labels));
// generate the response
return clusterContext(generateOkResponse(entity)).build();
}
// ---------------------
// remote process groups
// ---------------------
/**
* Creates a new remote process group.
*
* @param httpServletRequest request
* @param groupId The group id
* @param requestRemoteProcessGroupEntity A remoteProcessGroupEntity.
* @return A remoteProcessGroupEntity.
*/
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}/remote-process-groups")
@ApiOperation(
value = "Creates a new process group",
response = RemoteProcessGroupEntity.class,
authorizations = {
@Authorization(value = "Write - /process-groups/{uuid}", type = "")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response createRemoteProcessGroup(
@Context final HttpServletRequest httpServletRequest,
@ApiParam(
value = "The process group id.",
required = true
)
@PathParam("id") final String groupId,
@ApiParam(
value = "The remote process group configuration details.",
required = true
) final RemoteProcessGroupEntity requestRemoteProcessGroupEntity) {
if (requestRemoteProcessGroupEntity == null || requestRemoteProcessGroupEntity.getComponent() == null) {
throw new IllegalArgumentException("Remote process group details must be specified.");
}
if (requestRemoteProcessGroupEntity.getRevision() == null
|| (requestRemoteProcessGroupEntity.getRevision().getVersion() == null || requestRemoteProcessGroupEntity.getRevision().getVersion() != 0)) {
throw new IllegalArgumentException("A revision of 0 must be specified when creating a new Remote process group.");
}
final RemoteProcessGroupDTO requestRemoteProcessGroupDTO = requestRemoteProcessGroupEntity.getComponent();
if (requestRemoteProcessGroupDTO.getId() != null) {
throw new IllegalArgumentException("Remote process group ID cannot be specified.");
}
if (requestRemoteProcessGroupDTO.getTargetUri() == null) {
throw new IllegalArgumentException("The URI of the process group must be specified.");
}
final PositionDTO proposedPosition = requestRemoteProcessGroupDTO.getPosition();
if (proposedPosition != null) {
if (proposedPosition.getX() == null || proposedPosition.getY() == null) {
throw new IllegalArgumentException("The x and y coordinate of the proposed position must be specified.");
}
}
if (requestRemoteProcessGroupDTO.getParentGroupId() != null && !groupId.equals(requestRemoteProcessGroupDTO.getParentGroupId())) {
throw new IllegalArgumentException(String.format("If specified, the parent process group id %s must be the same as specified in the URI %s",
requestRemoteProcessGroupDTO.getParentGroupId(), groupId));
}
requestRemoteProcessGroupDTO.setParentGroupId(groupId);
if (isReplicateRequest()) {
return replicate(HttpMethod.POST, requestRemoteProcessGroupEntity);
}
return withWriteLock(
serviceFacade,
requestRemoteProcessGroupEntity,
lookup -> {
final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable();
processGroup.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
},
null,
remoteProcessGroupEntity -> {
final RemoteProcessGroupDTO remoteProcessGroupDTO = remoteProcessGroupEntity.getComponent();
// set the processor id as appropriate
remoteProcessGroupDTO.setId(generateUuid());
// parse the uri to check if the uri is valid
final String targetUris = remoteProcessGroupDTO.getTargetUris();
SiteToSiteRestApiClient.parseClusterUrls(targetUris);
// since the uri is valid, use it
remoteProcessGroupDTO.setTargetUris(targetUris);
// create the remote process group
final Revision revision = getRevision(remoteProcessGroupEntity, remoteProcessGroupDTO.getId());
final RemoteProcessGroupEntity entity = serviceFacade.createRemoteProcessGroup(revision, groupId, remoteProcessGroupDTO);
remoteProcessGroupResource.populateRemainingRemoteProcessGroupEntityContent(entity);
return clusterContext(generateCreatedResponse(URI.create(entity.getUri()), entity)).build();
}
);
}
/**
* Retrieves all the of remote process groups in this NiFi.
*
* @return A remoteProcessGroupEntity.
*/
@GET
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}/remote-process-groups")
@ApiOperation(
value = "Gets all remote process groups",
response = RemoteProcessGroupsEntity.class,
authorizations = {
@Authorization(value = "Read - /process-groups/{uuid}", type = "")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response getRemoteProcessGroups(
@ApiParam(
value = "The process group id.",
required = true
)
@PathParam("id") final String groupId) {
if (isReplicateRequest()) {
return replicate(HttpMethod.GET);
}
// authorize access
serviceFacade.authorizeAccess(lookup -> {
final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable();
processGroup.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
});
// get all the remote process groups
final Set<RemoteProcessGroupEntity> remoteProcessGroups = serviceFacade.getRemoteProcessGroups(groupId);
// prune response as necessary
for (RemoteProcessGroupEntity remoteProcessGroupEntity : remoteProcessGroups) {
if (remoteProcessGroupEntity.getComponent() != null) {
remoteProcessGroupEntity.getComponent().setContents(null);
}
}
// create the response entity
final RemoteProcessGroupsEntity entity = new RemoteProcessGroupsEntity();
entity.setRemoteProcessGroups(remoteProcessGroupResource.populateRemainingRemoteProcessGroupEntitiesContent(remoteProcessGroups));
// generate the response
return clusterContext(generateOkResponse(entity)).build();
}
// -----------
// connections
// -----------
/**
* Creates a new connection.
*
* @param httpServletRequest request
* @param groupId The group id
* @param requestConnectionEntity A connectionEntity.
* @return A connectionEntity.
*/
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}/connections")
@ApiOperation(
value = "Creates a connection",
response = ConnectionEntity.class,
authorizations = {
@Authorization(value = "Write - /process-groups/{uuid}", type = ""),
@Authorization(value = "Write Source - /{component-type}/{uuid}", type = ""),
@Authorization(value = "Write Destination - /{component-type}/{uuid}", type = "")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response createConnection(
@Context final HttpServletRequest httpServletRequest,
@ApiParam(
value = "The process group id.",
required = true
)
@PathParam("id") final String groupId,
@ApiParam(
value = "The connection configuration details.",
required = true
) final ConnectionEntity requestConnectionEntity) {
if (requestConnectionEntity == null || requestConnectionEntity.getComponent() == null) {
throw new IllegalArgumentException("Connection details must be specified.");
}
if (requestConnectionEntity.getRevision() == null || (requestConnectionEntity.getRevision().getVersion() == null || requestConnectionEntity.getRevision().getVersion() != 0)) {
throw new IllegalArgumentException("A revision of 0 must be specified when creating a new Connection.");
}
if (requestConnectionEntity.getComponent().getId() != null) {
throw new IllegalArgumentException("Connection ID cannot be specified.");
}
final List<PositionDTO> proposedBends = requestConnectionEntity.getComponent().getBends();
if (proposedBends != null) {
for (final PositionDTO proposedBend : proposedBends) {
if (proposedBend.getX() == null || proposedBend.getY() == null) {
throw new IllegalArgumentException("The x and y coordinate of the each bend must be specified.");
}
}
}
if (requestConnectionEntity.getComponent().getParentGroupId() != null && !groupId.equals(requestConnectionEntity.getComponent().getParentGroupId())) {
throw new IllegalArgumentException(String.format("If specified, the parent process group id %s must be the same as specified in the URI %s",
requestConnectionEntity.getComponent().getParentGroupId(), groupId));
}
requestConnectionEntity.getComponent().setParentGroupId(groupId);
// get the connection
final ConnectionDTO requestConnection = requestConnectionEntity.getComponent();
if (requestConnection.getSource() == null || requestConnection.getSource().getId() == null) {
throw new IllegalArgumentException("The source of the connection must be specified.");
}
if (requestConnection.getSource().getType() == null) {
throw new IllegalArgumentException("The type of the source of the connection must be specified.");
}
final ConnectableType sourceConnectableType;
try {
sourceConnectableType = ConnectableType.valueOf(requestConnection.getSource().getType());
} catch (final IllegalArgumentException e) {
throw new IllegalArgumentException(String.format("Unrecognized source type %s. Expected values are [%s]",
requestConnection.getSource().getType(), StringUtils.join(ConnectableType.values(), ", ")));
}
if (requestConnection.getDestination() == null || requestConnection.getDestination().getId() == null) {
throw new IllegalArgumentException("The destination of the connection must be specified.");
}
if (requestConnection.getDestination().getType() == null) {
throw new IllegalArgumentException("The type of the destination of the connection must be specified.");
}
final ConnectableType destinationConnectableType;
try {
destinationConnectableType = ConnectableType.valueOf(requestConnection.getDestination().getType());
} catch (final IllegalArgumentException e) {
throw new IllegalArgumentException(String.format("Unrecognized destination type %s. Expected values are [%s]",
requestConnection.getDestination().getType(), StringUtils.join(ConnectableType.values(), ", ")));
}
if (isReplicateRequest()) {
return replicate(HttpMethod.POST, requestConnectionEntity);
}
return withWriteLock(
serviceFacade,
requestConnectionEntity,
lookup -> {
// ensure write access to the group
final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable();
processGroup.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
// explicitly handle RPGs differently as the connectable id can be ambiguous if self referencing
final Authorizable source;
if (ConnectableType.REMOTE_OUTPUT_PORT.equals(sourceConnectableType)) {
source = lookup.getRemoteProcessGroup(requestConnection.getSource().getGroupId());
} else {
source = lookup.getLocalConnectable(requestConnection.getSource().getId());
}
// ensure write access to the source
if (source == null) {
throw new ResourceNotFoundException("Cannot find source component with ID [" + requestConnection.getSource().getId() + "]");
}
source.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
// explicitly handle RPGs differently as the connectable id can be ambiguous if self referencing
final Authorizable destination;
if (ConnectableType.REMOTE_INPUT_PORT.equals(destinationConnectableType)) {
destination = lookup.getRemoteProcessGroup(requestConnection.getDestination().getGroupId());
} else {
destination = lookup.getLocalConnectable(requestConnection.getDestination().getId());
}
// ensure write access to the destination
if (destination == null) {
throw new ResourceNotFoundException("Cannot find destination component with ID [" + requestConnection.getDestination().getId() + "]");
}
destination.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
},
() -> serviceFacade.verifyCreateConnection(groupId, requestConnection),
connectionEntity -> {
final ConnectionDTO connection = connectionEntity.getComponent();
// set the processor id as appropriate
connection.setId(generateUuid());
// create the new relationship target
final Revision revision = getRevision(connectionEntity, connection.getId());
final ConnectionEntity entity = serviceFacade.createConnection(revision, groupId, connection);
connectionResource.populateRemainingConnectionEntityContent(entity);
// extract the href and build the response
String uri = entity.getUri();
return clusterContext(generateCreatedResponse(URI.create(uri), entity)).build();
}
);
}
/**
* Gets all the connections.
*
* @return A connectionsEntity.
*/
@GET
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}/connections")
@ApiOperation(
value = "Gets all connections",
response = ConnectionsEntity.class,
authorizations = {
@Authorization(value = "Read - /process-groups/{uuid}", type = "")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response getConnections(
@ApiParam(
value = "The process group id.",
required = true
)
@PathParam("id") String groupId) {
if (isReplicateRequest()) {
return replicate(HttpMethod.GET);
}
// authorize access
serviceFacade.authorizeAccess(lookup -> {
final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable();
processGroup.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
});
// all of the relationships for the specified source processor
Set<ConnectionEntity> connections = serviceFacade.getConnections(groupId);
// create the client response entity
ConnectionsEntity entity = new ConnectionsEntity();
entity.setConnections(connectionResource.populateRemainingConnectionEntitiesContent(connections));
// generate the response
return clusterContext(generateOkResponse(entity)).build();
}
// ----------------
// snippet instance
// ----------------
/**
* Copies the specified snippet within this ProcessGroup. The snippet instance that is instantiated cannot be referenced at a later time, therefore there is no
* corresponding URI. Instead the request URI is returned.
* <p>
* Alternatively, we could have performed a PUT request. However, PUT requests are supposed to be idempotent and this endpoint is certainly not.
*
* @param httpServletRequest request
* @param groupId The group id
* @param requestCopySnippetEntity The copy snippet request
* @return A flowSnippetEntity.
*/
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}/snippet-instance")
@ApiOperation(
value = "Copies a snippet and discards it.",
response = FlowSnippetEntity.class,
authorizations = {
@Authorization(value = "Write - /process-groups/{uuid}", type = ""),
@Authorization(value = "Read - /{component-type}/{uuid} - For each component in the snippet and their descendant components", type = ""),
@Authorization(value = "Write - if the snippet contains any restricted Processors - /restricted-components", type = "")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response copySnippet(
@Context HttpServletRequest httpServletRequest,
@ApiParam(
value = "The process group id.",
required = true
)
@PathParam("id") String groupId,
@ApiParam(
value = "The copy snippet request.",
required = true
) CopySnippetRequestEntity requestCopySnippetEntity) {
// ensure the position has been specified
if (requestCopySnippetEntity == null || requestCopySnippetEntity.getOriginX() == null || requestCopySnippetEntity.getOriginY() == null) {
throw new IllegalArgumentException("The origin position (x, y) must be specified");
}
if (requestCopySnippetEntity.getSnippetId() == null) {
throw new IllegalArgumentException("The snippet id must be specified.");
}
if (isReplicateRequest()) {
return replicate(HttpMethod.POST, requestCopySnippetEntity);
}
return withWriteLock(
serviceFacade,
requestCopySnippetEntity,
lookup -> {
final NiFiUser user = NiFiUserUtils.getNiFiUser();
final SnippetAuthorizable snippet = authorizeSnippetUsage(lookup, groupId, requestCopySnippetEntity.getSnippetId(), false);
// flag to only perform the restricted check once, atomic reference so we can mark final and use in lambda
final AtomicBoolean restrictedCheckPerformed = new AtomicBoolean(false);
final Consumer<ComponentAuthorizable> authorizeRestricted = authorizable -> {
if (authorizable.isRestricted() && restrictedCheckPerformed.compareAndSet(false, true)) {
lookup.getRestrictedComponents().authorize(authorizer, RequestAction.WRITE, user);
}
};
// consider each processor. note - this request will not create new controller services so we do not need to check
// for if there are not restricted controller services. it will however, need to authorize the user has access
// to any referenced services and this is done within authorizeSnippetUsage above.
snippet.getSelectedProcessors().stream().forEach(authorizeRestricted);
snippet.getSelectedProcessGroups().stream().forEach(processGroup -> {
processGroup.getEncapsulatedProcessors().forEach(authorizeRestricted);
});
},
null,
copySnippetRequestEntity -> {
// copy the specified snippet
final FlowEntity flowEntity = serviceFacade.copySnippet(
groupId, copySnippetRequestEntity.getSnippetId(), copySnippetRequestEntity.getOriginX(), copySnippetRequestEntity.getOriginY(), getIdGenerationSeed().orElse(null));
// get the snippet
final FlowDTO flow = flowEntity.getFlow();
// prune response as necessary
for (ProcessGroupEntity childGroupEntity : flow.getProcessGroups()) {
childGroupEntity.getComponent().setContents(null);
}
// create the response entity
populateRemainingSnippetContent(flow);
// generate the response
return clusterContext(generateCreatedResponse(getAbsolutePath(), flowEntity)).build();
}
);
}
// -----------------
// template instance
// -----------------
/**
* Discovers the compatible bundle details for the components in the specified snippet.
*
* @param snippet the snippet
*/
private void discoverCompatibleBundles(final FlowSnippetDTO snippet) {
if (snippet.getProcessors() != null) {
snippet.getProcessors().forEach(processor -> {
final BundleCoordinate coordinate = BundleUtils.getCompatibleBundle(processor.getType(), processor.getBundle());
processor.setBundle(new BundleDTO(coordinate.getGroup(), coordinate.getId(), coordinate.getVersion()));
});
}
if (snippet.getControllerServices() != null) {
snippet.getControllerServices().forEach(controllerService -> {
final BundleCoordinate coordinate = BundleUtils.getCompatibleBundle(controllerService.getType(), controllerService.getBundle());
controllerService.setBundle(new BundleDTO(coordinate.getGroup(), coordinate.getId(), coordinate.getVersion()));
});
}
if (snippet.getProcessGroups() != null) {
snippet.getProcessGroups().forEach(processGroup -> {
discoverCompatibleBundles(processGroup.getContents());
});
}
}
/**
* Instantiates the specified template within this ProcessGroup. The template instance that is instantiated cannot be referenced at a later time, therefore there is no
* corresponding URI. Instead the request URI is returned.
* <p>
* Alternatively, we could have performed a PUT request. However, PUT requests are supposed to be idempotent and this endpoint is certainly not.
*
* @param httpServletRequest request
* @param groupId The group id
* @param requestInstantiateTemplateRequestEntity The instantiate template request
* @return A flowEntity.
*/
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}/template-instance")
@ApiOperation(
value = "Instantiates a template",
response = FlowEntity.class,
authorizations = {
@Authorization(value = "Write - /process-groups/{uuid}", type = ""),
@Authorization(value = "Read - /templates/{uuid}", type = ""),
@Authorization(value = "Write - if the template contains any restricted components - /restricted-components", type = "")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response instantiateTemplate(
@Context HttpServletRequest httpServletRequest,
@ApiParam(
value = "The process group id.",
required = true
)
@PathParam("id") String groupId,
@ApiParam(
value = "The instantiate template request.",
required = true
) InstantiateTemplateRequestEntity requestInstantiateTemplateRequestEntity) {
// ensure the position has been specified
if (requestInstantiateTemplateRequestEntity == null || requestInstantiateTemplateRequestEntity.getOriginX() == null || requestInstantiateTemplateRequestEntity.getOriginY() == null) {
throw new IllegalArgumentException("The origin position (x, y) must be specified.");
}
// ensure the template id was provided
if (requestInstantiateTemplateRequestEntity.getTemplateId() == null) {
throw new IllegalArgumentException("The template id must be specified.");
}
// ensure the template encoding version is valid
if (requestInstantiateTemplateRequestEntity.getEncodingVersion() != null) {
try {
FlowEncodingVersion.parse(requestInstantiateTemplateRequestEntity.getEncodingVersion());
} catch (final IllegalArgumentException e) {
throw new IllegalArgumentException("The template encoding version is not valid. The expected format is <number>.<number>");
}
}
// populate the encoding version if necessary
if (requestInstantiateTemplateRequestEntity.getEncodingVersion() == null) {
// if the encoding version is not specified, use the latest encoding version as these options were
// not available pre 1.x, will be overridden if populating from the underlying template below
requestInstantiateTemplateRequestEntity.setEncodingVersion(TemplateDTO.MAX_ENCODING_VERSION);
}
// populate the component bundles if necessary
if (requestInstantiateTemplateRequestEntity.getSnippet() == null) {
// get the desired template in order to determine the supported bundles
final TemplateDTO requestedTemplate = serviceFacade.exportTemplate(requestInstantiateTemplateRequestEntity.getTemplateId());
final FlowSnippetDTO requestTemplateContents = requestedTemplate.getSnippet();
// determine the compatible bundles to use for each component in this template, this ensures the nodes in the cluster
// instantiate the components from the same bundles
discoverCompatibleBundles(requestTemplateContents);
// update the requested template as necessary - use the encoding version from the underlying template
requestInstantiateTemplateRequestEntity.setEncodingVersion(requestedTemplate.getEncodingVersion());
requestInstantiateTemplateRequestEntity.setSnippet(requestTemplateContents);
}
if (isReplicateRequest()) {
return replicate(HttpMethod.POST, requestInstantiateTemplateRequestEntity);
}
return withWriteLock(
serviceFacade,
requestInstantiateTemplateRequestEntity,
lookup -> {
final NiFiUser user = NiFiUserUtils.getNiFiUser();
// ensure write on the group
final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable();
processGroup.authorize(authorizer, RequestAction.WRITE, user);
final Authorizable template = lookup.getTemplate(requestInstantiateTemplateRequestEntity.getTemplateId());
template.authorize(authorizer, RequestAction.READ, user);
// ensure read on the template
final TemplateContentsAuthorizable templateContents = lookup.getTemplateContents(requestInstantiateTemplateRequestEntity.getSnippet());
// flag to only perform the restricted check once, atomic reference so we can mark final and use in lambda
final AtomicBoolean restrictedCheckPerformed = new AtomicBoolean(false);
final Consumer<ComponentAuthorizable> authorizeRestricted = authorizable -> {
if (authorizable.isRestricted() && restrictedCheckPerformed.compareAndSet(false, true)) {
lookup.getRestrictedComponents().authorize(authorizer, RequestAction.WRITE, user);
}
};
// ensure restricted access if necessary
templateContents.getEncapsulatedProcessors().forEach(authorizeRestricted);
templateContents.getEncapsulatedControllerServices().forEach(authorizeRestricted);
},
() -> serviceFacade.verifyComponentTypes(requestInstantiateTemplateRequestEntity.getSnippet()),
instantiateTemplateRequestEntity -> {
// create the template and generate the json
final FlowEntity entity = serviceFacade.createTemplateInstance(groupId, instantiateTemplateRequestEntity.getOriginX(), instantiateTemplateRequestEntity.getOriginY(),
instantiateTemplateRequestEntity.getEncodingVersion(), instantiateTemplateRequestEntity.getSnippet(), getIdGenerationSeed().orElse(null));
final FlowDTO flowSnippet = entity.getFlow();
// prune response as necessary
for (ProcessGroupEntity childGroupEntity : flowSnippet.getProcessGroups()) {
childGroupEntity.getComponent().setContents(null);
}
// create the response entity
populateRemainingSnippetContent(flowSnippet);
// generate the response
return clusterContext(generateCreatedResponse(getAbsolutePath(), entity)).build();
}
);
}
// ---------
// templates
// ---------
private SnippetAuthorizable authorizeSnippetUsage(final AuthorizableLookup lookup, final String groupId, final String snippetId, final boolean authorizeTransitiveServices) {
final NiFiUser user = NiFiUserUtils.getNiFiUser();
// ensure write access to the target process group
lookup.getProcessGroup(groupId).getAuthorizable().authorize(authorizer, RequestAction.WRITE, user);
// ensure read permission to every component in the snippet including referenced services
final SnippetAuthorizable snippet = lookup.getSnippet(snippetId);
authorizeSnippet(snippet, authorizer, lookup, RequestAction.READ, true, authorizeTransitiveServices);
return snippet;
}
/**
* Creates a new template based off of the specified template.
*
* @param httpServletRequest request
* @param requestCreateTemplateRequestEntity request to create the template
* @return A templateEntity
*/
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}/templates")
@ApiOperation(
value = "Creates a template and discards the specified snippet.",
response = TemplateEntity.class,
authorizations = {
@Authorization(value = "Write - /process-groups/{uuid}", type = ""),
@Authorization(value = "Read - /{component-type}/{uuid} - For each component in the snippet and their descendant components", type = "")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response createTemplate(
@Context final HttpServletRequest httpServletRequest,
@ApiParam(
value = "The process group id.",
required = true
)
@PathParam("id") final String groupId,
@ApiParam(
value = "The create template request.",
required = true
) final CreateTemplateRequestEntity requestCreateTemplateRequestEntity) {
if (requestCreateTemplateRequestEntity.getSnippetId() == null) {
throw new IllegalArgumentException("The snippet identifier must be specified.");
}
if (isReplicateRequest()) {
return replicate(HttpMethod.POST, requestCreateTemplateRequestEntity);
}
return withWriteLock(
serviceFacade,
requestCreateTemplateRequestEntity,
lookup -> {
authorizeSnippetUsage(lookup, groupId, requestCreateTemplateRequestEntity.getSnippetId(), true);
},
() -> serviceFacade.verifyCanAddTemplate(groupId, requestCreateTemplateRequestEntity.getName()),
createTemplateRequestEntity -> {
// create the template and generate the json
final TemplateDTO template = serviceFacade.createTemplate(createTemplateRequestEntity.getName(), createTemplateRequestEntity.getDescription(),
createTemplateRequestEntity.getSnippetId(), groupId, getIdGenerationSeed());
templateResource.populateRemainingTemplateContent(template);
// build the response entity
final TemplateEntity entity = new TemplateEntity();
entity.setTemplate(template);
// build the response
return clusterContext(generateCreatedResponse(URI.create(template.getUri()), entity)).build();
}
);
}
/**
* Imports the specified template.
*
* @param httpServletRequest request
* @param in The template stream
* @return A templateEntity or an errorResponse XML snippet.
* @throws InterruptedException if interrupted
*/
@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.APPLICATION_XML)
@Path("{id}/templates/upload")
@ApiOperation(
value = "Uploads a template",
response = TemplateEntity.class,
authorizations = {
@Authorization(value = "Write - /process-groups/{uuid}", type = "")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response uploadTemplate(
@Context final HttpServletRequest httpServletRequest,
@Context final UriInfo uriInfo,
@ApiParam(
value = "The process group id.",
required = true
)
@PathParam("id") final String groupId,
@FormDataParam("template") final InputStream in) throws InterruptedException {
// unmarshal the template
final TemplateDTO template;
try {
JAXBContext context = JAXBContext.newInstance(TemplateDTO.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
JAXBElement<TemplateDTO> templateElement = unmarshaller.unmarshal(new StreamSource(in), TemplateDTO.class);
template = templateElement.getValue();
} catch (JAXBException jaxbe) {
logger.warn("An error occurred while parsing a template.", jaxbe);
String responseXml = String.format("<errorResponse status=\"%s\" statusText=\"The specified template is not in a valid format.\"/>", Response.Status.BAD_REQUEST.getStatusCode());
return Response.status(Response.Status.OK).entity(responseXml).type("application/xml").build();
} catch (IllegalArgumentException iae) {
logger.warn("Unable to import template.", iae);
String responseXml = String.format("<errorResponse status=\"%s\" statusText=\"%s\"/>", Response.Status.BAD_REQUEST.getStatusCode(), iae.getMessage());
return Response.status(Response.Status.OK).entity(responseXml).type("application/xml").build();
} catch (Exception e) {
logger.warn("An error occurred while importing a template.", e);
String responseXml = String.format("<errorResponse status=\"%s\" statusText=\"Unable to import the specified template: %s\"/>",
Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e.getMessage());
return Response.status(Response.Status.OK).entity(responseXml).type("application/xml").build();
}
// build the response entity
TemplateEntity entity = new TemplateEntity();
entity.setTemplate(template);
if (isReplicateRequest()) {
// convert request accordingly
final UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
uriBuilder.segment("process-groups", groupId, "templates", "import");
final URI importUri = uriBuilder.build();
// change content type to XML for serializing entity
final Map<String, String> headersToOverride = new HashMap<>();
headersToOverride.put("content-type", MediaType.APPLICATION_XML);
// Determine whether we should replicate only to the cluster coordinator, or if we should replicate directly
// to the cluster nodes themselves.
if (getReplicationTarget() == ReplicationTarget.CLUSTER_NODES) {
return getRequestReplicator().replicate(HttpMethod.POST, importUri, entity, getHeaders(headersToOverride)).awaitMergedResponse().getResponse();
} else {
return getRequestReplicator().forwardToCoordinator(
getClusterCoordinatorNode(), HttpMethod.POST, importUri, entity, getHeaders(headersToOverride)).awaitMergedResponse().getResponse();
}
}
// otherwise import the template locally
return importTemplate(httpServletRequest, groupId, entity);
}
/**
* Imports the specified template.
*
* @param httpServletRequest request
* @param requestTemplateEntity A templateEntity.
* @return A templateEntity.
*/
@POST
@Consumes(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_XML)
@Path("{id}/templates/import")
@ApiOperation(
value = "Imports a template",
response = TemplateEntity.class,
authorizations = {
@Authorization(value = "Write - /process-groups/{uuid}", type = "")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response importTemplate(
@Context final HttpServletRequest httpServletRequest,
@ApiParam(
value = "The process group id.",
required = true
)
@PathParam("id") final String groupId,
final TemplateEntity requestTemplateEntity) {
// verify the template was specified
if (requestTemplateEntity == null || requestTemplateEntity.getTemplate() == null || requestTemplateEntity.getTemplate().getSnippet() == null) {
throw new IllegalArgumentException("Template details must be specified.");
}
if (isReplicateRequest()) {
return replicate(HttpMethod.POST, requestTemplateEntity);
}
return withWriteLock(
serviceFacade,
requestTemplateEntity,
lookup -> {
final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable();
processGroup.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
},
() -> serviceFacade.verifyCanAddTemplate(groupId, requestTemplateEntity.getTemplate().getName()),
templateEntity -> {
try {
// import the template
final TemplateDTO template = serviceFacade.importTemplate(templateEntity.getTemplate(), groupId, getIdGenerationSeed());
templateResource.populateRemainingTemplateContent(template);
// build the response entity
TemplateEntity entity = new TemplateEntity();
entity.setTemplate(template);
// build the response
return clusterContext(generateCreatedResponse(URI.create(template.getUri()), entity)).build();
} catch (IllegalArgumentException | IllegalStateException e) {
logger.info("Unable to import template: " + e);
String responseXml = String.format("<errorResponse status=\"%s\" statusText=\"%s\"/>", Response.Status.BAD_REQUEST.getStatusCode(), e.getMessage());
return Response.status(Response.Status.OK).entity(responseXml).type("application/xml").build();
} catch (Exception e) {
logger.warn("An error occurred while importing a template.", e);
String responseXml = String.format("<errorResponse status=\"%s\" statusText=\"Unable to import the specified template: %s\"/>",
Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e.getMessage());
return Response.status(Response.Status.OK).entity(responseXml).type("application/xml").build();
}
}
);
}
// -------------------
// controller services
// -------------------
/**
* Creates a new Controller Service.
*
* @param httpServletRequest request
* @param requestControllerServiceEntity A controllerServiceEntity.
* @return A controllerServiceEntity.
*/
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}/controller-services")
@ApiOperation(
value = "Creates a new controller service",
response = ControllerServiceEntity.class,
authorizations = {
@Authorization(value = "Write - /process-groups/{uuid}", type = ""),
@Authorization(value = "Read - any referenced Controller Services - /controller-services/{uuid}", type = ""),
@Authorization(value = "Write - if the Controller Service is restricted - /restricted-components", type = "")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response createControllerService(
@Context final HttpServletRequest httpServletRequest,
@ApiParam(
value = "The process group id.",
required = true
)
@PathParam("id") final String groupId,
@ApiParam(
value = "The controller service configuration details.",
required = true
) final ControllerServiceEntity requestControllerServiceEntity) {
if (requestControllerServiceEntity == null || requestControllerServiceEntity.getComponent() == null) {
throw new IllegalArgumentException("Controller service details must be specified.");
}
if (requestControllerServiceEntity.getRevision() == null
|| (requestControllerServiceEntity.getRevision().getVersion() == null || requestControllerServiceEntity.getRevision().getVersion() != 0)) {
throw new IllegalArgumentException("A revision of 0 must be specified when creating a new Controller service.");
}
final ControllerServiceDTO requestControllerService = requestControllerServiceEntity.getComponent();
if (requestControllerService.getId() != null) {
throw new IllegalArgumentException("Controller service ID cannot be specified.");
}
if (StringUtils.isBlank(requestControllerService.getType())) {
throw new IllegalArgumentException("The type of controller service to create must be specified.");
}
if (requestControllerService.getParentGroupId() != null && !groupId.equals(requestControllerService.getParentGroupId())) {
throw new IllegalArgumentException(String.format("If specified, the parent process group id %s must be the same as specified in the URI %s",
requestControllerService.getParentGroupId(), groupId));
}
requestControllerService.setParentGroupId(groupId);
if (isReplicateRequest()) {
return replicate(HttpMethod.POST, requestControllerServiceEntity);
}
return withWriteLock(
serviceFacade,
requestControllerServiceEntity,
lookup -> {
final NiFiUser user = NiFiUserUtils.getNiFiUser();
final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable();
processGroup.authorize(authorizer, RequestAction.WRITE, user);
ComponentAuthorizable authorizable = null;
try {
authorizable = lookup.getConfigurableComponent(requestControllerService.getType(), requestControllerService.getBundle());
if (authorizable.isRestricted()) {
lookup.getRestrictedComponents().authorize(authorizer, RequestAction.WRITE, user);
}
if (requestControllerService.getProperties() != null) {
AuthorizeControllerServiceReference.authorizeControllerServiceReferences(requestControllerService.getProperties(), authorizable, authorizer, lookup);
}
} finally {
if (authorizable != null) {
authorizable.cleanUpResources();
}
}
},
() -> serviceFacade.verifyCreateControllerService(requestControllerService),
controllerServiceEntity -> {
final ControllerServiceDTO controllerService = controllerServiceEntity.getComponent();
// set the processor id as appropriate
controllerService.setId(generateUuid());
// create the controller service and generate the json
final Revision revision = getRevision(controllerServiceEntity, controllerService.getId());
final ControllerServiceEntity entity = serviceFacade.createControllerService(revision, groupId, controllerService);
controllerServiceResource.populateRemainingControllerServiceEntityContent(entity);
// build the response
return clusterContext(generateCreatedResponse(URI.create(entity.getUri()), entity)).build();
}
);
}
// setters
public void setServiceFacade(NiFiServiceFacade serviceFacade) {
this.serviceFacade = serviceFacade;
}
public void setProcessorResource(ProcessorResource processorResource) {
this.processorResource = processorResource;
}
public void setInputPortResource(InputPortResource inputPortResource) {
this.inputPortResource = inputPortResource;
}
public void setOutputPortResource(OutputPortResource outputPortResource) {
this.outputPortResource = outputPortResource;
}
public void setFunnelResource(FunnelResource funnelResource) {
this.funnelResource = funnelResource;
}
public void setLabelResource(LabelResource labelResource) {
this.labelResource = labelResource;
}
public void setRemoteProcessGroupResource(RemoteProcessGroupResource remoteProcessGroupResource) {
this.remoteProcessGroupResource = remoteProcessGroupResource;
}
public void setConnectionResource(ConnectionResource connectionResource) {
this.connectionResource = connectionResource;
}
public void setTemplateResource(TemplateResource templateResource) {
this.templateResource = templateResource;
}
public void setControllerServiceResource(ControllerServiceResource controllerServiceResource) {
this.controllerServiceResource = controllerServiceResource;
}
public void setAuthorizer(Authorizer authorizer) {
this.authorizer = authorizer;
}
}