From 0b4cfb74a2d17615a7c5a87d99fbde75199f40ff Mon Sep 17 00:00:00 2001 From: IamMujuziMoses Date: Fri, 4 Apr 2025 16:16:27 +0300 Subject: [PATCH] TRUNK-6243: Add a new runtime property to determine if app is running in development or production mode --- .../org/openmrs/util/OpenmrsConstants.java | 7 ++ .../java/org/openmrs/util/OpenmrsUtil.java | 65 +++++++++++++++++++ .../org/openmrs/util/OpenmrsUtilTest.java | 48 ++++++++++++++ .../initialization/InitializationFilter.java | 2 + 4 files changed, 122 insertions(+) diff --git a/api/src/main/java/org/openmrs/util/OpenmrsConstants.java b/api/src/main/java/org/openmrs/util/OpenmrsConstants.java index e831017121a5..c847334cacd9 100644 --- a/api/src/main/java/org/openmrs/util/OpenmrsConstants.java +++ b/api/src/main/java/org/openmrs/util/OpenmrsConstants.java @@ -558,6 +558,13 @@ public static final Collection AUTO_ROLES() { public static final String ENCRYPTION_KEY_RUNTIME_PROPERTY = "encryption.key"; public static final String ENCRYPTION_KEY_DEFAULT = "dTfyELRrAICGDwzjHDjuhw=="; + + /** + * Runtime property that will tell the system if app is running in development or production mode. + */ + public static final String DEVELOPMENT_MODE_RUNTIME_PROPERTY = "development.mode"; + + public static final String DEVELOPMENT_MODE_DEFAULT = "true"; /** * Global property name for the visit type(s) to automatically close diff --git a/api/src/main/java/org/openmrs/util/OpenmrsUtil.java b/api/src/main/java/org/openmrs/util/OpenmrsUtil.java index 7b202f714975..855fc2d698e3 100644 --- a/api/src/main/java/org/openmrs/util/OpenmrsUtil.java +++ b/api/src/main/java/org/openmrs/util/OpenmrsUtil.java @@ -1946,6 +1946,71 @@ public static String shortenedStackTrace(String stackTrace) { return StringUtils.join(results, "\n"); } + /** + * Convert a stack trace into a formatted message version for easier viewing In production mode, instead of a page + * with stack traces. In development mode, we should continue to have stack traces. + * + * @param stackTrace original stack trace from an error + * @return formatted stack trace message + * Should return null if stackTrace is null + * Should return the Error and the Cause + * @since 2.8 + */ + public static String formattedStackTrace(String stackTrace) { + if (stackTrace == null) { + return null; + } + + // If not in dev mode, convert a stack trace into a formatted message version + String props = Context.getRuntimeProperties().getProperty(OpenmrsConstants.DEVELOPMENT_MODE_RUNTIME_PROPERTY); + if (StringUtils.equals(props, "false")) { + // Pattern to match the first exception and first "Caused by" + Pattern pattern = Pattern.compile("(?m)^(([\\w.$]+Exception|Error|Throwable): .*)|" + + "(Caused by: [\\w.$]+(Exception|Error|Throwable): .*)"); + + Matcher matcher = pattern.matcher(stackTrace); + StringBuilder result = new StringBuilder(); + boolean foundFirst = false; + boolean foundCause = false; + + while (matcher.find()) { + String line = matcher.group().trim(); + if (!foundFirst) { + if (isExceptionLine(line)) { + result.append(detectExceptionType(line)).append(": ").append(line); + } + foundFirst = true; + } else if (line.startsWith("Caused by:")) { + result.append("\n").append(line); + foundCause = true; + } + + if (foundCause) { + break; + } + } + + return result.toString(); + } else { + return stackTrace; + } + } + + private static boolean isExceptionLine(String line) { + return line.matches("([\\w$]+\\.)*[\\w$]+(Exception|Error|Throwable): .*"); + } + + private static String detectExceptionType(String line) { + if (line.contains(".hl7")) { + return "Error (HL7)"; + } else if (line.contains("org.springframework.")) { + return "Error (Spring)"; + } else if (line.contains("java.") || line.contains("javax.")) { + return "Error (Java)"; + } + return "Error"; + } + /** *
 	 * Finds and loads the runtime properties file for a specific OpenMRS application.
diff --git a/api/src/test/java/org/openmrs/util/OpenmrsUtilTest.java b/api/src/test/java/org/openmrs/util/OpenmrsUtilTest.java
index 82346adf714c..7831706cefd0 100644
--- a/api/src/test/java/org/openmrs/util/OpenmrsUtilTest.java
+++ b/api/src/test/java/org/openmrs/util/OpenmrsUtilTest.java
@@ -672,6 +672,54 @@ public void shortenedStackTrace_shouldRemoveSpringframeworkAndReflectionRelatedL
 		assertEquals(expected, OpenmrsUtil.shortenedStackTrace(test), "stack trace was not shortened properly");
 	}
 	
+	/**
+	 * @see OpenmrsUtil#formattedStackTrace(String)
+	 */
+	@Test
+	public void formattedStackTrace_shouldReturnTheErrorAndCauseAfterFormattingIfNotInDevMode() {
+		Properties props = Context.getRuntimeProperties();
+		props.setProperty(OpenmrsConstants.DEVELOPMENT_MODE_RUNTIME_PROPERTY, "false");
+		Context.setRuntimeProperties(props);
+		
+		String test = "ca.uhn.hl7v2.HL7Exception: Error while processing HL7 message: ORU_R01\n"
+			+ "\tat org.openmrs.hl7.impl.HL7ServiceImpl.processHL7Message(HL7ServiceImpl.java:752)\n"
+			+ "Caused by: ca.uhn.hl7v2.app.ApplicationException: ca.uhn.hl7v2.HL7Exception: Could not resolve patient by identifier\n"
+			+ "\tat org.openmrs.hl7.handler.ORUR01Handler.processMessage(ORUR01Handler.java:132)\n"
+			+ "Caused by: ca.uhn.hl7v2.HL7Exception: Could not resolve patient by identifier\n"
+			+ "\tat org.openmrs.hl7.handler.ORUR01Handler.getPatientByIdentifier(ORUR01Handler.java:998)";
+		
+		String expected = "Error (HL7): ca.uhn.hl7v2.HL7Exception: Error while processing HL7 message: ORU_R01\n" 
+			+ "Caused by: ca.uhn.hl7v2.app.ApplicationException: ca.uhn.hl7v2.HL7Exception: Could not resolve patient by identifier";
+		
+		assertEquals(expected, OpenmrsUtil.formattedStackTrace(test), "stack trace was not formatted properly");
+	}
+	
+	/**
+	 * @see OpenmrsUtil#formattedStackTrace(String)
+	 */
+	@Test
+	public void formattedStackTrace_shouldReturnTheUnFormattedStackTraceIfInDevMode() {
+		Properties props = Context.getRuntimeProperties();
+		props.setProperty(OpenmrsConstants.DEVELOPMENT_MODE_RUNTIME_PROPERTY, "true");
+		Context.setRuntimeProperties(props);
+		
+		String test = "ca.uhn.hl7v2.HL7Exception: Error while processing HL7 message: ORU_R01\n"
+			+ "\tat org.openmrs.hl7.impl.HL7ServiceImpl.processHL7Message(HL7ServiceImpl.java:752)\n"
+			+ "Caused by: ca.uhn.hl7v2.app.ApplicationException: ca.uhn.hl7v2.HL7Exception: Could not resolve patient by identifier\n"
+			+ "\tat org.openmrs.hl7.handler.ORUR01Handler.processMessage(ORUR01Handler.java:132)\n"
+			+ "Caused by: ca.uhn.hl7v2.HL7Exception: Could not resolve patient by identifier\n"
+			+ "\tat org.openmrs.hl7.handler.ORUR01Handler.getPatientByIdentifier(ORUR01Handler.java:998)";
+		
+		String expected = "ca.uhn.hl7v2.HL7Exception: Error while processing HL7 message: ORU_R01\n"
+			+ "\tat org.openmrs.hl7.impl.HL7ServiceImpl.processHL7Message(HL7ServiceImpl.java:752)\n"
+			+ "Caused by: ca.uhn.hl7v2.app.ApplicationException: ca.uhn.hl7v2.HL7Exception: Could not resolve patient by identifier\n"
+			+ "\tat org.openmrs.hl7.handler.ORUR01Handler.processMessage(ORUR01Handler.java:132)\n"
+			+ "Caused by: ca.uhn.hl7v2.HL7Exception: Could not resolve patient by identifier\n"
+			+ "\tat org.openmrs.hl7.handler.ORUR01Handler.getPatientByIdentifier(ORUR01Handler.java:998)";
+		
+		assertEquals(expected, OpenmrsUtil.formattedStackTrace(test), "stack trace was not returned properly");
+	}
+	
 	/**
 	 * @see OpenmrsUtil#shortenedStackTrace(String)
 	 */
diff --git a/web/src/main/java/org/openmrs/web/filter/initialization/InitializationFilter.java b/web/src/main/java/org/openmrs/web/filter/initialization/InitializationFilter.java
index c47a0aea1e93..83e18f9dca4b 100644
--- a/web/src/main/java/org/openmrs/web/filter/initialization/InitializationFilter.java
+++ b/web/src/main/java/org/openmrs/web/filter/initialization/InitializationFilter.java
@@ -1533,6 +1533,8 @@ public void run() {
 							new String(base64.encode(Security.generateNewInitVector()), StandardCharsets.UTF_8));
 						runtimeProperties.put(OpenmrsConstants.ENCRYPTION_KEY_RUNTIME_PROPERTY,
 							new String(base64.encode(Security.generateNewSecretKey()), StandardCharsets.UTF_8));
+						runtimeProperties.put(OpenmrsConstants.DEVELOPMENT_MODE_RUNTIME_PROPERTY,
+							OpenmrsConstants.DEVELOPMENT_MODE_DEFAULT);
 						
 						Properties properties = Context.getRuntimeProperties();
 						properties.putAll(runtimeProperties);