http://git-wip-us.apache.org/repos/asf/nifi/blob/b330fd16/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectionResource.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectionResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectionResource.java index 6741348..5831d6d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectionResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectionResource.java @@ -22,65 +22,79 @@ 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 java.net.URI; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import javax.servlet.http.HttpServletRequest; - -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.FormParam; -import javax.ws.rs.GET; -import javax.ws.rs.HttpMethod; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; - +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.cluster.context.ClusterContext; +import org.apache.nifi.cluster.context.ClusterContextThreadLocal; +import org.apache.nifi.cluster.manager.exception.UnknownNodeException; import org.apache.nifi.cluster.manager.impl.WebClusterManager; +import org.apache.nifi.cluster.node.Node; +import org.apache.nifi.cluster.protocol.NodeIdentifier; +import org.apache.nifi.controller.queue.SortColumn; +import org.apache.nifi.controller.queue.SortDirection; +import org.apache.nifi.stream.io.StreamUtils; import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.web.ConfigurationSnapshot; +import org.apache.nifi.web.DownloadableContent; import org.apache.nifi.web.IllegalClusterResourceRequestException; import org.apache.nifi.web.NiFiServiceFacade; import org.apache.nifi.web.Revision; -import static org.apache.nifi.web.api.ApplicationResource.CLIENT_ID; import org.apache.nifi.web.api.dto.ConnectableDTO; import org.apache.nifi.web.api.dto.ConnectionDTO; +import org.apache.nifi.web.api.dto.DropRequestDTO; +import org.apache.nifi.web.api.dto.FlowFileDTO; +import org.apache.nifi.web.api.dto.FlowFileSummaryDTO; +import org.apache.nifi.web.api.dto.ListingRequestDTO; import org.apache.nifi.web.api.dto.PositionDTO; import org.apache.nifi.web.api.dto.RevisionDTO; import org.apache.nifi.web.api.dto.status.StatusHistoryDTO; import org.apache.nifi.web.api.entity.ConnectionEntity; import org.apache.nifi.web.api.entity.ConnectionsEntity; +import org.apache.nifi.web.api.entity.DropRequestEntity; +import org.apache.nifi.web.api.entity.FlowFileEntity; +import org.apache.nifi.web.api.entity.ListingRequestEntity; import org.apache.nifi.web.api.entity.StatusHistoryEntity; import org.apache.nifi.web.api.request.ClientIdParameter; import org.apache.nifi.web.api.request.ConnectableTypeParameter; import org.apache.nifi.web.api.request.IntegerParameter; import org.apache.nifi.web.api.request.LongParameter; - -import org.apache.commons.lang3.StringUtils; -import org.apache.nifi.cluster.context.ClusterContext; -import org.apache.nifi.cluster.context.ClusterContextThreadLocal; -import org.apache.nifi.web.api.dto.DropRequestDTO; -import org.apache.nifi.web.api.entity.DropRequestEntity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.access.prepost.PreAuthorize; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.HttpMethod; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.StreamingOutput; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + /** * RESTful endpoint for managing a Connection. */ @@ -95,7 +109,7 @@ public class ConnectionResource extends ApplicationResource { private String groupId; /** - * Populate the uri's for the specified processors and their relationships. + * Populate the URIs for the specified connections. * * @param connections connections * @return dtos @@ -108,7 +122,10 @@ public class ConnectionResource extends ApplicationResource { } /** - * Populate the uri's for the specified processor and its relationships. + * Populate the URIs for the specified connection. + * + * @param connection connection + * @return dto */ private ConnectionDTO populateRemainingConnectionContent(ConnectionDTO connection) { // populate the remaining properties @@ -117,6 +134,38 @@ public class ConnectionResource extends ApplicationResource { } /** + * Populate the URIs for the specified flowfile listing. + * + * @param connectionId connection + * @param flowFileListing flowfile listing + * @return dto + */ + public ListingRequestDTO populateRemainingFlowFileListingContent(final String connectionId, final ListingRequestDTO flowFileListing) { + // uri of the listing + flowFileListing.setUri(generateResourceUri("controller", "process-groups", groupId, "connections", connectionId, "listing-requests", flowFileListing.getId())); + + // uri of each flowfile + if (flowFileListing.getFlowFileSummaries() != null) { + for (FlowFileSummaryDTO flowFile : flowFileListing.getFlowFileSummaries()) { + populateRemainingFlowFileContent(connectionId, flowFile); + } + } + return flowFileListing; + } + + /** + * Populate the URIs for the specified flowfile. + * + * @param connectionId the connection id + * @param flowFile the flowfile + * @return the dto + */ + private FlowFileSummaryDTO populateRemainingFlowFileContent(final String connectionId, final FlowFileSummaryDTO flowFile) { + flowFile.setUri(generateResourceUri("controller", "process-groups", groupId, "connections", connectionId, "flowfiles", flowFile.getUuid())); + return flowFile; + } + + /** * Gets all the connections. * * @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. @@ -839,7 +888,7 @@ public class ConnectionResource extends ApplicationResource { @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 deleteRelationshipTarget( + public Response deleteConnection( @Context HttpServletRequest httpServletRequest, @ApiParam( value = "The revision is used to verify the client is working with the latest version of the flow.", @@ -892,7 +941,195 @@ public class ConnectionResource extends ApplicationResource { } /** - * Drops the flowfiles in the queue of the specified connection. + * Gets the specified flowfile from the specified connection. + * + * @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 connectionId The connection id + * @param flowFileUuid The flowfile uuid + * @param clusterNodeId The cluster node id where the flowfile resides + * @return a flowFileDTO + */ + @GET + @Consumes(MediaType.WILDCARD) + @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) + @Path("/{connection-id}/flowfiles/{flowfile-uuid}") + @PreAuthorize("hasRole('ROLE_DFM')") + @ApiOperation( + value = "Gets a FlowFile from a Connection.", + authorizations = { + @Authorization(value = "Data Flow Manager", type = "ROLE_DFM") + } + ) + @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 getFlowFile( + @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) ClientIdParameter clientId, + @ApiParam( + value = "The connection id.", + required = true + ) + @PathParam("connection-id") String connectionId, + @ApiParam( + value = "The flowfile uuid.", + required = true + ) + @PathParam("flowfile-uuid") String flowFileUuid, + @ApiParam( + value = "The id of the node where the content exists if clustered.", + required = false + ) + @QueryParam("clusterNodeId") String clusterNodeId) { + + // replicate if cluster manager + if (properties.isClusterManager()) { + // determine where this request should be sent + if (clusterNodeId == null) { + throw new IllegalArgumentException("The id of the node in the cluster is required."); + } else { + // get the target node and ensure it exists + final Node targetNode = clusterManager.getNode(clusterNodeId); + if (targetNode == null) { + throw new UnknownNodeException("The specified cluster node does not exist."); + } + + final Set<NodeIdentifier> targetNodes = new HashSet<>(); + targetNodes.add(targetNode.getNodeId()); + + // replicate the request to the specific node + return clusterManager.applyRequest(HttpMethod.GET, getAbsolutePath(), getRequestParameters(true), getHeaders(), targetNodes).getResponse(); + } + } + + // get the flowfile + final FlowFileDTO flowfileDto = serviceFacade.getFlowFile(groupId, connectionId, flowFileUuid); + populateRemainingFlowFileContent(connectionId, flowfileDto); + + // create the revision + final RevisionDTO revision = new RevisionDTO(); + revision.setClientId(clientId.getClientId()); + + // create the response entity + final FlowFileEntity entity = new FlowFileEntity(); + entity.setRevision(revision); + entity.setFlowFile(flowfileDto); + + return generateOkResponse(entity).build(); + } + + /** + * Gets the content for the specified flowfile in the specified connection. + * + * @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 connectionId The connection id + * @param flowFileUuid The flowfile uuid + * @param clusterNodeId The cluster node id + * @return The content stream + */ + @GET + @Consumes(MediaType.WILDCARD) + @Produces(MediaType.WILDCARD) + @Path("/{connection-id}/flowfiles/{flowfile-uuid}/content") + @PreAuthorize("hasRole('ROLE_DFM')") + @ApiOperation( + value = "Gets the content for a FlowFile in a Connection.", + authorizations = { + @Authorization(value = "Data Flow Manager", type = "ROLE_DFM") + } + ) + @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 downloadFlowFileContent( + @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) ClientIdParameter clientId, + @ApiParam( + value = "The connection id.", + required = true + ) + @PathParam("connection-id") String connectionId, + @ApiParam( + value = "The flowfile uuid.", + required = true + ) + @PathParam("flowfile-uuid") String flowFileUuid, + @ApiParam( + value = "The id of the node where the content exists if clustered.", + required = false + ) + @QueryParam("clusterNodeId") String clusterNodeId) { + + // replicate if cluster manager + if (properties.isClusterManager()) { + // determine where this request should be sent + if (clusterNodeId == null) { + throw new IllegalArgumentException("The id of the node in the cluster is required."); + } else { + // get the target node and ensure it exists + final Node targetNode = clusterManager.getNode(clusterNodeId); + if (targetNode == null) { + throw new UnknownNodeException("The specified cluster node does not exist."); + } + + final Set<NodeIdentifier> targetNodes = new HashSet<>(); + targetNodes.add(targetNode.getNodeId()); + + // replicate the request to the specific node + return clusterManager.applyRequest(HttpMethod.GET, getAbsolutePath(), getRequestParameters(true), getHeaders(), targetNodes).getResponse(); + } + } + + // get the uri of the request + final String uri = generateResourceUri("controller", "process-groups", groupId, "connections", connectionId, "flowfiles", flowFileUuid, "content"); + + // get an input stream to the content + final DownloadableContent content = serviceFacade.getContent(groupId, connectionId, flowFileUuid, uri); + + // generate a streaming response + final StreamingOutput response = new StreamingOutput() { + @Override + public void write(OutputStream output) throws IOException, WebApplicationException { + try (InputStream is = content.getContent()) { + // stream the content to the response + StreamUtils.copy(is, output); + + // flush the response + output.flush(); + } + } + }; + + // use the appropriate content type + String contentType = content.getType(); + if (contentType == null) { + contentType = MediaType.APPLICATION_OCTET_STREAM; + } + + return generateOkResponse(response).type(contentType).header("Content-Disposition", String.format("attachment; filename=\"%s\"", content.getFilename())).build(); + } + + /** + * Drops the flowfiles in the queue of the specified connection. This endpoint is DEPRECATED. Please use + * POST /nifi-api/controller/process-groups/{process-group-id}/connections/{connection-id}/drop-requests instead. * * @param httpServletRequest request * @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. @@ -906,6 +1143,7 @@ public class ConnectionResource extends ApplicationResource { @PreAuthorize("hasRole('ROLE_DFM')") @ApiOperation( value = "Drops the contents of the queue in this connection.", + notes = "This endpoint is DEPRECATED. Please use POST /nifi-api/controller/process-groups/{process-group-id}/connections/{connection-id}/drop-requests instead.", response = DropRequestEntity.class, authorizations = { @Authorization(value = "Data Flow Manager", type = "ROLE_DFM") @@ -913,6 +1151,7 @@ public class ConnectionResource extends ApplicationResource { ) @ApiResponses( value = { + @ApiResponse(code = 202, message = "The request has been accepted. A HTTP response header will contain the URI where the response can be polled."), @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."), @@ -920,6 +1159,7 @@ public class ConnectionResource extends ApplicationResource { @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.") } ) + @Deprecated public Response dropQueueContents( @Context HttpServletRequest httpServletRequest, @ApiParam( @@ -933,6 +1173,319 @@ public class ConnectionResource extends ApplicationResource { ) @PathParam("connection-id") String id) { + // defer to the new endpoint that references /drop-requests in the URI + return createDropRequest(httpServletRequest, clientId, id); + } + + /** + * Creates a request to list the flowfiles in the queue of the specified connection. + * + * @param httpServletRequest request + * @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 connection + * @return A listRequestEntity + */ + @POST + @Consumes(MediaType.WILDCARD) + @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) + @Path("/{connection-id}/listing-requests") + @PreAuthorize("hasRole('ROLE_DFM')") + @ApiOperation( + value = "Lists the contents of the queue in this connection.", + response = ListingRequestEntity.class, + authorizations = { + @Authorization(value = "Data Flow Manager", type = "ROLE_DFM") + } + ) + @ApiResponses( + value = { + @ApiResponse(code = 202, message = "The request has been accepted. A HTTP response header will contain the URI where the response can be polled."), + @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 createFlowFileListing( + @Context HttpServletRequest httpServletRequest, + @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) ClientIdParameter clientId, + @ApiParam( + value = "The connection id.", + required = true + ) + @PathParam("connection-id") String id, + @ApiParam( + value = "The sort column.", + required = false, + defaultValue = "QUEUE_POSITION", + allowableValues = "QUEUE_POSITION, FLOWFILE_UUID, FILENAME, FLOWFILE_SIZE, QUEUED_DURATION, FLOWFILE_AGE, PENALIZATION" + ) + @FormParam("sortColumn") String sortColumn, + @ApiParam( + value = "The sort direction.", + required = false, + defaultValue = "asc", + allowableValues = "asc, desc" + ) + @FormParam("sortOrder") @DefaultValue("asc") String sortOrder) { + + // parse the sort column + final SortColumn column; + if (sortColumn == null) { + column = SortColumn.QUEUE_POSITION; + } else { + try { + column = SortColumn.valueOf(sortColumn); + } catch (final IllegalArgumentException iae) { + throw new IllegalArgumentException(String.format("Sort Column: Value must be one of [%s]", StringUtils.join(SortColumn.values(), ", "))); + } + } + + // normalize the sort order + if (!sortOrder.equalsIgnoreCase("asc") && !sortOrder.equalsIgnoreCase("desc")) { + throw new IllegalArgumentException("The sort order must be 'asc' or 'desc'."); + } + + // replicate if cluster manager + if (properties.isClusterManager()) { + return clusterManager.applyRequest(HttpMethod.GET, getAbsolutePath(), getRequestParameters(true), getHeaders()).getResponse(); + } + + // handle expects request (usually from the cluster manager) + final String expects = httpServletRequest.getHeader(WebClusterManager.NCM_EXPECTS_HTTP_HEADER); + if (expects != null) { + serviceFacade.verifyListQueue(groupId, id); + return generateContinueResponse().build(); + } + + final SortDirection direction; + if (sortOrder.equalsIgnoreCase("asc")) { + direction = SortDirection.ASCENDING; + } else { + direction = SortDirection.DESCENDING; + } + + // ensure the id is the same across the cluster + final String listingRequestId; + final ClusterContext clusterContext = ClusterContextThreadLocal.getContext(); + if (clusterContext != null) { + listingRequestId = UUID.nameUUIDFromBytes(clusterContext.getIdGenerationSeed().getBytes(StandardCharsets.UTF_8)).toString(); + } else { + listingRequestId = UUID.randomUUID().toString(); + } + + // submit the listing request + final ListingRequestDTO listingRequest = serviceFacade.createFlowFileListingRequest(groupId, id, listingRequestId, column, direction); + populateRemainingFlowFileListingContent(id, listingRequest); + + // create the revision + final RevisionDTO revision = new RevisionDTO(); + revision.setClientId(clientId.getClientId()); + + // create the response entity + final ListingRequestEntity entity = new ListingRequestEntity(); + entity.setRevision(revision); + entity.setListingRequest(listingRequest); + + // generate the URI where the response will be + final URI location = URI.create(listingRequest.getUri()); + return Response.status(Status.ACCEPTED).location(location).entity(entity).build(); + } + + /** + * Checks the status of an outstanding listing request. + * + * @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 connectionId The id of the connection + * @param listingRequestId The id of the drop request + * @return A dropRequestEntity + */ + @GET + @Consumes(MediaType.WILDCARD) + @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) + @Path("/{connection-id}/listing-requests/{listing-request-id}") + @PreAuthorize("hasRole('ROLE_DFM')") + @ApiOperation( + value = "Gets the current status of a listing request for the specified connection.", + response = ListingRequestEntity.class, + authorizations = { + @Authorization(value = "Data Flow Manager", type = "ROLE_DFM") + } + ) + @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 getListingRequest( + @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) ClientIdParameter clientId, + @ApiParam( + value = "The connection id.", + required = true + ) + @PathParam("connection-id") String connectionId, + @ApiParam( + value = "The listing request id.", + required = true + ) + @PathParam("listing-request-id") String listingRequestId) { + + // replicate if cluster manager + if (properties.isClusterManager()) { + return clusterManager.applyRequest(HttpMethod.GET, getAbsolutePath(), getRequestParameters(true), getHeaders()).getResponse(); + } + + // get the listing request + final ListingRequestDTO listingRequest = serviceFacade.getFlowFileListingRequest(groupId, connectionId, listingRequestId); + populateRemainingFlowFileListingContent(connectionId, listingRequest); + + // create the revision + final RevisionDTO revision = new RevisionDTO(); + revision.setClientId(clientId.getClientId()); + + // create the response entity + final ListingRequestEntity entity = new ListingRequestEntity(); + entity.setRevision(revision); + entity.setListingRequest(listingRequest); + + return generateOkResponse(entity).build(); + } + + /** + * Deletes the specified listing request. + * + * @param httpServletRequest request + * @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 connectionId The connection id + * @param listingRequestId The drop request id + * @return A dropRequestEntity + */ + @DELETE + @Consumes(MediaType.WILDCARD) + @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) + @Path("/{connection-id}/listing-requests/{listing-request-id}") + @ApiOperation( + value = "Cancels and/or removes a request to list the contents of this connection.", + response = DropRequestEntity.class, + authorizations = { + @Authorization(value = "Data Flow Manager", type = "ROLE_DFM") + } + ) + @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 deleteListingRequest( + @Context HttpServletRequest httpServletRequest, + @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) ClientIdParameter clientId, + @ApiParam( + value = "The connection id.", + required = true + ) + @PathParam("connection-id") String connectionId, + @ApiParam( + value = "The listing request id.", + required = true + ) + @PathParam("listing-request-id") String listingRequestId) { + + // replicate if cluster manager + if (properties.isClusterManager()) { + return clusterManager.applyRequest(HttpMethod.DELETE, getAbsolutePath(), getRequestParameters(true), getHeaders()).getResponse(); + } + + // handle expects request (usually from the cluster manager) + final String expects = httpServletRequest.getHeader(WebClusterManager.NCM_EXPECTS_HTTP_HEADER); + if (expects != null) { + return generateContinueResponse().build(); + } + + // delete the listing request + final ListingRequestDTO listingRequest = serviceFacade.deleteFlowFileListingRequest(groupId, connectionId, listingRequestId); + + // prune the results as they were already received when the listing completed + listingRequest.setFlowFileSummaries(null); + + // populate remaining content + populateRemainingFlowFileListingContent(connectionId, listingRequest); + + // create the revision + final RevisionDTO revision = new RevisionDTO(); + revision.setClientId(clientId.getClientId()); + + // create the response entity + final ListingRequestEntity entity = new ListingRequestEntity(); + entity.setRevision(revision); + entity.setListingRequest(listingRequest); + + return generateOkResponse(entity).build(); + } + + /** + * Creates a request to delete the flowfiles in the queue of the specified connection. + * + * @param httpServletRequest request + * @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 connection + * @return A dropRequestEntity + */ + @POST + @Consumes(MediaType.WILDCARD) + @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) + @Path("/{connection-id}/drop-requests") + @PreAuthorize("hasRole('ROLE_DFM')") + @ApiOperation( + value = "Creates a request to drop the contents of the queue in this connection.", + response = DropRequestEntity.class, + authorizations = { + @Authorization(value = "Data Flow Manager", type = "ROLE_DFM") + } + ) + @ApiResponses( + value = { + @ApiResponse(code = 202, message = "The request has been accepted. A HTTP response header will contain the URI where the response can be polled."), + @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 createDropRequest( + @Context HttpServletRequest httpServletRequest, + @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) ClientIdParameter clientId, + @ApiParam( + value = "The connection id.", + required = true + ) + @PathParam("connection-id") String id) { + // replicate if cluster manager if (properties.isClusterManager()) { return clusterManager.applyRequest(HttpMethod.DELETE, getAbsolutePath(), getRequestParameters(true), getHeaders()).getResponse(); @@ -968,11 +1521,7 @@ public class ConnectionResource extends ApplicationResource { // generate the URI where the response will be final URI location = URI.create(dropRequest.getUri()); - if (dropRequest.isFinished()) { - return generateCreatedResponse(location, entity).build(); - } else { - return Response.status(Status.ACCEPTED).location(location).entity(entity).build(); - } + return Response.status(Status.ACCEPTED).location(location).entity(entity).build(); } /** @@ -1057,7 +1606,7 @@ public class ConnectionResource extends ApplicationResource { @Path("/{connection-id}/drop-requests/{drop-request-id}") @PreAuthorize("hasRole('ROLE_DFM')") @ApiOperation( - value = "Cancels and/or removes a request drop of the contents in this connection.", + value = "Cancels and/or removes a request to drop the contents of this connection.", response = DropRequestEntity.class, authorizations = { @Authorization(value = "Data Flow Manager", type = "ROLE_DFM")
http://git-wip-us.apache.org/repos/asf/nifi/blob/b330fd16/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java index a2619e6..6433e9f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java @@ -75,6 +75,12 @@ import org.apache.nifi.controller.ProcessorNode; import org.apache.nifi.controller.Snippet; import org.apache.nifi.controller.Template; import org.apache.nifi.controller.label.Label; +import org.apache.nifi.controller.queue.FlowFileSummary; +import org.apache.nifi.controller.queue.ListFlowFileState; +import org.apache.nifi.controller.queue.ListFlowFileStatus; +import org.apache.nifi.controller.repository.FlowFileRecord; +import org.apache.nifi.controller.repository.claim.ContentClaim; +import org.apache.nifi.controller.repository.claim.ResourceClaim; import org.apache.nifi.controller.status.ConnectionStatus; import org.apache.nifi.controller.status.PortStatus; import org.apache.nifi.controller.status.ProcessGroupStatus; @@ -84,6 +90,7 @@ import org.apache.nifi.diagnostics.GarbageCollection; import org.apache.nifi.diagnostics.StorageUsage; import org.apache.nifi.diagnostics.SystemDiagnostics; import org.apache.nifi.flowfile.FlowFilePrioritizer; +import org.apache.nifi.flowfile.attributes.CoreAttributes; import org.apache.nifi.groups.ProcessGroup; import org.apache.nifi.groups.ProcessGroupCounts; import org.apache.nifi.groups.RemoteProcessGroup; @@ -345,6 +352,113 @@ public final class DtoFactory { return dto; } + private boolean isListingRequestComplete(final ListFlowFileState state) { + return ListFlowFileState.COMPLETE.equals(state) || ListFlowFileState.CANCELED.equals(state) || ListFlowFileState.FAILURE.equals(state); + } + + private QueueSizeDTO createQueueSizeDTO(final QueueSize queueSize) { + final QueueSizeDTO dto = new QueueSizeDTO(); + dto.setByteCount(queueSize.getByteCount()); + dto.setObjectCount(queueSize.getObjectCount()); + return dto; + } + + /** + * Creates a ListingRequestDTO from the specified ListFlowFileStatus. + * + * @param listingRequest listingRequest + * @return dto + */ + public ListingRequestDTO createListingRequestDTO(final ListFlowFileStatus listingRequest) { + final ListingRequestDTO dto = new ListingRequestDTO(); + dto.setId(listingRequest.getRequestIdentifier()); + dto.setSubmissionTime(new Date(listingRequest.getRequestSubmissionTime())); + dto.setLastUpdated(new Date(listingRequest.getLastUpdated())); + dto.setState(listingRequest.getState().toString()); + dto.setFailureReason(listingRequest.getFailureReason()); + dto.setFinished(isListingRequestComplete(listingRequest.getState())); + dto.setMaxResults(listingRequest.getMaxResults()); + dto.setSortColumn(listingRequest.getSortColumn().name()); + dto.setSortDirection(listingRequest.getSortDirection().name()); + dto.setTotalStepCount(listingRequest.getTotalStepCount()); + dto.setCompletedStepCount(listingRequest.getCompletedStepCount()); + dto.setPercentCompleted(listingRequest.getCompletionPercentage()); + + dto.setQueueSize(createQueueSizeDTO(listingRequest.getQueueSize())); + + if (isListingRequestComplete(listingRequest.getState())) { + final List<FlowFileSummary> flowFileSummaries = listingRequest.getFlowFileSummaries(); + if (flowFileSummaries != null) { + final Date now = new Date(); + final List<FlowFileSummaryDTO> summaryDtos = new ArrayList<>(flowFileSummaries.size()); + for (final FlowFileSummary summary : flowFileSummaries) { + summaryDtos.add(createFlowFileSummaryDTO(summary, now)); + } + dto.setFlowFileSummaries(summaryDtos); + } + } + + return dto; + } + + /** + * Creates a FlowFileSummaryDTO from the specified FlowFileSummary. + * + * @param summary summary + * @return dto + */ + public FlowFileSummaryDTO createFlowFileSummaryDTO(final FlowFileSummary summary, final Date now) { + final FlowFileSummaryDTO dto = new FlowFileSummaryDTO(); + dto.setUuid(summary.getUuid()); + dto.setFilename(summary.getFilename()); + dto.setPenalized(summary.isPenalized()); + dto.setPosition(summary.getPosition()); + dto.setSize(summary.getSize()); + + final long queuedDuration = now.getTime() - summary.getLastQueuedTime(); + dto.setQueuedDuration(queuedDuration); + + final long age = now.getTime() - summary.getLineageStartDate(); + dto.setLineageDuration(age); + + return dto; + } + + /** + * Creates a FlowFileDTO from the specified FlowFileRecord. + * + * @param record record + * @return dto + */ + public FlowFileDTO createFlowFileDTO(final FlowFileRecord record) { + final Date now = new Date(); + final FlowFileDTO dto = new FlowFileDTO(); + dto.setUuid(record.getAttribute(CoreAttributes.UUID.key())); + dto.setFilename(record.getAttribute(CoreAttributes.FILENAME.key())); + dto.setPenalized(record.isPenalized()); + dto.setSize(record.getSize()); + dto.setAttributes(record.getAttributes()); + + final long queuedDuration = now.getTime() - record.getLastQueueDate(); + dto.setQueuedDuration(queuedDuration); + + final long age = now.getTime() - record.getLineageStartDate(); + dto.setLineageDuration(age); + + final ContentClaim contentClaim = record.getContentClaim(); + if (contentClaim != null) { + final ResourceClaim resourceClaim = contentClaim.getResourceClaim(); + dto.setContentClaimSection(resourceClaim.getSection()); + dto.setContentClaimContainer(resourceClaim.getContainer()); + dto.setContentClaimIdentifier(resourceClaim.getId()); + dto.setContentClaimOffset(contentClaim.getOffset()); + dto.setContentClaimFileSizeBytes(contentClaim.getLength()); + dto.setContentClaimFileSize(FormatUtils.formatDataSize(contentClaim.getLength())); + } + + return dto; + } + /** * Creates a ConnectionDTO from the specified Connection. * http://git-wip-us.apache.org/repos/asf/nifi/blob/b330fd16/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectionDAO.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectionDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectionDAO.java index 2be4403..932cc23 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectionDAO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectionDAO.java @@ -19,6 +19,11 @@ package org.apache.nifi.web.dao; import java.util.Set; import org.apache.nifi.connectable.Connection; import org.apache.nifi.controller.queue.DropFlowFileStatus; +import org.apache.nifi.controller.queue.ListFlowFileStatus; +import org.apache.nifi.controller.queue.SortColumn; +import org.apache.nifi.controller.queue.SortDirection; +import org.apache.nifi.controller.repository.FlowFileRecord; +import org.apache.nifi.web.DownloadableContent; import org.apache.nifi.web.api.dto.ConnectionDTO; public interface ConnectionDAO { @@ -43,6 +48,26 @@ public interface ConnectionDAO { DropFlowFileStatus getFlowFileDropRequest(String groupId, String id, String dropRequestId); /** + * Gets the specified flowfile listing request. + * + * @param groupId group id + * @param id connection id + * @param listingRequestId The listing request id + * @return The listing request status + */ + ListFlowFileStatus getFlowFileListingRequest(String groupId, String id, String listingRequestId); + + /** + * Gets the specified flowfile in the specified connection. + * + * @param groupId group id + * @param id connection id + * @param flowFileUuid the flowfile uuid + * @return The flowfile + */ + FlowFileRecord getFlowFile(String groupId, String id, String flowFileUuid); + + /** * Gets the connections for the specified source processor. * * @param groupId group id @@ -85,7 +110,27 @@ public interface ConnectionDAO { * @param dropRequestId drop request id * @return The drop request status */ - DropFlowFileStatus createFileFlowDropRequest(String groupId, String id, String dropRequestId); + DropFlowFileStatus createFlowFileDropRequest(String groupId, String id, String dropRequestId); + + /** + * Creates a new flow file listing request. + * + * @param groupId group id + * @param id connection id + * @param listingRequestId listing request id + * @param column sort column + * @param direction sort direction + * @return The listing request status + */ + ListFlowFileStatus createFlowFileListingRequest(String groupId, String id, String listingRequestId, SortColumn column, SortDirection direction); + + /** + * Verifies the listing can be processed. + * + * @param groupId group id + * @param id connection id + */ + void verifyList(String groupId, String id); /** * Verifies the create request can be processed. @@ -137,4 +182,25 @@ public interface ConnectionDAO { * @return The drop request */ DropFlowFileStatus deleteFlowFileDropRequest(String groupId, String id, String dropRequestId); + + /** + * Deletes the specified flow file listing request. + * + * @param groupId group id + * @param id connection id + * @param listingRequestId The listing request id + * @return The listing request status + */ + ListFlowFileStatus deleteFlowFileListingRequest(String groupId, String id, String listingRequestId); + + /** + * Gets the content for the specified flowfile in the specified connection. + * + * @param groupId group id + * @param id connection id + * @param flowfileUuid flowfile uuid + * @param requestUri request uri + * @return The downloadable content + */ + DownloadableContent getContent(String groupId, String id, String flowfileUuid, String requestUri); } http://git-wip-us.apache.org/repos/asf/nifi/blob/b330fd16/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectionDAO.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectionDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectionDAO.java index 565e5af..d5fb713 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectionDAO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectionDAO.java @@ -16,15 +16,20 @@ */ package org.apache.nifi.web.dao.impl; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import javax.ws.rs.WebApplicationException; +import org.apache.nifi.admin.service.UserService; +import org.apache.nifi.authorization.DownloadAuthorization; import org.apache.nifi.connectable.Connectable; import org.apache.nifi.connectable.ConnectableType; import org.apache.nifi.connectable.Connection; @@ -34,6 +39,12 @@ import org.apache.nifi.controller.ProcessorNode; import org.apache.nifi.controller.exception.ValidationException; import org.apache.nifi.controller.queue.DropFlowFileStatus; import org.apache.nifi.controller.queue.FlowFileQueue; +import org.apache.nifi.controller.queue.ListFlowFileStatus; +import org.apache.nifi.controller.queue.SortColumn; +import org.apache.nifi.controller.queue.SortDirection; +import org.apache.nifi.controller.repository.ContentNotFoundException; +import org.apache.nifi.controller.repository.FlowFileRecord; +import org.apache.nifi.flowfile.attributes.CoreAttributes; import org.apache.nifi.groups.ProcessGroup; import org.apache.nifi.groups.RemoteProcessGroup; import org.apache.nifi.flowfile.FlowFilePrioritizer; @@ -41,16 +52,24 @@ import org.apache.nifi.processor.Relationship; import org.apache.nifi.remote.RemoteGroupPort; import org.apache.nifi.user.NiFiUser; import org.apache.nifi.util.FormatUtils; +import org.apache.nifi.web.DownloadableContent; import org.apache.nifi.web.ResourceNotFoundException; import org.apache.nifi.web.api.dto.ConnectableDTO; import org.apache.nifi.web.api.dto.ConnectionDTO; import org.apache.nifi.web.api.dto.PositionDTO; import org.apache.nifi.web.dao.ConnectionDAO; +import org.apache.nifi.web.security.ProxiedEntitiesUtils; import org.apache.nifi.web.security.user.NiFiUserUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.access.AccessDeniedException; public class StandardConnectionDAO extends ComponentDAO implements ConnectionDAO { + private static final Logger logger = LoggerFactory.getLogger(StandardConnectionDAO.class); + private FlowController flowController; + private UserService userService; private Connection locateConnection(final String groupId, final String id) { return locateConnection(locateProcessGroup(flowController, groupId), id); @@ -87,6 +106,37 @@ public class StandardConnectionDAO extends ComponentDAO implements ConnectionDAO } @Override + public ListFlowFileStatus getFlowFileListingRequest(String groupId, String connectionId, String listingRequestId) { + final Connection connection = locateConnection(groupId, connectionId); + final FlowFileQueue queue = connection.getFlowFileQueue(); + + final ListFlowFileStatus listRequest = queue.getListFlowFileStatus(listingRequestId); + if (listRequest == null) { + throw new ResourceNotFoundException(String.format("Unable to find listing request with id '%s'.", listingRequestId)); + } + + return listRequest; + } + + @Override + public FlowFileRecord getFlowFile(String groupId, String id, String flowFileUuid) { + try { + final Connection connection = locateConnection(groupId, id); + final FlowFileQueue queue = connection.getFlowFileQueue(); + final FlowFileRecord flowFile = queue.getFlowFile(flowFileUuid); + + if (flowFile == null) { + throw new ResourceNotFoundException(String.format("Unable to find FlowFile '%s' in Connection '%s'.", flowFileUuid, id)); + } + + return flowFile; + } catch (final IOException ioe) { + logger.error(String.format("Unable to get the flowfile (%s) at this time.", flowFileUuid), ioe); + throw new IllegalStateException("Unable to get the FlowFile at this time."); + } + } + + @Override public Set<Connection> getConnectionsForSource(final String groupId, final String processorId) { final Set<Connection> connections = new HashSet<>(getConnections(groupId)); for (final Iterator<Connection> connectionIter = connections.iterator(); connectionIter.hasNext();) { @@ -312,7 +362,7 @@ public class StandardConnectionDAO extends ComponentDAO implements ConnectionDAO } @Override - public DropFlowFileStatus createFileFlowDropRequest(String groupId, String id, String dropRequestId) { + public DropFlowFileStatus createFlowFileDropRequest(String groupId, String id, String dropRequestId) { final Connection connection = locateConnection(groupId, id); final FlowFileQueue queue = connection.getFlowFileQueue(); @@ -325,6 +375,17 @@ public class StandardConnectionDAO extends ComponentDAO implements ConnectionDAO } @Override + public ListFlowFileStatus createFlowFileListingRequest(String groupId, String id, String listingRequestId, SortColumn column, SortDirection direction) { + final Connection connection = locateConnection(groupId, id); + final FlowFileQueue queue = connection.getFlowFileQueue(); + + // ensure we can list + verifyList(queue); + + return queue.listFlowFiles(listingRequestId, 100, column, direction); + } + + @Override public void verifyCreate(String groupId, ConnectionDTO connectionDTO) { // validate the incoming request final List<String> validationErrors = validateProposedConfiguration(groupId, connectionDTO); @@ -335,6 +396,17 @@ public class StandardConnectionDAO extends ComponentDAO implements ConnectionDAO } } + private void verifyList(final FlowFileQueue queue) { + queue.verifyCanList(); + } + + @Override + public void verifyList(String groupId, String id) { + final Connection connection = locateConnection(groupId, id); + final FlowFileQueue queue = connection.getFlowFileQueue(); + verifyList(queue); + } + @Override public void verifyUpdate(String groupId, ConnectionDTO connectionDTO) { final ProcessGroup group = locateProcessGroup(flowController, groupId); @@ -508,8 +580,71 @@ public class StandardConnectionDAO extends ComponentDAO implements ConnectionDAO return dropFlowFileStatus; } + @Override + public ListFlowFileStatus deleteFlowFileListingRequest(String groupId, String connectionId, String listingRequestId) { + final Connection connection = locateConnection(groupId, connectionId); + final FlowFileQueue queue = connection.getFlowFileQueue(); + + final ListFlowFileStatus listFlowFileStatus = queue.cancelListFlowFileRequest(listingRequestId); + if (listFlowFileStatus == null) { + throw new ResourceNotFoundException(String.format("Unable to find listing request with id '%s'.", listingRequestId)); + } + + return listFlowFileStatus; + } + + @Override + public DownloadableContent getContent(String groupId, String id, String flowFileUuid, String requestUri) { + try { + final NiFiUser user = NiFiUserUtils.getNiFiUser(); + if (user == null) { + throw new WebApplicationException(new Throwable("Unable to access details for current user.")); + } + + final Connection connection = locateConnection(groupId, id); + final FlowFileQueue queue = connection.getFlowFileQueue(); + final FlowFileRecord flowFile = queue.getFlowFile(flowFileUuid); + + if (flowFile == null) { + throw new ResourceNotFoundException(String.format("Unable to find FlowFile '%s' in Connection '%s'.", flowFileUuid, id)); + } + + // calculate the dn chain + final List<String> dnChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(user); + + // ensure the users in this chain are allowed to download this content + final Map<String, String> attributes = flowFile.getAttributes(); + final DownloadAuthorization downloadAuthorization = userService.authorizeDownload(dnChain, attributes); + if (!downloadAuthorization.isApproved()) { + throw new AccessDeniedException(downloadAuthorization.getExplanation()); + } + + // get the filename and fall back to the identifier (should never happen) + String filename = attributes.get(CoreAttributes.FILENAME.key()); + if (filename == null) { + filename = flowFileUuid; + } + + // get the mime-type + final String type = attributes.get(CoreAttributes.MIME_TYPE.key()); + + // get the content + final InputStream content = flowController.getContent(flowFile, user.getIdentity(), requestUri); + return new DownloadableContent(filename, type, content); + } catch (final ContentNotFoundException cnfe) { + throw new ResourceNotFoundException("Unable to find the specified content."); + } catch (final IOException ioe) { + logger.error(String.format("Unable to get the content for flowfile (%s) at this time.", flowFileUuid), ioe); + throw new IllegalStateException("Unable to get the content at this time."); + } + } + /* setters */ public void setFlowController(final FlowController flowController) { this.flowController = flowController; } + + public void setUserService(UserService userService) { + this.userService = userService; + } } http://git-wip-us.apache.org/repos/asf/nifi/blob/b330fd16/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml index 9f3d2f5..406f38c 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml @@ -76,6 +76,7 @@ </bean> <bean id="connectionDAO" class="org.apache.nifi.web.dao.impl.StandardConnectionDAO"> <property name="flowController" ref="flowController"/> + <property name="userService" ref="userService"/> </bean> <bean id="processorDAO" class="org.apache.nifi.web.dao.impl.StandardProcessorDAO"> <property name="flowController" ref="flowController"/> http://git-wip-us.apache.org/repos/asf/nifi/blob/b330fd16/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml index 35ad4a2..8e87131 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml @@ -280,6 +280,7 @@ <include>${staging.dir}/js/nf/canvas/nf-snippet.js</include> <include>${staging.dir}/js/nf/canvas/nf-canvas-toolbox.js</include> <include>${staging.dir}/js/nf/canvas/nf-custom-ui.js</include> + <include>${staging.dir}/js/nf/canvas/nf-queue-listing.js</include> <include>${staging.dir}/js/nf/canvas/nf-controller-service.js</include> <include>${staging.dir}/js/nf/canvas/nf-reporting-task.js</include> <include>${staging.dir}/js/nf/canvas/nf-processor-configuration.js</include> @@ -445,6 +446,7 @@ <include>${staging.dir}/css/remote-process-group-configuration.css</include> <include>${staging.dir}/css/port-configuration.css</include> <include>${staging.dir}/css/port-details.css</include> + <include>${staging.dir}/css/queue-listing.css</include> <include>${staging.dir}/css/label-configuration.css</include> <include>${staging.dir}/css/connection-configuration.css</include> <include>${staging.dir}/css/connection-details.css</include> http://git-wip-us.apache.org/repos/asf/nifi/blob/b330fd16/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties index bf61846..43e60f4 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties @@ -21,6 +21,7 @@ nf.canvas.script.tags=<script type="text/javascript" src="js/nf/nf-namespace.js? <script type="text/javascript" src="js/nf/nf-shell.js?${project.version}"></script>\n\ <script type="text/javascript" src="js/nf/nf-storage.js?${project.version}"></script>\n\ <script type="text/javascript" src="js/nf/canvas/nf-snippet.js?${project.version}"></script>\n\ +<script type="text/javascript" src="js/nf/canvas/nf-queue-listing.js?${project.version}"></script>\n\ <script type="text/javascript" src="js/nf/canvas/nf-canvas-toolbox.js?${project.version}"></script>\n\ <script type="text/javascript" src="js/nf/canvas/nf-custom-ui.js?${project.version}"></script>\n\ <script type="text/javascript" src="js/nf/canvas/nf-controller-service.js?${project.version}"></script>\n\ http://git-wip-us.apache.org/repos/asf/nifi/blob/b330fd16/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp index f9970df..5a9eab7 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp @@ -119,9 +119,13 @@ <jsp:include page="/WEB-INF/partials/canvas/label-configuration.jsp"/> <jsp:include page="/WEB-INF/partials/canvas/connection-configuration.jsp"/> <jsp:include page="/WEB-INF/partials/canvas/drop-request-status-dialog.jsp"/> + <jsp:include page="/WEB-INF/partials/canvas/flowfile-details-dialog.jsp"/> + <jsp:include page="/WEB-INF/partials/canvas/listing-request-status-dialog.jsp"/> + <jsp:include page="/WEB-INF/partials/canvas/queue-listing.jsp"/> <jsp:include page="/WEB-INF/partials/connection-details.jsp"/> <div id="faded-background"></div> <div id="glass-pane"></div> <div id="context-menu" class="unselectable"></div> + <span id="nifi-content-viewer-url" class="hidden"></span> </body> </html> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/nifi/blob/b330fd16/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/flowfile-details-dialog.jsp ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/flowfile-details-dialog.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/flowfile-details-dialog.jsp new file mode 100644 index 0000000..cead866 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/flowfile-details-dialog.jsp @@ -0,0 +1,114 @@ +<%-- + 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. +--%> +<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %> +<div id="flowfile-details-dialog"> + <div id="flowfile-details-dialog-content"> + <div id="flowfile-details-tabs"></div> + <div id="flowfile-details-tabs-content"> + <div id="flowfile-details-tab-content" class="details-tab"> + <span id="flowfile-uri" class="hidden"></span> + <span id="flowfile-cluster-node-id" class="hidden"></span> + <div class="settings-left"> + <div id="flowfile-details"> + <div class="flowfile-header">FlowFile Details</div> + <div class="flowfile-detail"> + <div class="detail-name">UUID</div> + <div id="flowfile-uuid" class="detail-value"></div> + <div class="clear"></div> + </div> + <div class="flowfile-detail"> + <div class="detail-name">Filename</div> + <div id="flowfile-filename" class="detail-value"></div> + <div class="clear"></div> + </div> + <div class="flowfile-detail"> + <div class="detail-name">File Size</div> + <div id="flowfile-file-size" class="detail-value"></div> + <div class="clear"></div> + </div> + <div class="flowfile-detail"> + <div class="detail-name">Queue Position</div> + <div id="flowfile-queue-position" class="detail-value"></div> + <div class="clear"></div> + </div> + <div class="flowfile-detail"> + <div class="detail-name">Queued Duration</div> + <div id="flowfile-queued-duration" class="detail-value"></div> + <div class="clear"></div> + </div> + <div class="flowfile-detail"> + <div class="detail-name">Lineage Duration</div> + <div id="flowfile-lineage-duration" class="detail-value"></div> + <div class="clear"></div> + </div> + <div class="flowfile-detail"> + <div class="detail-name">Penalized</div> + <div id="flowfile-penalized" class="detail-value"></div> + <div class="clear"></div> + </div> + <div id="additional-flowfile-details"></div> + </div> + </div> + <div class="spacer"> </div> + <div class="settings-right"> + <div id="flowfile-content-details" class="content-details"> + <div class="flowfile-header">Content Claim</div> + <div class="flowfile-detail"> + <div class="content-detail-name">Container</div> + <div id="content-container" class="content-detail-value"></div> + <div class="clear"></div> + </div> + <div class="flowfile-detail"> + <div class="content-detail-name">Section</div> + <div id="content-section" class="content-detail-value"></div> + <div class="clear"></div> + </div> + <div class="flowfile-detail"> + <div class="content-detail-name">Identifier</div> + <div id="content-identifier" class="content-detail-value"></div> + <div class="clear"></div> + </div> + <div class="flowfile-detail"> + <div class="content-detail-name">Offset</div> + <div id="content-offset" class="content-detail-value"></div> + <div class="clear"></div> + </div> + <div class="flowfile-detail"> + <div class="content-detail-name">Size</div> + <div id="content-size" class="content-detail-value"></div> + <div id="content-bytes" class="content-detail-value hidden"></div> + <div class="clear"></div> + </div> + <div class="flowfile-detail"> + <div id="content-download" class="button">Download</div> + <div id="content-view" class="button hidden">View</div> + <div class="clear"></div> + </div> + </div> + </div> + <div class="clear"></div> + </div> + <div id="flowfile-attributes-tab-content" class="details-tab"> + <div id="flowfile-attributes-details"> + <div id="flowfile-attributes-header" class="flowfile-header">Attribute Values</div> + <div class="clear"></div> + <div id="flowfile-attributes-container"></div> + </div> + </div> + </div> + </div> +</div> http://git-wip-us.apache.org/repos/asf/nifi/blob/b330fd16/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/listing-request-status-dialog.jsp ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/listing-request-status-dialog.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/listing-request-status-dialog.jsp new file mode 100644 index 0000000..3320388 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/listing-request-status-dialog.jsp @@ -0,0 +1,29 @@ +<%-- + 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. +--%> +<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %> +<div id="listing-request-status-dialog"> + <div class="dialog-content"> + <div class="setting"> + <div class="setting-field"> + <div id="listing-request-status-message"></div> + </div> + <div class="setting-field"> + <div id="listing-request-percent-complete"></div> + </div> + </div> + </div> +</div> http://git-wip-us.apache.org/repos/asf/nifi/blob/b330fd16/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/queue-listing.jsp ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/queue-listing.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/queue-listing.jsp new file mode 100644 index 0000000..0d8435a --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/queue-listing.jsp @@ -0,0 +1,29 @@ +<%-- + 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. +--%> +<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %> +<div id="queue-listing-container" class="hidden"> + <div id="queue-listing-header-container"> + <div id="queue-listing-header-text"></div> + </div> + <div id="queue-listing-stats-container"> + <div id="queue-listing-stats"> + Displaying <span id="displayed-flowfiles"></span> of <span id="total-flowfiles-count"></span> (<span id="total-flowfiles-size"></span>) + </div> + <div id="queue-listing-loading-container" class="loading-container"></div> + </div> + <div id="queue-listing-table"></div> +</div> http://git-wip-us.apache.org/repos/asf/nifi/blob/b330fd16/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/canvas.css ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/canvas.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/canvas.css index abb5ebd..2df31d6 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/canvas.css +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/canvas.css @@ -19,6 +19,7 @@ @import url(processor-details.css); @import url(process-group-configuration.css); @import url(process-group-details.css); +@import url(queue-listing.css); @import url(remote-process-group-configuration.css); @import url(controller-service.css); @import url(reporting-task.css); http://git-wip-us.apache.org/repos/asf/nifi/blob/b330fd16/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/dialog.css ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/dialog.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/dialog.css index 9f4fefb..86d52fe 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/dialog.css +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/dialog.css @@ -276,10 +276,6 @@ div.go-to-link { z-index: 1301; } -#drop-request-status-message { - -} - #drop-request-percent-complete { margin-top: 10px; width: 378px; @@ -290,6 +286,23 @@ div.go-to-link { border-radius: 0; } +#listing-request-status-dialog { + display: none; + width: 400px; + height: 125px; + z-index: 1301; +} + +#listing-request-percent-complete { + margin-top: 10px; + width: 378px; + border-radius: 0; +} + +#listing-request-percent-complete .ui-progressbar-value { + border-radius: 0; +} + div.progress-label { color: #000000; display: block; http://git-wip-us.apache.org/repos/asf/nifi/blob/b330fd16/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/queue-listing.css ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/queue-listing.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/queue-listing.css new file mode 100644 index 0000000..689ceaf --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/queue-listing.css @@ -0,0 +1,215 @@ +/* + * 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. + */ + +/* + Queue listing styles +*/ + +#queue-listing-container { + position: absolute; + top: 0px; + bottom: 0px; + left: 0px; + right: 0px; +} + +#queue-listing-header-container { + height: 35px; + margin-top: 20px; + margin-left: 20px; + margin-right: 20px; +} + +#queue-listing-header-text { + float: left; + font-size: 16px; + font-weight: bold; +} + +#queue-listing-stats-container { + margin-left: 15px; + margin-top: 30px; + -webkit-user-select: none; + -moz-user-select: none; +} + +#queue-listing-stats { + font-size: 9px; + font-weight: bold; + color: #9f6000; + clear: left; + line-height: normal; + margin-left: 5px; +} + +#queue-listing-loading-container { + float: left; + width: 16px; + height: 16px; + background-color: transparent; + margin-top: 4px; + margin-left: 3px; +} + +/* queue listing table */ + +#queue-listing-table { + position: absolute; + top: 100px; + left: 20px; + bottom: 20px; + right: 20px; + border: 1px solid #666; + overflow: hidden; +} + +/* flowfile details */ + +#flowfile-details-dialog { + display: none; + width: 800px; + height: 450px; + z-index: 1301; +} + +#flowfile-details-dialog-content { + margin-top: -10px; + padding: 5px 11px +} + +#flowfile-details-tabs { + background-color: transparent; + border-bottom: 3px solid #666666; + height: 21px; + width: 778px; +} + +#flowfile-details-dialog div.details-tab { + background: url("../images/bgTabContainer.png") repeat-x scroll 0 0 #EEEEEE; + display: none; + height: 330px; + padding: 10px; +} + +#flowfile-details-tab-content div.settings-left { + float: left; + width: 420px; +} + +#flowfile-details-tab-content div.spacer { + float: left; + margin-right: 40px; +} + +#flowfile-details-tab-content div.settings-right { + float: left; + width: 292px; +} + +div.flowfile-header { + color: #264C58; + font-size: 11px; + font-weight: bold; + margin-bottom: 5px; +} + +#flowfile-attributes-container { + height: 290px; + overflow: auto; + border: 1px solid #aaa; + padding: 5px; +} + +div.detail-name { + float: left; + width: 145px; + color: #527991; + font-size: 10px; + font-weight: bold; + overflow: hidden; + white-space: nowrap; + line-height: normal; +} + +div.detail-value { + float: left; + width: 270px; + overflow: hidden; + white-space: nowrap; + line-height: normal; +} + +div.attribute-detail { + margin-bottom: 5px; +} + +div.attribute-name { + float: left; + width: 225px; + color: #527991; + font-size: 10px; + font-weight: bold; + overflow: hidden; + white-space: nowrap; + line-height: normal; +} + +div.attribute-value { + float: left; + width: 490px; + margin-left: 10px; + overflow: hidden; + white-space: nowrap; + line-height: normal; +} + +#flowfile-attributes-header { + float: left; +} + +div.flowfile-detail { + margin-bottom: 4px; +} + +#content-download { + float: left; + margin: 0; +} + +#content-view { + float: left; + margin-left: 5px; +} + +div.content-detail-name { + color: #527991; + float: left; + font-size: 10px; + font-weight: bold; + line-height: normal; + overflow: hidden; + white-space: nowrap; + width: 90px; +} + +div.content-detail-value { + width: 200px; + float: left; + line-height: normal; + overflow: hidden; + white-space: nowrap; +} http://git-wip-us.apache.org/repos/asf/nifi/blob/b330fd16/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/images/iconListQueue.png ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/images/iconListQueue.png b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/images/iconListQueue.png new file mode 100644 index 0000000..9a60eef Binary files /dev/null and b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/images/iconListQueue.png differ http://git-wip-us.apache.org/repos/asf/nifi/blob/b330fd16/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js index 08f0e42..2f1bbd3 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js @@ -928,7 +928,7 @@ nf.Actions = (function () { // cancel it clearTimeout(dropRequestTimer); - // cancel the provenance + // cancel the drop request completeDropRequest(); } } @@ -1028,8 +1028,8 @@ nf.Actions = (function () { // issue the request to delete the flow files $.ajax({ - type: 'DELETE', - url: connection.component.uri + '/contents', + type: 'POST', + url: connection.component.uri + '/drop-requests', dataType: 'json' }).done(function(response) { // initialize the progress bar value @@ -1045,7 +1045,24 @@ nf.Actions = (function () { } }); }, - + + /** + * Lists the flow files in the specified connection. + * + * @param {selection} selection + */ + listQueue: function (selection) { + if (selection.size() !== 1 || !nf.CanvasUtils.isConnection(selection)) { + return; + } + + // get the connection data + var connection = selection.datum(); + + // list the flow files in the specified connection + nf.QueueListing.listQueue(connection); + }, + /** * Opens the fill color dialog for the component in the specified selection. * http://git-wip-us.apache.org/repos/asf/nifi/blob/b330fd16/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js index 428ddf2..005f7e3 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js @@ -1122,6 +1122,11 @@ nf.Canvas = (function () { var configDetails = configResponse.config; var loginDetails = loginResponse.config; + // store the content viewer url if available + if (!nf.Common.isBlank(configDetails.contentViewerUrl)) { + $('#nifi-content-viewer-url').text(configDetails.contentViewerUrl); + } + // when both request complete, load the application isClusteredRequest.done(function () { // get the auto refresh interval @@ -1145,6 +1150,7 @@ nf.Canvas = (function () { nf.Search.init(); nf.Settings.init(); nf.Actions.init(); + nf.QueueListing.init(); // initialize the component behaviors nf.Draggable.init();
