[New] Added bundle persisting to LogMessage and retrieval scheme

This commit is contained in:
Robert von Burg 2020-08-03 16:27:05 +02:00
parent 80841770e2
commit 37ac403923
10 changed files with 532 additions and 32 deletions

View File

@ -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";

View File

@ -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);
}
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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
);

View File

@ -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
);

View File

@ -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

View File

@ -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;

View File

@ -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<String, Locale, ResourceBundle> 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<Locale, ResourceBundle> 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<String, Locale, ResourceBundle> 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<String, Locale, ResourceBundle> 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<JarEntry> 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<String, Locale> 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<String, Locale> 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<String, Locale> 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");
}
}