@Override @PreAuthorize("hasRole('ROLE_PROVENANCE')") public DownloadableContent getContent(final ContentRequestContext request) { // if clustered, send request to cluster manager if (properties.isClusterManager()) { // get the URI URI dataUri; try { dataUri = new URI(request.getDataUri()); } catch (final URISyntaxException use) { throw new ClusterRequestException(use); } // set the request parameters final MultivaluedMap<String, String> parameters = new MultivaluedMapImpl(); parameters.add(CLIENT_ID_PARAM, request.getClientId()); // set the headers final Map<String, String> headers = new HashMap<>(); if (StringUtils.isNotBlank(request.getProxiedEntitiesChain())) { headers.put("X-ProxiedEntitiesChain", request.getProxiedEntitiesChain()); } // add the user's authorities (if any) to the headers final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null) { final Object userDetailsObj = authentication.getPrincipal(); if (userDetailsObj instanceof NiFiUserDetails) { // serialize user details object final String hexEncodedUserDetails = WebUtils.serializeObjectToHex((Serializable) userDetailsObj); // put serialized user details in header headers.put("X-ProxiedEntityUserDetails", hexEncodedUserDetails); } } // get the target node and ensure it exists final Node targetNode = clusterManager.getNode(request.getClusterNodeId()); 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 final NodeResponse nodeResponse = clusterManager.applyRequest(HttpMethod.GET, dataUri, parameters, headers, targetNodes); final ClientResponse clientResponse = nodeResponse.getClientResponse(); final MultivaluedMap<String, String> responseHeaders = clientResponse.getHeaders(); // get the file name final String contentDisposition = responseHeaders.getFirst("Content-Disposition"); final String filename = StringUtils.substringBetween(contentDisposition, "filename=\"", "\""); // get the content type final String contentType = responseHeaders.getFirst("Content-Type"); // create the downloadable content return new DownloadableContent(filename, contentType, clientResponse.getEntityInputStream()); } else { // example URI: http://localhost:8080/nifi-api/controller/provenance/events/1/content/input final String eventDetails = StringUtils.substringAfterLast(request.getDataUri(), "events/"); final String rawEventId = StringUtils.substringBefore(eventDetails, "/content/"); final String rawDirection = StringUtils.substringAfterLast(eventDetails, "/content/"); // get the content type final Long eventId; final ContentDirection direction; try { eventId = Long.parseLong(rawEventId); direction = ContentDirection.valueOf(rawDirection.toUpperCase()); } catch (final IllegalArgumentException iae) { throw new IllegalArgumentException("The specified data reference URI is not valid."); } return serviceFacade.getContent(eventId, request.getDataUri(), direction); } }
@Override public DownloadableContent getContent(final ContentRequestContext request) { // if clustered, send request to cluster manager if (properties.isClustered() && clusterCoordinator != null && clusterCoordinator.isConnected()) { // get the URI URI dataUri; try { dataUri = new URI(request.getDataUri()); } catch (final URISyntaxException use) { throw new ClusterRequestException(use); } // set the request parameters final MultivaluedMap<String, String> parameters = new MultivaluedMapImpl(); parameters.add(CLIENT_ID_PARAM, request.getClientId()); // set the headers final Map<String, String> headers = new HashMap<>(); if (StringUtils.isNotBlank(request.getProxiedEntitiesChain())) { headers.put("X-ProxiedEntitiesChain", request.getProxiedEntitiesChain()); } // add the user's authorities (if any) to the headers final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null) { final Object userDetailsObj = authentication.getPrincipal(); if (userDetailsObj instanceof NiFiUserDetails) { // serialize user details object final String hexEncodedUserDetails = WebUtils.serializeObjectToHex((Serializable) userDetailsObj); // put serialized user details in header headers.put("X-ProxiedEntityUserDetails", hexEncodedUserDetails); } } // ensure we were able to detect the cluster node id if (request.getClusterNodeId() == null) { throw new IllegalArgumentException("Unable to determine the which node has the content."); } // get the target node and ensure it exists final NodeIdentifier nodeId = clusterCoordinator.getNodeIdentifier(request.getClusterNodeId()); final Set<NodeIdentifier> targetNodes = Collections.singleton(nodeId); // replicate the request to the specific node NodeResponse nodeResponse; try { nodeResponse = requestReplicator .replicate(targetNodes, HttpMethod.GET, dataUri, parameters, headers) .awaitMergedResponse(); } catch (InterruptedException e) { throw new IllegalClusterStateException( "Interrupted while waiting for a response from node"); } final ClientResponse clientResponse = nodeResponse.getClientResponse(); final MultivaluedMap<String, String> responseHeaders = clientResponse.getHeaders(); // ensure an appropriate response if (Status.NOT_FOUND.getStatusCode() == clientResponse.getStatusInfo().getStatusCode()) { throw new ResourceNotFoundException(clientResponse.getEntity(String.class)); } else if (Status.FORBIDDEN.getStatusCode() == clientResponse.getStatusInfo().getStatusCode() || Status.UNAUTHORIZED.getStatusCode() == clientResponse.getStatusInfo().getStatusCode()) { throw new AccessDeniedException(clientResponse.getEntity(String.class)); } else if (Status.OK.getStatusCode() != clientResponse.getStatusInfo().getStatusCode()) { throw new IllegalStateException(clientResponse.getEntity(String.class)); } // get the file name final String contentDisposition = responseHeaders.getFirst("Content-Disposition"); final String filename = StringUtils.substringBetween(contentDisposition, "filename=\"", "\""); // get the content type final String contentType = responseHeaders.getFirst("Content-Type"); // create the downloadable content return new DownloadableContent(filename, contentType, clientResponse.getEntityInputStream()); } else { // example URIs: // http://localhost:8080/nifi-api/provenance/events/{id}/content/{input|output} // http://localhost:8080/nifi-api/flowfile-queues/{uuid}/flowfiles/{uuid}/content // get just the context path for comparison final String dataUri = StringUtils.substringAfter(request.getDataUri(), "/nifi-api"); if (StringUtils.isBlank(dataUri)) { throw new IllegalArgumentException("The specified data reference URI is not valid."); } // flowfile listing content final Matcher flowFileMatcher = FLOWFILE_CONTENT_URI_PATTERN.matcher(dataUri); if (flowFileMatcher.matches()) { final String connectionId = flowFileMatcher.group(1); final String flowfileId = flowFileMatcher.group(2); return getFlowFileContent(connectionId, flowfileId, dataUri); } // provenance event content final Matcher provenanceMatcher = PROVENANCE_CONTENT_URI_PATTERN.matcher(dataUri); if (provenanceMatcher.matches()) { try { final Long eventId = Long.parseLong(provenanceMatcher.group(1)); final ContentDirection direction = ContentDirection.valueOf(provenanceMatcher.group(2).toUpperCase()); return getProvenanceEventContent(eventId, dataUri, direction); } catch (final IllegalArgumentException iae) { throw new IllegalArgumentException("The specified data reference URI is not valid."); } } // invalid uri throw new IllegalArgumentException("The specified data reference URI is not valid."); } }
/** * Gets the content and defers to registered viewers to generate the markup. * * @param request servlet request * @param response servlet response * @throws ServletException if a servlet-specific error occurs * @throws IOException if an I/O error occurs */ @Override protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { // specify the charset in a response header response.addHeader("Content-Type", "text/html; charset=UTF-8"); // get the content final ServletContext servletContext = request.getServletContext(); final ContentAccess contentAccess = (ContentAccess) servletContext.getAttribute("nifi-content-access"); final ContentRequestContext contentRequest = getContentRequest(request); if (contentRequest.getDataUri() == null) { request.setAttribute("title", "Error"); request.setAttribute("messages", "The data reference must be specified."); // forward to the error page final ServletContext viewerContext = servletContext.getContext("/nifi"); viewerContext.getRequestDispatcher("/message").forward(request, response); return; } // get the content final DownloadableContent downloadableContent; try { downloadableContent = contentAccess.getContent(contentRequest); } catch (final ResourceNotFoundException rnfe) { request.setAttribute("title", "Error"); request.setAttribute("messages", "Unable to find the specified content"); // forward to the error page final ServletContext viewerContext = servletContext.getContext("/nifi"); viewerContext.getRequestDispatcher("/message").forward(request, response); return; } catch (final AccessDeniedException ade) { request.setAttribute("title", "Acess Denied"); request.setAttribute( "messages", "Unable to approve access to the specified content: " + ade.getMessage()); // forward to the error page final ServletContext viewerContext = servletContext.getContext("/nifi"); viewerContext.getRequestDispatcher("/message").forward(request, response); return; } catch (final Exception e) { request.setAttribute("title", "Error"); request.setAttribute("messages", "An unexcepted error has occurred: " + e.getMessage()); // forward to the error page final ServletContext viewerContext = servletContext.getContext("/nifi"); viewerContext.getRequestDispatcher("/message").forward(request, response); return; } // determine how we want to view the data String mode = request.getParameter("mode"); // if the name isn't set, use original if (mode == null) { mode = DisplayMode.Original.name(); } // determine the display mode final DisplayMode displayMode; try { displayMode = DisplayMode.valueOf(mode); } catch (final IllegalArgumentException iae) { request.setAttribute("title", "Error"); request.setAttribute("messages", "Invalid display mode: " + mode); // forward to the error page final ServletContext viewerContext = servletContext.getContext("/nifi"); viewerContext.getRequestDispatcher("/message").forward(request, response); return; } // buffer the content to support reseting in case we need to detect the content type or char // encoding try (final BufferedInputStream bis = new BufferedInputStream(downloadableContent.getContent()); ) { final String mimeType; // when standalone and we don't know the type is null as we were able to directly access the // content bypassing the rest endpoint, // when clustered and we don't know the type set to octet stream since the content was // retrieved from the node's rest endpoint if (downloadableContent.getType() == null || downloadableContent.getType().equals(MediaType.OCTET_STREAM.toString())) { // attempt to detect the content stream if we don't know what it is () final DefaultDetector detector = new DefaultDetector(); // create the stream for tika to process, buffered to support reseting final TikaInputStream tikaStream = TikaInputStream.get(bis); // provide a hint based on the filename final Metadata metadata = new Metadata(); metadata.set(Metadata.RESOURCE_NAME_KEY, downloadableContent.getFilename()); // Get mime type final MediaType mediatype = detector.detect(tikaStream, metadata); mimeType = mediatype.toString(); } else { mimeType = downloadableContent.getType(); } // add attributes needed for the header request.setAttribute("filename", downloadableContent.getFilename()); request.setAttribute("contentType", mimeType); // generate the header request.getRequestDispatcher("/WEB-INF/jsp/header.jsp").include(request, response); // remove the attributes needed for the header request.removeAttribute("filename"); request.removeAttribute("contentType"); // generate the markup for the content based on the display mode if (DisplayMode.Hex.equals(displayMode)) { final byte[] buffer = new byte[BUFFER_LENGTH]; final int read = StreamUtils.fillBuffer(bis, buffer, false); // trim the byte array if necessary byte[] bytes = buffer; if (read != buffer.length) { bytes = new byte[read]; System.arraycopy(buffer, 0, bytes, 0, read); } // convert bytes into the base 64 bytes final String base64 = Base64.encodeBase64String(bytes); // defer to the jsp request.setAttribute("content", base64); request.getRequestDispatcher("/WEB-INF/jsp/hexview.jsp").include(request, response); } else { // lookup a viewer for the content final String contentViewerUri = servletContext.getInitParameter(mimeType); // handle no viewer for content type if (contentViewerUri == null) { request.getRequestDispatcher("/WEB-INF/jsp/no-viewer.jsp").include(request, response); } else { // create a request attribute for accessing the content request.setAttribute( ViewableContent.CONTENT_REQUEST_ATTRIBUTE, new ViewableContent() { @Override public InputStream getContentStream() { return bis; } @Override public String getContent() throws IOException { // detect the charset final CharsetDetector detector = new CharsetDetector(); detector.setText(bis); detector.enableInputFilter(true); final CharsetMatch match = detector.detect(); // ensure we were able to detect the charset if (match == null) { throw new IOException("Unable to detect character encoding."); } // convert the stream using the detected charset return IOUtils.toString(bis, match.getName()); } @Override public ViewableContent.DisplayMode getDisplayMode() { return displayMode; } @Override public String getFileName() { return downloadableContent.getFilename(); } @Override public String getContentType() { return mimeType; } }); try { // generate the content final ServletContext viewerContext = servletContext.getContext(contentViewerUri); viewerContext.getRequestDispatcher("/view-content").include(request, response); } catch (final Exception e) { String message = e.getMessage() != null ? e.getMessage() : e.toString(); message = "Unable to generate view of data: " + message; // log the error logger.error(message); if (logger.isDebugEnabled()) { logger.error(StringUtils.EMPTY, e); } // populate the request attributes request.setAttribute("title", "Error"); request.setAttribute("messages", message); // forward to the error page final ServletContext viewerContext = servletContext.getContext("/nifi"); viewerContext.getRequestDispatcher("/message").forward(request, response); return; } // remove the request attribute request.removeAttribute(ViewableContent.CONTENT_REQUEST_ATTRIBUTE); } } // generate footer request.getRequestDispatcher("/WEB-INF/jsp/footer.jsp").include(request, response); } }