diff --git a/conf/app_config.xml b/conf/app_config.xml
new file mode 100644
index 00000000000..9519ca66e05
--- /dev/null
+++ b/conf/app_config.xml
@@ -0,0 +1,13 @@
+
+
+
+
+ zstack.properties
+
\ No newline at end of file
diff --git a/conf/zstack.xml b/conf/zstack.xml
index ed752bf46ba..d054d019034 100755
--- a/conf/zstack.xml
+++ b/conf/zstack.xml
@@ -22,9 +22,12 @@
- classpath:zstack.properties
+
+
+ classpath:${app.properties.file}
+
diff --git a/conf/zstackSimulator.xml b/conf/zstackSimulator.xml
index 8fc6028b201..f24deb79bd3 100755
--- a/conf/zstackSimulator.xml
+++ b/conf/zstackSimulator.xml
@@ -21,9 +21,10 @@
- classpath:zstack.properties
+ classpath:${app.properties.file}
+
diff --git a/core/src/main/java/org/zstack/core/Platform.java b/core/src/main/java/org/zstack/core/Platform.java
index a8d8ae191ed..9703394df34 100755
--- a/core/src/main/java/org/zstack/core/Platform.java
+++ b/core/src/main/java/org/zstack/core/Platform.java
@@ -21,6 +21,7 @@
import org.zstack.core.statemachine.StateMachine;
import org.zstack.core.statemachine.StateMachineImpl;
import org.zstack.core.thread.ThreadFacade;
+import org.zstack.core.config.AppConfig;
import org.zstack.header.Component;
import org.zstack.header.core.StaticInit;
import org.zstack.header.core.encrypt.ENCRYPT;
@@ -52,6 +53,10 @@
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.Inet4Address;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.NodeList;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
@@ -488,7 +493,14 @@ private static void prepareHibernateSearchProperties() {
}
}
- File globalPropertiesFile = PathUtil.findFileOnClassPath("zstack.properties", true);
+ // Load properties file name from app configuration
+ // This allows OEM customization by replacing app_config.xml during build
+ String propertiesFileName = AppConfig.getPropertiesFileName();
+
+ // Set system property for Spring to use the same file
+ System.setProperty("app.properties.file", propertiesFileName);
+
+ File globalPropertiesFile = PathUtil.findFileOnClassPath(propertiesFileName, true);
in = new FileInputStream(globalPropertiesFile);
System.getProperties().load(in);
diff --git a/core/src/main/java/org/zstack/core/config/AppConfig.java b/core/src/main/java/org/zstack/core/config/AppConfig.java
new file mode 100644
index 00000000000..a2c5a0e016b
--- /dev/null
+++ b/core/src/main/java/org/zstack/core/config/AppConfig.java
@@ -0,0 +1,68 @@
+package org.zstack.core.config;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.NodeList;
+import org.zstack.utils.path.PathUtil;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.File;
+
+/**
+ * Centralized configuration reader for app_config.xml
+ * This class provides a single source of truth for the properties file name
+ * All other components should use this class instead of hardcoding "zstack.properties"
+ */
+public class AppConfig {
+ private static final String DEFAULT_PROPERTIES_FILE = "zstack.properties";
+ private static volatile String propertiesFileName = null;
+
+ /**
+ * Get the properties file name from app_config.xml
+ * This method is thread-safe and caches the result
+ *
+ * @return properties file name (e.g., "zstack.properties", "myapp.properties")
+ */
+ public static String getPropertiesFileName() {
+ if (propertiesFileName == null) {
+ synchronized (AppConfig.class) {
+ if (propertiesFileName == null) {
+ propertiesFileName = loadPropertiesFileNameFromConfig();
+ }
+ }
+ }
+ return propertiesFileName;
+ }
+
+ /**
+ * Load properties file name from app_config.xml
+ * Falls back to "zstack.properties" if app_config.xml is not found or cannot be parsed
+ */
+ private static String loadPropertiesFileNameFromConfig() {
+ try {
+ File appConfigFile = PathUtil.findFileOnClassPath("app_config.xml", true);
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document doc = builder.parse(appConfigFile);
+
+ NodeList nodes = doc.getElementsByTagName("propertiesFile");
+ if (nodes.getLength() > 0) {
+ String fileName = nodes.item(0).getTextContent().trim();
+ System.out.println("[AppConfig] Using properties file: " + fileName);
+ return fileName;
+ }
+ } catch (Exception e) {
+ System.err.println("[AppConfig] Failed to load app_config.xml, using default: " + DEFAULT_PROPERTIES_FILE);
+ e.printStackTrace();
+ }
+
+ return DEFAULT_PROPERTIES_FILE;
+ }
+
+ /**
+ * Reset cached value (mainly for testing)
+ */
+ public static void reset() {
+ propertiesFileName = null;
+ }
+}
\ No newline at end of file
diff --git a/test/src/test/java/org/zstack/test/DBUtil.java b/test/src/test/java/org/zstack/test/DBUtil.java
index 1f1e7c18583..72ad41a7a5a 100755
--- a/test/src/test/java/org/zstack/test/DBUtil.java
+++ b/test/src/test/java/org/zstack/test/DBUtil.java
@@ -2,6 +2,7 @@
import org.apache.commons.io.FileUtils;
import org.zstack.core.Platform;
+import org.zstack.core.config.AppConfig;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.utils.ShellResult;
import org.zstack.utils.ShellUtils;
@@ -28,7 +29,7 @@ public static void reDeployDB() {
Properties prop = new Properties();
try {
- prop.load(DBUtil.class.getClassLoader().getResourceAsStream("zstack.properties"));
+ prop.load(DBUtil.class.getClassLoader().getResourceAsStream(AppConfig.getPropertiesFileName()));
String user = System.getProperty("DB.user");
if (user == null) {
diff --git a/test/src/test/resources/zstack-template.xml b/test/src/test/resources/zstack-template.xml
index 5a1c525669c..a32b58d5d83 100755
--- a/test/src/test/resources/zstack-template.xml
+++ b/test/src/test/resources/zstack-template.xml
@@ -17,9 +17,10 @@
- classpath:zstack.properties
+ classpath:${app.properties.file}
+