This is an automated email from the ASF dual-hosted git repository.
pvillard pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new 4f6a736f4b NIFI-15378 Removed empty lines from formatted XML in
Content Viewer
4f6a736f4b is described below
commit 4f6a736f4bae83ca8ccb0ae1e87153557b61117c
Author: exceptionfactory <[email protected]>
AuthorDate: Tue Dec 30 12:25:06 2025 -0600
NIFI-15378 Removed empty lines from formatted XML in Content Viewer
Co-authored-by: Pierre Villard <[email protected]>
Signed-off-by: Pierre Villard <[email protected]>
This closes #10711.
---
.../StandardContentViewerController.java | 65 +++++++--
.../StandardContentViewerControllerTest.java | 153 +++++++++++++++++++++
2 files changed, 209 insertions(+), 9 deletions(-)
diff --git
a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-content-viewer/src/main/java/org/apache/nifi/web/controller/StandardContentViewerController.java
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-content-viewer/src/main/java/org/apache/nifi/web/controller/StandardContentViewerController.java
index f249aeb4a9..e290dcd795 100644
---
a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-content-viewer/src/main/java/org/apache/nifi/web/controller/StandardContentViewerController.java
+++
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-content-viewer/src/main/java/org/apache/nifi/web/controller/StandardContentViewerController.java
@@ -41,12 +41,17 @@ import org.yaml.snakeyaml.Yaml;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
+import java.io.ByteArrayOutputStream;
+import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.net.HttpURLConnection;
public class StandardContentViewerController extends HttpServlet {
+ static final String CONTENT_ACCESS_ATTRIBUTE = "nifi-content-access";
+
private static final Logger logger =
LoggerFactory.getLogger(StandardContentViewerController.class);
@Override
@@ -55,7 +60,7 @@ public class StandardContentViewerController extends
HttpServlet {
// get the content
final ServletContext servletContext = request.getServletContext();
- final ContentAccess contentAccess = (ContentAccess)
servletContext.getAttribute("nifi-content-access");
+ final ContentAccess contentAccess = (ContentAccess)
servletContext.getAttribute(CONTENT_ACCESS_ATTRIBUTE);
// get the content
final DownloadableContent downloadableContent;
@@ -106,15 +111,14 @@ public class StandardContentViewerController extends
HttpServlet {
break;
}
case "xml": {
- // format xml
final StreamSource source = new
StreamSource(downloadableContent.getContent());
- final StreamResult result = new
StreamResult(response.getOutputStream());
-
- final StandardTransformProvider transformProvider = new
StandardTransformProvider();
- transformProvider.setIndent(true);
- transformProvider.setOmitXmlDeclaration(true);
-
- transformProvider.transform(source, result);
+ try (OutputStream outputStream = new
FormattingOutputStream(response.getOutputStream())) {
+ final StreamResult result = new
StreamResult(outputStream);
+ final StandardTransformProvider transformProvider =
new StandardTransformProvider();
+ transformProvider.setIndent(true);
+ transformProvider.setOmitXmlDeclaration(true);
+ transformProvider.transform(source, result);
+ }
break;
}
case "avro": {
@@ -198,4 +202,47 @@ public class StandardContentViewerController extends
HttpServlet {
case null, default -> null;
};
}
+
+ private static class FormattingOutputStream extends FilterOutputStream {
+
+ private static final byte LINE_FEED = 10;
+ private static final byte SPACE = 32;
+
+ private final ByteArrayOutputStream buffer = new
ByteArrayOutputStream();
+
+ private FormattingOutputStream(final OutputStream outputStream) {
+ super(outputStream);
+ }
+
+ @Override
+ public void write(final int currentByte) throws IOException {
+ buffer.write(currentByte);
+ if (currentByte == LINE_FEED) {
+ processBuffer();
+ }
+ }
+
+ private void processBuffer() throws IOException {
+ final byte[] bytes = buffer.toByteArray();
+
+ if (hasCharacters(bytes)) {
+ super.out.write(bytes);
+ }
+
+ buffer.reset();
+ }
+
+ private boolean hasCharacters(final byte[] bytes) {
+ boolean charactersFound = false;
+
+ for (byte currentByte : bytes) {
+ if (currentByte > SPACE) {
+ charactersFound = true;
+ break;
+ }
+ }
+
+ return charactersFound;
+ }
+ }
}
diff --git
a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-content-viewer/src/test/java/org/apache/nifi/web/controller/StandardContentViewerControllerTest.java
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-content-viewer/src/test/java/org/apache/nifi/web/controller/StandardContentViewerControllerTest.java
new file mode 100644
index 0000000000..038726a09b
--- /dev/null
+++
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-content-viewer/src/test/java/org/apache/nifi/web/controller/StandardContentViewerControllerTest.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.controller;
+
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.WriteListener;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.nifi.web.ContentAccess;
+import org.apache.nifi.web.DownloadableContent;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class StandardContentViewerControllerTest {
+
+ private static final String REF_PARAMETER = "ref";
+ private static final String REF_URL = "https://localhost:8443";
+ private static final String CLIENT_ID_PARAMETER = "clientId";
+ private static final String FORMATTED_PARAMETER = "formatted";
+ private static final String MIME_TYPE_DISPLAY_NAME = "mimeTypeDisplayName";
+ private static final String XML_DISPLAY_NAME = "xml";
+
+ private static final String FILENAME = "FlowFile";
+ private static final String TEXT_XML = "text/xml";
+ private static final String XML_DOCUMENT = "<?xml version=\"1.0\"
encoding=\"UTF-8\"?><document><element/></document>";
+ private static final String XML_DOCUMENT_FORMATTED = "<document>%n
<element/>%n</document>%n".formatted();
+
+ @Mock
+ private HttpServletRequest request;
+
+ @Mock
+ private HttpServletResponse response;
+
+ @Mock
+ private ServletContext servletContext;
+
+ @Mock
+ private ContentAccess contentAccess;
+
+ private final StandardContentViewerController controller = new
StandardContentViewerController();
+
+ @Test
+ void testDoGetNotFormatted() throws IOException {
+ final InputStream contentStream = new
ByteArrayInputStream(XML_DOCUMENT.getBytes(StandardCharsets.UTF_8));
+ final DownloadableContent downloadableContent = new
DownloadableContent(FILENAME, TEXT_XML, contentStream);
+ setDownloadableContent(downloadableContent);
+
+ final MockServletOutputStream mockServletOutputStream = new
MockServletOutputStream();
+ when(response.getOutputStream()).thenReturn(mockServletOutputStream);
+
+ controller.doGet(request, response);
+
+ final String outputString = mockServletOutputStream.getOutputString();
+ assertEquals(XML_DOCUMENT, outputString);
+ }
+
+ @Test
+ void testDoGetFormattedXml() throws IOException {
+ setDownloadableContentXml(XML_DOCUMENT);
+
+ final MockServletOutputStream mockServletOutputStream = new
MockServletOutputStream();
+ when(response.getOutputStream()).thenReturn(mockServletOutputStream);
+
+ controller.doGet(request, response);
+
+ final String outputString = mockServletOutputStream.getOutputString();
+ assertEquals(XML_DOCUMENT_FORMATTED, outputString);
+ }
+
+ @Test
+ void testDoGetFormattedXmlPreformatted() throws IOException {
+ setDownloadableContentXml(XML_DOCUMENT_FORMATTED);
+
+ final MockServletOutputStream mockServletOutputStream = new
MockServletOutputStream();
+ when(response.getOutputStream()).thenReturn(mockServletOutputStream);
+
+ controller.doGet(request, response);
+
+ final String outputString = mockServletOutputStream.getOutputString();
+ assertEquals(XML_DOCUMENT_FORMATTED, outputString);
+ }
+
+ private void setDownloadableContentXml(final String contentXml) {
+ final InputStream contentStream = new
ByteArrayInputStream(contentXml.getBytes(StandardCharsets.UTF_8));
+ final DownloadableContent downloadableContent = new
DownloadableContent(FILENAME, TEXT_XML, contentStream);
+ setDownloadableContent(downloadableContent);
+
+
when(request.getParameter(eq(FORMATTED_PARAMETER))).thenReturn(Boolean.TRUE.toString());
+
when(request.getParameter(eq(MIME_TYPE_DISPLAY_NAME))).thenReturn(XML_DISPLAY_NAME);
+
when(request.getParameter(eq(CLIENT_ID_PARAMETER))).thenReturn(UUID.randomUUID().toString());
+ }
+
+ private void setDownloadableContent(final DownloadableContent
downloadableContent) {
+ when(request.getServletContext()).thenReturn(servletContext);
+
when(servletContext.getAttribute(eq(StandardContentViewerController.CONTENT_ACCESS_ATTRIBUTE))).thenReturn(contentAccess);
+ when(request.getParameter(eq(REF_PARAMETER))).thenReturn(REF_URL);
+ when(contentAccess.getContent(any())).thenReturn(downloadableContent);
+ }
+
+ private static class MockServletOutputStream extends ServletOutputStream {
+
+ private final ByteArrayOutputStream outputStream = new
ByteArrayOutputStream();
+
+ @Override
+ public boolean isReady() {
+ return true;
+ }
+
+ @Override
+ public void setWriteListener(final WriteListener writeListener) {
+
+ }
+
+ @Override
+ public void write(final int b) {
+ outputStream.write(b);
+ }
+
+ private String getOutputString() {
+ return outputStream.toString(StandardCharsets.UTF_8);
+ }
+ }
+}