From 37ac40392328826adabc6ed3f45269fe0c56ac50 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 3 Aug 2020 16:27:05 +0200 Subject: [PATCH] [New] Added bundle persisting to LogMessage and retrieval scheme --- .../src/main/java/li/strolch/model/Tags.java | 2 + .../model/i18n/I18nMessageJsonParser.java | 3 +- .../java/li/strolch/model/log/LogMessage.java | 9 +- .../postgresql/PostgreSqlLogMessageDao.java | 28 +- .../strolch_db_schema_0.9.1_drop.sql | 16 ++ .../strolch_db_schema_0.9.1_initial.sql | 217 +++++++++++++++ .../strolch_db_schema_0.9.1_migration.sql | 18 ++ .../resources/strolch_db_version.properties | 2 +- .../xml/model/LogMessageSaxReader.java | 8 +- .../java/li/strolch/utils/I18nMessage.java | 261 +++++++++++++++++- 10 files changed, 532 insertions(+), 32 deletions(-) create mode 100644 li.strolch.persistence.postgresql/src/main/resources/strolch_db_schema_0.9.1_drop.sql create mode 100644 li.strolch.persistence.postgresql/src/main/resources/strolch_db_schema_0.9.1_initial.sql create mode 100644 li.strolch.persistence.postgresql/src/main/resources/strolch_db_schema_0.9.1_migration.sql diff --git a/li.strolch.model/src/main/java/li/strolch/model/Tags.java b/li.strolch.model/src/main/java/li/strolch/model/Tags.java index 4dcc0c88b..6ceb193e6 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/Tags.java +++ b/li.strolch.model/src/main/java/li/strolch/model/Tags.java @@ -76,6 +76,7 @@ public class Tags { public static final String LOCATOR = "Locator"; public static final String SEVERITY = "Severity"; public static final String KEY = "Key"; + public static final String BUNDLE = "Bundle"; public static final String LOG_MESSAGE = "LogMessage"; public static final String MESSAGE = "Message"; public static final String EXCEPTION = "Exception"; @@ -133,6 +134,7 @@ public class Tags { public static final String LOCATOR = "locator"; public static final String SEVERITY = "severity"; public static final String KEY = "key"; + public static final String BUNDLE = "bundle"; public static final String MESSAGE = "message"; public static final String MESSAGES = "messages"; public static final String REALM = "realm"; diff --git a/li.strolch.model/src/main/java/li/strolch/model/i18n/I18nMessageJsonParser.java b/li.strolch.model/src/main/java/li/strolch/model/i18n/I18nMessageJsonParser.java index f208f19e3..539777b21 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/i18n/I18nMessageJsonParser.java +++ b/li.strolch.model/src/main/java/li/strolch/model/i18n/I18nMessageJsonParser.java @@ -14,6 +14,7 @@ public class I18nMessageJsonParser { public I18nMessage parse(JsonObject messageJ) { String key = messageJ.get(Tags.Json.KEY).getAsString(); + String bundle = messageJ.get(Tags.Json.BUNDLE).getAsString(); String message = messageJ.get(Tags.Json.MESSAGE).getAsString(); Properties properties = new Properties(); @@ -29,6 +30,6 @@ public class I18nMessageJsonParser { } } - return new I18nMessage(key, properties, message); + return new I18nMessage(bundle, key, properties, message); } } diff --git a/li.strolch.model/src/main/java/li/strolch/model/log/LogMessage.java b/li.strolch.model/src/main/java/li/strolch/model/log/LogMessage.java index 9bea94e39..52b489714 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/log/LogMessage.java +++ b/li.strolch.model/src/main/java/li/strolch/model/log/LogMessage.java @@ -54,9 +54,9 @@ public class LogMessage extends I18nMessage { } public LogMessage(String id, ZonedDateTime zonedDateTime, String realm, String username, Locator locator, - LogSeverity severity, LogMessageState state, String key, Properties values, String message, + LogSeverity severity, LogMessageState state, String bundle, String key, Properties values, String message, String stackTrace) { - super(key, values, message); + super(bundle, key, values, message); this.id = id; this.zonedDateTime = zonedDateTime; this.realm = realm; @@ -153,6 +153,7 @@ public class LogMessage extends I18nMessage { LogSeverity severity = LogSeverity.valueOf(messageJ.get(Json.SEVERITY).getAsString()); LogMessageState state = LogMessageState.valueOf(messageJ.get(Json.STATE).getAsString()); String key = messageJ.get(Json.KEY).getAsString(); + String bundle = messageJ.get(Json.BUNDLE).getAsString(); String message = messageJ.get(Json.MESSAGE).getAsString(); String stackTrace = messageJ.has(Json.EXCEPTION) ? messageJ.get(Json.EXCEPTION).getAsString() : ""; @@ -164,8 +165,8 @@ public class LogMessage extends I18nMessage { } } - return new LogMessage(id, zonedDateTime, realm, username, locator, severity, state, key, properties, message, - stackTrace); + return new LogMessage(id, zonedDateTime, realm, username, locator, severity, state, bundle, key, properties, + message, stackTrace); } @Override diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlLogMessageDao.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlLogMessageDao.java index 98891e98c..daf24b15a 100644 --- a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlLogMessageDao.java +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlLogMessageDao.java @@ -26,12 +26,13 @@ public class PostgreSqlLogMessageDao implements LogMessageDao { private static final String USERNAME = "username"; private static final String SEVERITY = "severity"; private static final String LOCATOR = "locator"; + private static final String BUNDLE = "bundle"; private static final String KEY = "key"; private static final String MESSAGE = "message"; private static final String STACK_TRACE = "stacktrace"; private static final String STATE = "state"; - private static final String FIELDS = commaSeparated(ID, REALM, DATE_TIME, USERNAME, SEVERITY, STATE, LOCATOR, KEY, + private static final String FIELDS = commaSeparated(ID, REALM, DATE_TIME, USERNAME, SEVERITY, STATE, LOCATOR, BUNDLE, KEY, MESSAGE, STACK_TRACE); private static final String queryByRealmMaxSql = @@ -39,7 +40,7 @@ public class PostgreSqlLogMessageDao implements LogMessageDao { private static final String queryValuesSql = "select key, value from operations_log_values where id = ?"; private static final String insertLogMessageSql = "insert into operations_log (" + FIELDS - + ") values (?, ?, ?, ?, ?::log_severity_type, ?::log_state_type, ?, ?, ?, ?)"; + + ") values (?, ?, ?, ?, ?::log_severity_type, ?::log_state_type, ?, ?, ?, ?, ?)"; private static final String insertValuesSql = "insert into operations_log_values (id, key, value) values (?, ?, ?)"; private static final String updateLogMessageStateSql = "update operations_log set state = ?::log_state_type where id = ?"; @@ -281,9 +282,10 @@ public class PostgreSqlLogMessageDao implements LogMessageDao { // 5 severity = ?, // 6 state = ? // 7 locator = ?, - // 8 key = ?, - // 9 message = ?, - // 10 stacktrace = ?, + // 8 bundle = ?, + // 9 key = ?, + // 10 message = ?, + // 11 stacktrace = ?, ps.setString(1, logMessage.getId()); ps.setString(2, logMessage.getRealm()); @@ -293,9 +295,10 @@ public class PostgreSqlLogMessageDao implements LogMessageDao { ps.setString(5, logMessage.getSeverity().name()); ps.setString(6, logMessage.getState().name()); ps.setString(7, logMessage.getLocator().toString()); - ps.setString(8, logMessage.getKey()); - ps.setString(9, logMessage.getMessage()); - ps.setString(10, logMessage.getStackTrace()); + ps.setString(8, logMessage.getBundle()); + ps.setString(9, logMessage.getKey()); + ps.setString(10, logMessage.getMessage()); + ps.setString(11, logMessage.getStackTrace()); } private LogMessage logMessageFrom(ResultSet resultSet, ResultSet valuesResult) throws SQLException { @@ -307,9 +310,10 @@ public class PostgreSqlLogMessageDao implements LogMessageDao { LogSeverity severity = LogSeverity.valueOf(resultSet.getString(5)); LogMessageState state = LogMessageState.valueOf(resultSet.getString(6)); Locator locator = Locator.valueOf(resultSet.getString(7)); - String key = resultSet.getString(8); - String message = resultSet.getString(9); - String exception = resultSet.getString(10); + String bundle = resultSet.getString(8); + String key = resultSet.getString(9); + String message = resultSet.getString(10); + String exception = resultSet.getString(11); Properties properties = new Properties(); while (valuesResult.next()) { @@ -318,7 +322,7 @@ public class PostgreSqlLogMessageDao implements LogMessageDao { properties.setProperty(valueK, valueV); } - return new LogMessage(id, dateTime, realm, username, locator, severity, state, key, properties, message, + return new LogMessage(id, dateTime, realm, username, locator, severity, state, bundle, key, properties, message, exception); } } diff --git a/li.strolch.persistence.postgresql/src/main/resources/strolch_db_schema_0.9.1_drop.sql b/li.strolch.persistence.postgresql/src/main/resources/strolch_db_schema_0.9.1_drop.sql new file mode 100644 index 000000000..a255fc971 --- /dev/null +++ b/li.strolch.persistence.postgresql/src/main/resources/strolch_db_schema_0.9.1_drop.sql @@ -0,0 +1,16 @@ + +DROP TABLE IF EXISTS resources; +DROP TABLE IF EXISTS orders; +DROP TABLE IF EXISTS activities; + +DROP TABLE IF EXISTS audits; + +DROP TABLE IF EXISTS operations_log; +DROP TABLE IF EXISTS operations_log_values; + +DROP TABLE IF EXISTS db_version; + +DROP TYPE IF EXISTS order_state; +DROP TYPE IF EXISTS access_type; +DROP TYPE IF EXISTS log_severity_type; +DROP TYPE IF EXISTS log_state_type; diff --git a/li.strolch.persistence.postgresql/src/main/resources/strolch_db_schema_0.9.1_initial.sql b/li.strolch.persistence.postgresql/src/main/resources/strolch_db_schema_0.9.1_initial.sql new file mode 100644 index 000000000..c6321df55 --- /dev/null +++ b/li.strolch.persistence.postgresql/src/main/resources/strolch_db_schema_0.9.1_initial.sql @@ -0,0 +1,217 @@ + +-- DB_VERSION +CREATE TABLE IF NOT EXISTS db_version ( + id serial primary key not null, + app varchar(255) not null, + version varchar(255) not null, + description varchar(255) not null, + created timestamp with time zone not null +); + +-- RESOURCES +CREATE TABLE IF NOT EXISTS resources ( + id varchar(255) not null, + version integer not null, + created_by varchar(255) not null, + created_at timestamp with time zone not null, + updated_at timestamp with time zone not null, + deleted boolean not null, + latest boolean not null, + name varchar(255) not null, + type varchar(255) not null, + asxml xml, + asjson json, + + PRIMARY KEY (id, version) +); + +-- ORDERS +CREATE TYPE order_state AS ENUM ('CREATED', 'PLANNING', 'PLANNED', 'EXECUTION', 'STOPPED', 'WARNING', 'ERROR', 'EXECUTED', 'CLOSED'); + + +CREATE TABLE IF NOT EXISTS orders ( + id varchar(255) not null, + version integer not null, + created_by varchar(255) not null, + created_at timestamp with time zone not null, + updated_at timestamp with time zone not null, + deleted boolean, + latest boolean not null, + name varchar(255), + type varchar(255), + state order_state, + date timestamp with time zone, + asxml xml, + asjson json, + + PRIMARY KEY (id, version) +); + +-- ACTIVITIES +CREATE TABLE IF NOT EXISTS activities ( + id varchar(255) not null, + version integer not null, + created_by varchar(255) not null, + created_at timestamp with time zone not null, + updated_at timestamp with time zone not null, + deleted boolean not null, + latest boolean not null, + name varchar(255) not null, + type varchar(255) not null, + state order_state, + asxml xml, + asjson json, + + PRIMARY KEY (id, version) +); + +-- AUDITS +CREATE TYPE access_type AS ENUM ('READ', 'CREATE', 'UPDATE', 'DELETE'); +CREATE TABLE IF NOT EXISTS audits ( + id bigint PRIMARY KEY, + username varchar(255) NOT NULL, + firstname varchar(255) NOT NULL, + lastname varchar(255) NOT NULL, + date timestamp with time zone NOT NULL, + + element_type varchar(255) NOT NULL, + element_sub_type varchar(255) NOT NULL, + element_accessed varchar(255) NOT NULL, + new_version timestamp with time zone, + + action varchar(255) NOT NULL, + access_type access_type NOT NULL +); + +-- Operations Log +CREATE TYPE log_severity_type AS ENUM ('Info', 'Notification', 'Warning', 'Error', 'Exception'); +CREATE TYPE log_state_type AS ENUM ('Active', 'Inactive', 'Information'); + +CREATE TABLE IF NOT EXISTS operations_log ( + id varchar(255) PRIMARY KEY, + realm varchar(255), + dateTime timestamp with time zone, + username varchar(255), + severity log_severity_type, + state log_state_type, + locator varchar(1024), + bundle varchar(255), + key varchar(255), + message text, + stacktrace text +); + +CREATE TABLE IF NOT EXISTS operations_log_values ( + id varchar(255), + key varchar(255), + value text +); + +-- set version +INSERT INTO db_version + (version, app, description, created) +values( + '0.1.0', + 'strolch', + 'Initial schema version', + CURRENT_TIMESTAMP +); + +INSERT INTO db_version + (version, app, description, created) +values( + '0.2.0', + 'strolch', + 'Added new table for audits', + CURRENT_TIMESTAMP +); + +INSERT INTO db_version + (version, app, description, created) +values( + '0.2.1', + 'strolch', + 'Added new column app to table table version', + CURRENT_TIMESTAMP +); + +INSERT INTO db_version + (version, app, description, created) +values( + '0.3.0', + 'strolch', + 'Added new column element_sub_type to table audits', + CURRENT_TIMESTAMP +); + +INSERT INTO db_version + (version, app, description, created) +values( + '0.4.0', + 'strolch', + 'Added new table activities', + CURRENT_TIMESTAMP +); + +INSERT INTO db_version + (version, app, description, created) +values( + '0.5.0', + 'strolch', + 'Added versioning to root elements', + CURRENT_TIMESTAMP +); + +INSERT INTO db_version + (version, app, description, created) +values( + '0.5.1', + 'strolch', + 'Added state column to activity, and added new states', + CURRENT_TIMESTAMP +); + +INSERT INTO db_version + (version, app, description, created) +values( + '0.6.0', + 'strolch', + 'Added json column to all tables', + CURRENT_TIMESTAMP +); + +INSERT INTO db_version + (version, app, description, created) +values( + '0.7.0', + 'strolch', + 'Added persisting of operations log', + CURRENT_TIMESTAMP +); + +INSERT INTO db_version + (version, app, description, created) +values( + '0.8.0', + 'strolch', + 'Added updated_at column to all tables', + CURRENT_TIMESTAMP +); + +INSERT INTO db_version + (version, app, description, created) +values( + '0.9.0', + 'strolch', + 'Added log_state column to operations_log', + CURRENT_TIMESTAMP +); + +INSERT INTO db_version + (version, app, description, created) +values( + '0.9.1', + 'strolch', + 'Added bundle column to operations_log', + CURRENT_TIMESTAMP +); diff --git a/li.strolch.persistence.postgresql/src/main/resources/strolch_db_schema_0.9.1_migration.sql b/li.strolch.persistence.postgresql/src/main/resources/strolch_db_schema_0.9.1_migration.sql new file mode 100644 index 000000000..8c587b833 --- /dev/null +++ b/li.strolch.persistence.postgresql/src/main/resources/strolch_db_schema_0.9.1_migration.sql @@ -0,0 +1,18 @@ + +-- add bundle column +ALTER TABLE operations_log ADD COLUMN bundle varchar(255); + +-- set initial values for new columns +UPDATE operations_log SET bundle = '' where bundle IS NULL; + +-- make columns not null +ALTER TABLE operations_log ALTER COLUMN bundle SET NOT NULL; + +INSERT INTO db_version + (version, app, description, created) +values( + '0.9.1', + 'strolch', + 'Added bundle column to operations_log', + CURRENT_TIMESTAMP +); diff --git a/li.strolch.persistence.postgresql/src/main/resources/strolch_db_version.properties b/li.strolch.persistence.postgresql/src/main/resources/strolch_db_version.properties index 5aab0863d..4dca11b69 100644 --- a/li.strolch.persistence.postgresql/src/main/resources/strolch_db_version.properties +++ b/li.strolch.persistence.postgresql/src/main/resources/strolch_db_version.properties @@ -1,2 +1,2 @@ # Property file defining what the currently expected version is supposed to be -db_version=0.9.0 +db_version=0.9.1 diff --git a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/LogMessageSaxReader.java b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/LogMessageSaxReader.java index 40e65e7f6..5bf531706 100644 --- a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/LogMessageSaxReader.java +++ b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/LogMessageSaxReader.java @@ -44,6 +44,7 @@ public class LogMessageSaxReader extends DefaultHandler { private Locator locator; private LogSeverity severity; private LogMessageState state; + private String bundle; private String key; private Properties properties; private String message; @@ -103,7 +104,7 @@ public class LogMessageSaxReader extends DefaultHandler { this.state = LogMessageState.Information; LogMessage logMessage = new LogMessage(this.id, this.dateTime, this.realm, this.username, this.locator, - this.severity, this.state, this.key, this.properties, this.message, this.exception); + this.severity, this.state, this.bundle, this.key, this.properties, this.message, this.exception); this.logMessageConsumer.accept(logMessage); break; @@ -127,6 +128,11 @@ public class LogMessageSaxReader extends DefaultHandler { this.sb = null; break; + case Tags.BUNDLE: + this.bundle = this.sb.toString(); + this.sb = null; + break; + case Tags.KEY: this.key = this.sb.toString(); this.sb = null; diff --git a/li.strolch.utils/src/main/java/li/strolch/utils/I18nMessage.java b/li.strolch.utils/src/main/java/li/strolch/utils/I18nMessage.java index 0c6834114..22968615b 100644 --- a/li.strolch.utils/src/main/java/li/strolch/utils/I18nMessage.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/I18nMessage.java @@ -2,11 +2,17 @@ package li.strolch.utils; import static li.strolch.utils.helper.StringHelper.EMPTY; -import java.util.Locale; -import java.util.MissingResourceException; -import java.util.Properties; -import java.util.ResourceBundle; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.CodeSource; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import li.strolch.utils.collections.MapOfMaps; +import li.strolch.utils.collections.TypedTuple; import li.strolch.utils.dbc.DBC; import li.strolch.utils.helper.StringHelper; import org.slf4j.Logger; @@ -15,40 +21,50 @@ import org.slf4j.LoggerFactory; public class I18nMessage { private static final Logger logger = LoggerFactory.getLogger(I18nMessage.class); + private static MapOfMaps bundleMap; + private final String bundleName; private final String key; private final Properties values; private final ResourceBundle bundle; private String message; public I18nMessage(ResourceBundle bundle, String key) { - DBC.INTERIM.assertNotNull("bundle must be set!", bundle); + DBC.INTERIM.assertNotNull("bundle may not be null!", bundle); DBC.INTERIM.assertNotEmpty("key must be set!", key); this.key = key; this.values = new Properties(); this.bundle = bundle; + this.bundleName = bundle.getBaseBundleName(); } - public I18nMessage(String key, Properties values, String message) { + public I18nMessage(String bundle, String key, Properties values, String message) { + DBC.INTERIM.assertNotNull("bundle must not be empty!", bundle); DBC.INTERIM.assertNotEmpty("key must be set!", key); DBC.INTERIM.assertNotEmpty("message must be set!", message); this.key = key; this.values = values == null ? new Properties() : values; this.message = message; - this.bundle = null; + this.bundle = findBundle(bundle); + this.bundleName = this.bundle == null ? bundle : this.bundle.getBaseBundleName(); } - public I18nMessage(I18nMessage i18nMessage) { - this.key = i18nMessage.key; - this.values = i18nMessage.values; - this.bundle = i18nMessage.bundle; - this.message = i18nMessage.message; + public I18nMessage(I18nMessage other) { + this.key = other.key; + this.values = other.values; + this.bundle = other.bundle; + this.bundleName = other.bundleName; + this.message = other.message; } public String getKey() { return this.key; } + public String getBundle() { + return this.bundle.getBaseBundleName(); + } + public Properties getValues() { return this.values; } @@ -58,6 +74,8 @@ public class I18nMessage { } private ResourceBundle getBundle(Locale locale) { + if (this.bundle == null) + return null; if (this.bundle.getLocale() == locale) return this.bundle; String baseName = this.bundle.getBaseBundleName(); @@ -77,11 +95,22 @@ public class I18nMessage { } public String getMessage(ResourceBundle bundle) { + DBC.INTERIM.assertNotNull("bundle may not be null!", bundle); return formatMessage(bundle); } public String getMessage(Locale locale) { - return formatMessage(getBundle(locale)); + ResourceBundle bundle = getBundle(locale); + if (bundle == null) { + logger.warn("No bundle found for " + this.bundleName + " " + locale); + logger.info("Available: "); + bundleMap.forEach((s, map) -> { + logger.info(" " + s); + map.forEach((l, resourceBundle) -> logger.info(" " + l + ": " + map.keySet())); + }); + return getMessage(); + } + return formatMessage(bundle); } public String getMessage() { @@ -153,4 +182,210 @@ public class I18nMessage { return false; return true; } + + private ResourceBundle findBundle(String baseName) { + if (baseName.isEmpty()) + return null; + + if (bundleMap == null) { + synchronized (I18nMessage.class) { + bundleMap = findAllBundles(); + } + } + + Map bundlesByLocale = bundleMap.getMap(baseName); + if (bundlesByLocale == null || bundlesByLocale.isEmpty()) + return null; + + ResourceBundle bundle = bundlesByLocale.get(Locale.getDefault()); + if (bundle != null) + return bundle; + + return bundlesByLocale.values().iterator().next(); + } + + private static MapOfMaps findAllBundles() { + try { + CodeSource src = I18nMessage.class.getProtectionDomain().getCodeSource(); + if (src == null) { + logger.error( + "Failed to find CodeSource for ProtectionDomain " + I18nMessage.class.getProtectionDomain()); + return new MapOfMaps<>(); + } + + File jarLocationF = new File(src.getLocation().toURI()); + if (!(jarLocationF.exists() && jarLocationF.getParentFile().isDirectory())) { + logger.info("Found JAR repository at " + jarLocationF.getParentFile()); + return new MapOfMaps<>(); + } + + MapOfMaps bundleMap = new MapOfMaps<>(); + + File jarD = jarLocationF.getParentFile(); + File[] jarFiles = jarD.listFiles((dir, name) -> name.endsWith(".jar")); + if (jarFiles == null) + return new MapOfMaps<>(); + + for (File file : jarFiles) { + + if (shouldIgnoreFile(file)) + continue; + + logger.info("Scanning JAR " + file.getName() + " for property files..."); + + try (JarFile jarFile = new JarFile(file)) { + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry je = entries.nextElement(); + + String entryName = je.getName(); + + if (entryName.startsWith("META-INF") // + || entryName.equals("componentVersion.properties") // + || entryName.equals("strolch_db_version.properties")) + continue; + + if (!entryName.endsWith(".properties")) + continue; + + String propertyName = entryName.replace('/', '.'); + + logger.info(" Found property file " + propertyName + " in JAR " + file.getName()); + + TypedTuple tuple = parsePropertyName(entryName); + if (tuple == null) + continue; + String baseName = tuple.getFirst(); + Locale locale = tuple.getSecond(); + + ResourceBundle bundle = ResourceBundle + .getBundle(baseName, locale, new CustomControl(jarFile.getInputStream(je))); + + bundleMap.addElement(bundle.getBaseBundleName(), bundle.getLocale(), bundle); + logger.info(" Loaded bundle " + bundle.getBaseBundleName() + " " + bundle.getLocale()); + } + } + } + + File classesD = new File(jarD.getParentFile(), "classes"); + if (classesD.isDirectory()) { + File[] propertyFiles = classesD.listFiles( + (dir, name) -> name.endsWith(".properties") && !(name.equals("appVersion.properties") || name + .equals("ENV.properties"))); + if (propertyFiles != null && propertyFiles.length > 0) { + for (File propertyFile : propertyFiles) { + + logger.info(" Found property file " + propertyFile.getName() + " in classes " + classesD + .getAbsolutePath()); + + TypedTuple tuple = parsePropertyName(propertyFile.getName()); + if (tuple == null) + continue; + String baseName = tuple.getFirst(); + Locale locale = tuple.getSecond(); + + ResourceBundle bundle; + try (FileInputStream in = new FileInputStream(propertyFile)) { + bundle = ResourceBundle.getBundle(baseName, locale, new CustomControl(in)); + } + + bundleMap.addElement(bundle.getBaseBundleName(), bundle.getLocale(), bundle); + logger.info(" Loaded bundle " + bundle.getBaseBundleName() + " " + bundle.getLocale()); + } + } + } + + logger.info("Done."); + return bundleMap; + + } catch (Exception e) { + logger.error("Failed to find all property files!", e); + return new MapOfMaps<>(); + } + } + + private static TypedTuple parsePropertyName(String entryName) { + String propertyName = entryName.replace('/', '.'); + + String bundleName = propertyName.substring(0, propertyName.lastIndexOf(".")); + String baseName; + Locale locale; + int i = bundleName.indexOf('_'); + if (i > 0) { + baseName = bundleName.substring(0, i); + String localeS = bundleName.substring(i + 1); + String[] parts = localeS.split("_"); + if (parts.length == 2) { + String language = parts[0]; + String country = parts[1]; + int languageI = Arrays.binarySearch(Locale.getISOLanguages(), language); + int countryI = Arrays.binarySearch(Locale.getISOCountries(), country); + if (languageI >= 0 && countryI >= 0) + locale = new Locale(language, country); + else { + logger.warn("Ignoring bad bundle locale for " + entryName); + return null; + } + } else { + int languageI = Arrays.binarySearch(Locale.getISOLanguages(), localeS); + if (languageI >= 0) + locale = new Locale(localeS); + else { + logger.warn("Ignoring bad bundle locale for " + entryName); + return null; + } + } + } else { + baseName = bundleName; + locale = Locale.getDefault(); + } + + return new TypedTuple<>(baseName, locale); + } + + private static class CustomControl extends ResourceBundle.Control { + private final InputStream stream; + + public CustomControl(InputStream stream) { + this.stream = stream; + } + + @Override + public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, + boolean reload) throws IOException { + return new PropertyResourceBundle(this.stream); + } + } + + private static boolean shouldIgnoreFile(File file) { + return file.getName().contains("jaxb-core") // + || file.getName().contains("jaxrs-ri") // + || file.getName().contains("jaxb-impl") // + || file.getName().contains("jaxb-api") // + || file.getName().contains("jakarta") // + || file.getName().contains("cron") // + || file.getName().contains("sax") // + || file.getName().contains("aopalliance") // + || file.getName().contains("antlr") // + || file.getName().contains("ST4") // + || file.getName().contains("osgi") // + || file.getName().contains("gson") // + || file.getName().contains("logback") // + || file.getName().contains("slf4j") // + || file.getName().contains("postgresql") // + || file.getName().contains("commons-csv") // + || file.getName().contains("camel") // + || file.getName().contains("yasson") // + || file.getName().contains("tyrus") // + || file.getName().contains("jersey") // + || file.getName().contains("icu4j") // + || file.getName().contains("activation") // + || file.getName().contains("validation-api") // + || file.getName().contains("HikariCP") // + || file.getName().contains("javassist") // + || file.getName().contains("org.abego.treelayout") // + || file.getName().contains("hk2") // + || file.getName().contains("javax") // + || file.getName().contains("grizzly"); + } }