@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);
    }
  }
Exemplo n.º 2
0
  @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.");
    }
  }
Exemplo n.º 3
0
  /**
   * 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);
    }
  }