This is an automated email from the ASF dual-hosted git repository.
exceptionfactory 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 c7148f9197 NIFI-15104 Populate Bulletin stackTrace and for Bulletin
Board (#10431)
c7148f9197 is described below
commit c7148f9197c721f4ed9b7eff0c77be8ebf1014dc
Author: Pierre Villard <[email protected]>
AuthorDate: Fri Oct 17 18:40:43 2025 +0200
NIFI-15104 Populate Bulletin stackTrace and for Bulletin Board (#10431)
Signed-off-by: David Handermann <[email protected]>
---
.../org/apache/nifi/web/api/dto/BulletinDTO.java | 10 ++++
.../nifi/logging/ConnectableLogObserver.java | 4 +-
.../nifi/logging/ControllerServiceLogObserver.java | 2 +-
.../logging/FlowRegistryClientLogObserver.java | 2 +-
.../apache/nifi/logging/ProcessorLogObserver.java | 4 +-
.../nifi/logging/ReportingTaskLogObserver.java | 2 +-
.../org/apache/nifi/events/BulletinFactory.java | 64 +++++++++++++++++++++
.../nifi/events/BulletinFactoryStackTraceTest.java | 61 ++++++++++++++++++++
.../java/org/apache/nifi/jaxb/AdaptedBulletin.java | 9 +++
.../java/org/apache/nifi/jaxb/BulletinAdapter.java | 8 ++-
.../nifi/logging/FlowAnalysisRuleLogObserver.java | 2 +-
.../nifi/logging/ParameterProviderLogObserver.java | 2 +-
.../nifi/jaxb/BulletinAdapterStackTraceTest.java | 46 +++++++++++++++
.../apache/nifi/web/StandardNiFiServiceFacade.java | 18 +++---
.../org/apache/nifi/web/api/dto/DtoFactory.java | 13 +++--
.../api/dto/DtoFactoryBulletinStackTraceTest.java | 67 ++++++++++++++++++++++
16 files changed, 290 insertions(+), 24 deletions(-)
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/BulletinDTO.java
b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/BulletinDTO.java
index 6f5cc39547..51ee6b6388 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/BulletinDTO.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/BulletinDTO.java
@@ -39,6 +39,7 @@ public class BulletinDTO {
private String message;
private Date timestamp;
private String sourceType;
+ private String stackTrace;
/**
* @return id of this message
@@ -168,4 +169,13 @@ public class BulletinDTO {
public void setSourceType(String sourceType) {
this.sourceType = sourceType;
}
+
+ @Schema(description = "The stack trace associated with the bulletin, if
any.")
+ public String getStackTrace() {
+ return stackTrace;
+ }
+
+ public void setStackTrace(String stackTrace) {
+ this.stackTrace = stackTrace;
+ }
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ConnectableLogObserver.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ConnectableLogObserver.java
index 8ed66b9323..d05b04b6ea 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ConnectableLogObserver.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ConnectableLogObserver.java
@@ -38,7 +38,9 @@ public class ConnectableLogObserver implements LogObserver {
// Map LogLevel.WARN to Severity.WARNING so that we are consistent
with the Severity enumeration. Else, just use whatever
// the LogLevel is (INFO and ERROR map directly and all others we will
just accept as they are).
final String bulletinLevel = (message.getLogLevel() == LogLevel.WARN)
? Severity.WARNING.name() : message.getLogLevel().toString();
-
bulletinRepository.addBulletin(BulletinFactory.createBulletin(connectable,
CATEGORY, bulletinLevel, message.getMessage(), message.getFlowFileUuid()));
+ bulletinRepository.addBulletin(
+ BulletinFactory.createBulletin(connectable, CATEGORY,
bulletinLevel, message.getMessage(), message.getFlowFileUuid(),
message.getThrowable())
+ );
}
@Override
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ControllerServiceLogObserver.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ControllerServiceLogObserver.java
index 85ca37caee..0dfaa9aebf 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ControllerServiceLogObserver.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ControllerServiceLogObserver.java
@@ -45,7 +45,7 @@ public class ControllerServiceLogObserver implements
LogObserver {
final String groupName = pg == null ? null : pg.getName();
final Bulletin bulletin = BulletinFactory.createBulletin(groupId,
groupName, serviceNode.getIdentifier(), ComponentType.CONTROLLER_SERVICE,
- serviceNode.getName(), "Log Message", bulletinLevel,
message.getMessage());
+ serviceNode.getName(), "Log Message", bulletinLevel,
message.getMessage(), message.getThrowable());
bulletinRepository.addBulletin(bulletin);
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/FlowRegistryClientLogObserver.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/FlowRegistryClientLogObserver.java
index d7b0d5adbf..80376f3ab2 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/FlowRegistryClientLogObserver.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/FlowRegistryClientLogObserver.java
@@ -39,7 +39,7 @@ public class FlowRegistryClientLogObserver implements
LogObserver {
final String bulletinLevel = message.getLogLevel() == LogLevel.WARN ?
Severity.WARNING.name() : message.getLogLevel().toString();
final Bulletin bulletin = BulletinFactory.createBulletin(null,
clientNode.getIdentifier(), ComponentType.FLOW_REGISTRY_CLIENT,
- clientNode.getName(), "Log Message", bulletinLevel,
message.getMessage());
+ clientNode.getName(), "Log Message", bulletinLevel,
message.getMessage(), message.getThrowable());
bulletinRepository.addBulletin(bulletin);
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ProcessorLogObserver.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ProcessorLogObserver.java
index 18b99b4bfb..7209492ca3 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ProcessorLogObserver.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ProcessorLogObserver.java
@@ -41,7 +41,9 @@ public class ProcessorLogObserver implements LogObserver {
// Map LogLevel.WARN to Severity.WARNING so that we are consistent
with the Severity enumeration. Else, just use whatever
// the LogLevel is (INFO and ERROR map directly and all others we will
just accept as they are).
final String bulletinLevel = (message.getLogLevel() == LogLevel.WARN)
? Severity.WARNING.name() : message.getLogLevel().toString();
-
bulletinRepository.addBulletin(BulletinFactory.createBulletin(processorNode,
CATEGORY, bulletinLevel, message.getMessage(), message.getFlowFileUuid()));
+ bulletinRepository.addBulletin(
+ BulletinFactory.createBulletin(processorNode, CATEGORY,
bulletinLevel, message.getMessage(), message.getFlowFileUuid(),
message.getThrowable())
+ );
}
@Override
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ReportingTaskLogObserver.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ReportingTaskLogObserver.java
index 8631b4fb02..5df50132e7 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ReportingTaskLogObserver.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ReportingTaskLogObserver.java
@@ -39,7 +39,7 @@ public class ReportingTaskLogObserver implements LogObserver {
final String bulletinLevel = message.getLogLevel() == LogLevel.WARN ?
Severity.WARNING.name() : message.getLogLevel().toString();
final Bulletin bulletin = BulletinFactory.createBulletin(null,
taskNode.getIdentifier(), ComponentType.REPORTING_TASK,
- taskNode.getName(), "Log Message", bulletinLevel,
message.getMessage());
+ taskNode.getName(), "Log Message", bulletinLevel,
message.getMessage(), message.getThrowable());
bulletinRepository.addBulletin(bulletin);
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/events/BulletinFactory.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/events/BulletinFactory.java
index e9c14885df..443ac18e8e 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/events/BulletinFactory.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/events/BulletinFactory.java
@@ -21,6 +21,8 @@ import org.apache.nifi.groups.ProcessGroup;
import org.apache.nifi.reporting.Bulletin;
import org.apache.nifi.reporting.ComponentType;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.concurrent.atomic.AtomicLong;
public final class BulletinFactory {
@@ -50,6 +52,22 @@ public final class BulletinFactory {
return createBulletin(groupId, groupName, connectable.getIdentifier(),
type, connectable.getName(), category, severity, message, groupPath,
flowFileUUID);
}
+ public static Bulletin createBulletin(final Connectable connectable, final
String category, final String severity, final String message, final String
flowFileUUID, final Throwable t) {
+ final Bulletin bulletin = createBulletin(connectable, category,
severity, message, flowFileUUID);
+ if (t != null) {
+ bulletin.setStackTrace(formatStackTrace(t));
+ }
+ return bulletin;
+ }
+
+ public static Bulletin createBulletin(final Connectable connectable, final
String category, final String severity, final String message, final Throwable
t) {
+ final Bulletin bulletin = createBulletin(connectable, category,
severity, message);
+ if (t != null) {
+ bulletin.setStackTrace(formatStackTrace(t));
+ }
+ return bulletin;
+ }
+
private static String buildGroupPath(ProcessGroup group) {
if (group == null) {
return null;
@@ -78,6 +96,15 @@ public final class BulletinFactory {
return bulletin;
}
+ public static Bulletin createBulletin(final String groupId, final String
sourceId, final ComponentType sourceType, final String sourceName,
+ final String category, final String severity, final String message,
final Throwable t) {
+ final Bulletin bulletin = createBulletin(groupId, sourceId,
sourceType, sourceName, category, severity, message);
+ if (t != null) {
+ bulletin.setStackTrace(formatStackTrace(t));
+ }
+ return bulletin;
+ }
+
public static Bulletin createBulletin(final String groupId, final String
groupName, final String sourceId, final ComponentType sourceType,
final String sourceName, final String category, final String
severity, final String message) {
final Bulletin bulletin = new
ComponentBulletin(currentId.getAndIncrement());
@@ -92,6 +119,15 @@ public final class BulletinFactory {
return bulletin;
}
+ public static Bulletin createBulletin(final String groupId, final String
groupName, final String sourceId, final ComponentType sourceType,
+ final String sourceName, final String category, final String
severity, final String message, final Throwable t) {
+ final Bulletin bulletin = createBulletin(groupId, groupName, sourceId,
sourceType, sourceName, category, severity, message);
+ if (t != null) {
+ bulletin.setStackTrace(formatStackTrace(t));
+ }
+ return bulletin;
+ }
+
public static Bulletin createBulletin(final String groupId, final String
groupName, final String sourceId, final ComponentType sourceType,
final String sourceName, final String category, final String
severity, final String message, final String groupPath, final String
flowFileUUID) {
final Bulletin bulletin = new
ComponentBulletin(currentId.getAndIncrement());
@@ -108,6 +144,15 @@ public final class BulletinFactory {
return bulletin;
}
+ public static Bulletin createBulletin(final String groupId, final String
groupName, final String sourceId, final ComponentType sourceType,
+ final String sourceName, final String category, final String
severity, final String message, final String groupPath, final String
flowFileUUID, final Throwable t) {
+ final Bulletin bulletin = createBulletin(groupId, groupName, sourceId,
sourceType, sourceName, category, severity, message, groupPath, flowFileUUID);
+ if (t != null) {
+ bulletin.setStackTrace(formatStackTrace(t));
+ }
+ return bulletin;
+ }
+
public static Bulletin createBulletin(final String category, final String
severity, final String message) {
final Bulletin bulletin = new
SystemBulletin(currentId.getAndIncrement());
bulletin.setCategory(category);
@@ -117,6 +162,14 @@ public final class BulletinFactory {
return bulletin;
}
+ public static Bulletin createBulletin(final String category, final String
severity, final String message, final Throwable t) {
+ final Bulletin bulletin = createBulletin(category, severity, message);
+ if (t != null) {
+ bulletin.setStackTrace(formatStackTrace(t));
+ }
+ return bulletin;
+ }
+
private static ComponentType getComponentType(final Connectable
connectable) {
return switch (connectable.getConnectableType()) {
case REMOTE_INPUT_PORT, REMOTE_OUTPUT_PORT ->
ComponentType.REMOTE_PROCESS_GROUP;
@@ -126,4 +179,15 @@ public final class BulletinFactory {
default -> ComponentType.PROCESSOR;
};
}
+
+ private static String formatStackTrace(final Throwable t) {
+ try (final StringWriter sw = new StringWriter(); final PrintWriter pw
= new PrintWriter(sw)) {
+ t.printStackTrace(pw);
+ pw.flush();
+ return sw.toString();
+ } catch (final Exception e) {
+ // Fallback to Throwable#toString if printing fails for any reason
+ return t.toString();
+ }
+ }
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/events/BulletinFactoryStackTraceTest.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/events/BulletinFactoryStackTraceTest.java
new file mode 100644
index 0000000000..7b4e94da09
--- /dev/null
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/events/BulletinFactoryStackTraceTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.events;
+
+import org.apache.nifi.reporting.Bulletin;
+import org.apache.nifi.reporting.ComponentType;
+import org.junit.jupiter.api.Test;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class BulletinFactoryStackTraceTest {
+
+ private static String toStackTrace(final Throwable t) {
+ final StringWriter sw = new StringWriter();
+ try (PrintWriter pw = new PrintWriter(sw)) {
+ t.printStackTrace(pw);
+ }
+ return sw.toString();
+ }
+
+ @Test
+ void testCreateBulletinWithThrowableIncludesPrintableStackTrace() {
+ final Exception cause = new IllegalStateException("inner");
+ final RuntimeException ex = new RuntimeException("outer", cause);
+
+ final Bulletin bulletin = BulletinFactory.createBulletin(
+ "pg1", "Process Group 1", "proc1", ComponentType.PROCESSOR,
"MyProcessor",
+ "Log Message", "ERROR", "Something failed", "/root / Process
Group 1", null, ex);
+
+ assertNotNull(bulletin);
+ final String stackTrace = bulletin.getStackTrace();
+ assertNotNull(stackTrace, "Stack trace should be set on bulletin");
+
+ final String expected = toStackTrace(ex);
+ assertEquals(expected, stackTrace, "Stack trace should match
Throwable.printStackTrace output exactly");
+ assertTrue(stackTrace.contains(cause.getClass().getSimpleName()));
+ assertTrue(stackTrace.contains(ex.getClass().getSimpleName()));
+ assertTrue(stackTrace.contains("\n"), "Stack trace should contain
newlines for multiline formatting");
+ assertTrue(stackTrace.contains("\tat ") || stackTrace.contains("\n\tat
"), "Stack trace should include frame lines");
+ }
+}
+
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/jaxb/AdaptedBulletin.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/jaxb/AdaptedBulletin.java
index 9fc0626dc5..623bd45ac9 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/jaxb/AdaptedBulletin.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/jaxb/AdaptedBulletin.java
@@ -30,6 +30,7 @@ public class AdaptedBulletin {
private String level;
private String category;
private String message;
+ private String stackTrace;
private String groupId;
private String groupName;
@@ -85,6 +86,14 @@ public class AdaptedBulletin {
this.message = message;
}
+ public String getStackTrace() {
+ return stackTrace;
+ }
+
+ public void setStackTrace(String stackTrace) {
+ this.stackTrace = stackTrace;
+ }
+
public String getSourceId() {
return sourceId;
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/jaxb/BulletinAdapter.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/jaxb/BulletinAdapter.java
index 8a111f1cb0..9e6c17cc28 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/jaxb/BulletinAdapter.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/jaxb/BulletinAdapter.java
@@ -31,12 +31,15 @@ public class BulletinAdapter extends
XmlAdapter<AdaptedBulletin, Bulletin> {
return null;
}
// TODO - timestamp is overridden here with a new timestamp... address?
+ final Bulletin bulletin;
if (b.getSourceId() == null) {
- return BulletinFactory.createBulletin(b.getCategory(),
b.getLevel(), b.getMessage());
+ bulletin = BulletinFactory.createBulletin(b.getCategory(),
b.getLevel(), b.getMessage());
} else {
- return BulletinFactory.createBulletin(b.getGroupId(),
b.getGroupName(), b.getSourceId(), b.getSourceType(),
+ bulletin = BulletinFactory.createBulletin(b.getGroupId(),
b.getGroupName(), b.getSourceId(), b.getSourceType(),
b.getSourceName(), b.getCategory(), b.getLevel(),
b.getMessage());
}
+ bulletin.setStackTrace(b.getStackTrace());
+ return bulletin;
}
@Override
@@ -55,6 +58,7 @@ public class BulletinAdapter extends
XmlAdapter<AdaptedBulletin, Bulletin> {
aBulletin.setCategory(b.getCategory());
aBulletin.setLevel(b.getLevel());
aBulletin.setMessage(b.getMessage());
+ aBulletin.setStackTrace(b.getStackTrace());
return aBulletin;
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/logging/FlowAnalysisRuleLogObserver.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/logging/FlowAnalysisRuleLogObserver.java
index bb4ad15611..cbcd85fcb8 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/logging/FlowAnalysisRuleLogObserver.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/logging/FlowAnalysisRuleLogObserver.java
@@ -39,7 +39,7 @@ public class FlowAnalysisRuleLogObserver implements
LogObserver {
final String bulletinLevel = message.getLogLevel() == LogLevel.WARN ?
Severity.WARNING.name() : message.getLogLevel().toString();
final Bulletin bulletin = BulletinFactory.createBulletin(null,
flowAnalysisRuleNode.getIdentifier(), ComponentType.FLOW_ANALYSIS_RULE,
- flowAnalysisRuleNode.getName(), "Log Message", bulletinLevel,
message.getMessage());
+ flowAnalysisRuleNode.getName(), "Log Message", bulletinLevel,
message.getMessage(), message.getThrowable());
bulletinRepository.addBulletin(bulletin);
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/logging/ParameterProviderLogObserver.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/logging/ParameterProviderLogObserver.java
index e4aa3e0037..210866ab7c 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/logging/ParameterProviderLogObserver.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/logging/ParameterProviderLogObserver.java
@@ -39,7 +39,7 @@ public class ParameterProviderLogObserver implements
LogObserver {
final String bulletinLevel = message.getLogLevel() == LogLevel.WARN ?
Severity.WARNING.name() : message.getLogLevel().toString();
final Bulletin bulletin = BulletinFactory.createBulletin(null,
parameterProviderNode.getIdentifier(), ComponentType.PARAMETER_PROVIDER,
- parameterProviderNode.getName(), "Log Message", bulletinLevel,
message.getMessage());
+ parameterProviderNode.getName(), "Log Message", bulletinLevel,
message.getMessage(), message.getThrowable());
bulletinRepository.addBulletin(bulletin);
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/jaxb/BulletinAdapterStackTraceTest.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/jaxb/BulletinAdapterStackTraceTest.java
new file mode 100644
index 0000000000..6854d4f325
--- /dev/null
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/jaxb/BulletinAdapterStackTraceTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.jaxb;
+
+import org.apache.nifi.events.BulletinFactory;
+import org.apache.nifi.reporting.Bulletin;
+import org.apache.nifi.reporting.ComponentType;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class BulletinAdapterStackTraceTest {
+
+ @Test
+ void testMarshalUnmarshalCarriesStackTrace() throws Exception {
+ final Throwable t = new NullPointerException("npe");
+ final Bulletin original = BulletinFactory.createBulletin(
+ "g", "G", "id", ComponentType.PROCESSOR, "Name",
+ "Category", "ERROR", "msg", "/G", null, t);
+
+ final BulletinAdapter adapter = new BulletinAdapter();
+ final AdaptedBulletin adapted = adapter.marshal(original);
+ assertNotNull(adapted);
+ assertEquals(original.getStackTrace(), adapted.getStackTrace(),
"AdaptedBulletin must copy stackTrace");
+
+ final Bulletin roundTrip = adapter.unmarshal(adapted);
+ assertNotNull(roundTrip);
+ assertEquals(original.getStackTrace(), roundTrip.getStackTrace(),
"Unmarshalled Bulletin must preserve stackTrace");
+ }
+}
+
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index e4b234dd82..0d8685ce1b 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -2573,7 +2573,7 @@ public class StandardNiFiServiceFacade implements
NiFiServiceFacade {
public BulletinEntity createBulletin(final BulletinDTO bulletinDTO, final
Boolean canRead) {
final Bulletin bulletin =
BulletinFactory.createBulletin(bulletinDTO.getCategory(),
bulletinDTO.getLevel(), bulletinDTO.getMessage());
bulletinRepository.addBulletin(bulletin);
- return
entityFactory.createBulletinEntity(dtoFactory.createBulletinDto(bulletin),
canRead);
+ return
entityFactory.createBulletinEntity(dtoFactory.createBulletinDto(bulletin,
false), canRead);
}
@Override
@@ -4109,7 +4109,7 @@ public class StandardNiFiServiceFacade implements
NiFiServiceFacade {
final List<BulletinEntity> bulletinEntities = new ArrayList<>();
for (final ListIterator<Bulletin> bulletinIter =
results.listIterator(results.size()); bulletinIter.hasPrevious(); ) {
final Bulletin bulletin = bulletinIter.previous();
-
bulletinEntities.add(entityFactory.createBulletinEntity(dtoFactory.createBulletinDto(bulletin),
authorizeBulletin(bulletin)));
+
bulletinEntities.add(entityFactory.createBulletinEntity(dtoFactory.createBulletinDto(bulletin,
true), authorizeBulletin(bulletin)));
}
// create the bulletin board
@@ -4465,7 +4465,7 @@ public class StandardNiFiServiceFacade implements
NiFiServiceFacade {
final Authorizable controllerServiceAuthorizable =
authorizableLookup.getControllerService(bulletin.getSourceId()).getAuthorizable();
final boolean controllerServiceAuthorized =
controllerServiceAuthorizable.isAuthorized(authorizer, RequestAction.READ,
user);
- final BulletinEntity controllerServiceBulletin =
entityFactory.createBulletinEntity(dtoFactory.createBulletinDto(bulletin),
controllerServiceAuthorized);
+ final BulletinEntity controllerServiceBulletin =
entityFactory.createBulletinEntity(dtoFactory.createBulletinDto(bulletin,
false), controllerServiceAuthorized);
controllerServiceBulletinEntities.add(controllerServiceBulletin);
controllerBulletinEntities.add(controllerServiceBulletin);
} catch (final ResourceNotFoundException ignored) {
@@ -4483,7 +4483,7 @@ public class StandardNiFiServiceFacade implements
NiFiServiceFacade {
final Authorizable reportingTaskAuthorizable =
authorizableLookup.getReportingTask(bulletin.getSourceId()).getAuthorizable();
final boolean reportingTaskAuthorizableAuthorized =
reportingTaskAuthorizable.isAuthorized(authorizer, RequestAction.READ, user);
- final BulletinEntity reportingTaskBulletin =
entityFactory.createBulletinEntity(dtoFactory.createBulletinDto(bulletin),
reportingTaskAuthorizableAuthorized);
+ final BulletinEntity reportingTaskBulletin =
entityFactory.createBulletinEntity(dtoFactory.createBulletinDto(bulletin,
false), reportingTaskAuthorizableAuthorized);
reportingTaskBulletinEntities.add(reportingTaskBulletin);
controllerBulletinEntities.add(reportingTaskBulletin);
} catch (final ResourceNotFoundException ignored) {
@@ -4501,7 +4501,7 @@ public class StandardNiFiServiceFacade implements
NiFiServiceFacade {
final Authorizable flowAnalysisRuleAuthorizable =
authorizableLookup.getFlowAnalysisRule(bulletin.getSourceId()).getAuthorizable();
final boolean flowAnalysisRuleAuthorizableAuthorized =
flowAnalysisRuleAuthorizable.isAuthorized(authorizer, RequestAction.READ, user);
- final BulletinEntity flowAnalysisRuleBulletin =
entityFactory.createBulletinEntity(dtoFactory.createBulletinDto(bulletin),
flowAnalysisRuleAuthorizableAuthorized);
+ final BulletinEntity flowAnalysisRuleBulletin =
entityFactory.createBulletinEntity(dtoFactory.createBulletinDto(bulletin,
false), flowAnalysisRuleAuthorizableAuthorized);
flowAnalysisRuleBulletinEntities.add(flowAnalysisRuleBulletin);
controllerBulletinEntities.add(flowAnalysisRuleBulletin);
} catch (final ResourceNotFoundException ignored) {
@@ -4519,7 +4519,7 @@ public class StandardNiFiServiceFacade implements
NiFiServiceFacade {
final Authorizable parameterProviderAuthorizable =
authorizableLookup.getParameterProvider(bulletin.getSourceId()).getAuthorizable();
final boolean parameterProviderAuthorizableAuthorized =
parameterProviderAuthorizable.isAuthorized(authorizer, RequestAction.READ,
user);
- final BulletinEntity parameterProviderBulletin =
entityFactory.createBulletinEntity(dtoFactory.createBulletinDto(bulletin),
parameterProviderAuthorizableAuthorized);
+ final BulletinEntity parameterProviderBulletin =
entityFactory.createBulletinEntity(dtoFactory.createBulletinDto(bulletin,
false), parameterProviderAuthorizableAuthorized);
parameterProviderBulletinEntities.add(parameterProviderBulletin);
controllerBulletinEntities.add(parameterProviderBulletin);
} catch (final ResourceNotFoundException ignored) {
@@ -4537,7 +4537,7 @@ public class StandardNiFiServiceFacade implements
NiFiServiceFacade {
final Authorizable flowRegistryClientAuthorizable =
authorizableLookup.getFlowRegistryClient(bulletin.getSourceId()).getAuthorizable();
final boolean flowRegistryClientkAuthorizableAuthorized =
flowRegistryClientAuthorizable.isAuthorized(authorizer, RequestAction.READ,
user);
- final BulletinEntity flowRegistryClient =
entityFactory.createBulletinEntity(dtoFactory.createBulletinDto(bulletin),
flowRegistryClientkAuthorizableAuthorized);
+ final BulletinEntity flowRegistryClient =
entityFactory.createBulletinEntity(dtoFactory.createBulletinDto(bulletin,
false), flowRegistryClientkAuthorizableAuthorized);
flowRegistryClientBulletinEntities.add(flowRegistryClient);
controllerBulletinEntities.add(flowRegistryClient);
} catch (final ResourceNotFoundException ignored) {
@@ -4795,7 +4795,7 @@ public class StandardNiFiServiceFacade implements
NiFiServiceFacade {
List<BulletinEntity> bulletinEntities = new ArrayList<>();
for (final Bulletin bulletin : bulletins) {
-
bulletinEntities.add(entityFactory.createBulletinEntity(dtoFactory.createBulletinDto(bulletin),
authorizeBulletin(bulletin)));
+
bulletinEntities.add(entityFactory.createBulletinEntity(dtoFactory.createBulletinDto(bulletin,
false), authorizeBulletin(bulletin)));
}
return pruneAndSortBulletins(bulletinEntities,
BulletinRepository.MAX_BULLETINS_PER_COMPONENT);
@@ -6599,7 +6599,7 @@ public class StandardNiFiServiceFacade implements
NiFiServiceFacade {
final RevisionDTO revisionDto = dtoFactory.createRevisionDTO(revision);
final PermissionsDTO permissionsDto =
dtoFactory.createPermissionsDto(processor);
final List<BulletinEntity> bulletins =
bulletinRepository.findBulletinsForSource(id).stream()
- .map(bulletin -> dtoFactory.createBulletinDto(bulletin))
+ .map(bulletin -> dtoFactory.createBulletinDto(bulletin, false))
.map(bulletin -> entityFactory.createBulletinEntity(bulletin,
permissionsDto.getCanRead()))
.collect(Collectors.toList());
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
index 737410f066..1c401ba690 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
@@ -16,6 +16,7 @@
*/
package org.apache.nifi.web.api.dto;
+import jakarta.ws.rs.WebApplicationException;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
@@ -119,9 +120,9 @@ import org.apache.nifi.diagnostics.DiagnosticLevel;
import org.apache.nifi.diagnostics.GarbageCollection;
import org.apache.nifi.diagnostics.StorageUsage;
import org.apache.nifi.diagnostics.SystemDiagnostics;
-import org.apache.nifi.flowanalysis.FlowAnalysisRule;
import org.apache.nifi.flow.VersionedComponent;
import org.apache.nifi.flow.VersionedProcessGroup;
+import org.apache.nifi.flowanalysis.FlowAnalysisRule;
import org.apache.nifi.flowfile.FlowFilePrioritizer;
import org.apache.nifi.flowfile.attributes.CoreAttributes;
import org.apache.nifi.groups.ProcessGroup;
@@ -255,8 +256,6 @@ import
org.apache.nifi.web.api.entity.RemoteProcessGroupStatusSnapshotEntity;
import org.apache.nifi.web.api.entity.TenantEntity;
import org.apache.nifi.web.controller.ControllerFacade;
import org.apache.nifi.web.revision.RevisionManager;
-
-import jakarta.ws.rs.WebApplicationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -3466,18 +3465,19 @@ public final class DtoFactory {
public List<BulletinDTO> createBulletinDtos(final List<Bulletin> bulletins)
{
final List<BulletinDTO> bulletinDtos = new
ArrayList<>(bulletins.size());
for (final Bulletin bulletin : bulletins) {
- bulletinDtos.add(createBulletinDto(bulletin));
+ bulletinDtos.add(createBulletinDto(bulletin, false));
}
return bulletinDtos;
}
/**
- * Creates a BulletinDTO for the specified Bulletin.
+ * Creates a BulletinDTO for the specified Bulletin with optional stack
trace inclusion.
*
* @param bulletin bulletin
+ * @param includeStackTrace whether to include stack trace
* @return dto
*/
- public BulletinDTO createBulletinDto(final Bulletin bulletin) {
+ public BulletinDTO createBulletinDto(final Bulletin bulletin, final boolean
includeStackTrace) {
final BulletinDTO dto = new BulletinDTO();
dto.setId(bulletin.getId());
dto.setNodeAddress(bulletin.getNodeAddress());
@@ -3489,6 +3489,7 @@ public final class DtoFactory {
dto.setLevel(bulletin.getLevel());
dto.setMessage(bulletin.getMessage());
dto.setSourceType(bulletin.getSourceType().name());
+ dto.setStackTrace(includeStackTrace ? bulletin.getStackTrace() : null);
return dto;
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/dto/DtoFactoryBulletinStackTraceTest.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/dto/DtoFactoryBulletinStackTraceTest.java
new file mode 100644
index 0000000000..dc06fcd62c
--- /dev/null
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/dto/DtoFactoryBulletinStackTraceTest.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.api.dto;
+
+import org.apache.nifi.events.BulletinFactory;
+import org.apache.nifi.reporting.Bulletin;
+import org.apache.nifi.reporting.ComponentType;
+import org.junit.jupiter.api.Test;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class DtoFactoryBulletinStackTraceTest {
+
+ private static String toStackTrace(final Throwable t) {
+ final StringWriter sw = new StringWriter();
+ try (PrintWriter pw = new PrintWriter(sw)) {
+ t.printStackTrace(pw);
+ }
+ return sw.toString();
+ }
+
+ @Test
+ void testBulletinDtoDoesNotIncludeStackTraceByDefault() {
+ final Throwable ex = new IllegalArgumentException("invalid");
+ final Bulletin bulletin = BulletinFactory.createBulletin(
+ "pg", "PG", "svc1", ComponentType.CONTROLLER_SERVICE, "CS",
+ "Log Message", "ERROR", "boom", "/PG", null, ex);
+
+ final DtoFactory dtoFactory = new DtoFactory();
+ final BulletinDTO dto = dtoFactory.createBulletinDto(bulletin, false);
+
+ assertNotNull(dto);
+ assertEquals(null, dto.getStackTrace(), "DTO must not include
stackTrace by default");
+ }
+
+ @Test
+ void testBulletinDtoIncludesStackTraceWhenRequested() {
+ final Throwable ex = new IllegalArgumentException("invalid");
+ final Bulletin bulletin = BulletinFactory.createBulletin(
+ "pg", "PG", "svc1", ComponentType.CONTROLLER_SERVICE, "CS",
+ "Log Message", "ERROR", "boom", "/PG", null, ex);
+
+ final DtoFactory dtoFactory = new DtoFactory();
+ final BulletinDTO dto = dtoFactory.createBulletinDto(bulletin, true);
+
+ assertNotNull(dto);
+ assertEquals(toStackTrace(ex), dto.getStackTrace(), "DTO must carry
stackTrace when requested");
+ }
+}