|
@@ -22,9 +22,11 @@
|
|
|
|
|
|
package password.pwm.svc.event;
|
|
package password.pwm.svc.event;
|
|
|
|
|
|
|
|
+import password.pwm.AppProperty;
|
|
import password.pwm.PwmApplication;
|
|
import password.pwm.PwmApplication;
|
|
-import password.pwm.util.JsonUtil;
|
|
|
|
-import password.pwm.util.TimeDuration;
|
|
|
|
|
|
+import password.pwm.error.PwmException;
|
|
|
|
+import password.pwm.svc.PwmService;
|
|
|
|
+import password.pwm.util.*;
|
|
import password.pwm.util.localdb.LocalDB;
|
|
import password.pwm.util.localdb.LocalDB;
|
|
import password.pwm.util.localdb.LocalDBException;
|
|
import password.pwm.util.localdb.LocalDBException;
|
|
import password.pwm.util.localdb.LocalDBStoredQueue;
|
|
import password.pwm.util.localdb.LocalDBStoredQueue;
|
|
@@ -33,27 +35,59 @@ import password.pwm.util.logging.PwmLogger;
|
|
import java.util.Date;
|
|
import java.util.Date;
|
|
import java.util.Iterator;
|
|
import java.util.Iterator;
|
|
import java.util.Map;
|
|
import java.util.Map;
|
|
|
|
+import java.util.concurrent.Executors;
|
|
|
|
+import java.util.concurrent.ScheduledExecutorService;
|
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
public class LocalDbAuditVault implements AuditVault {
|
|
public class LocalDbAuditVault implements AuditVault {
|
|
private static final PwmLogger LOGGER = PwmLogger.forClass(LocalDbAuditVault.class);
|
|
private static final PwmLogger LOGGER = PwmLogger.forClass(LocalDbAuditVault.class);
|
|
|
|
|
|
- private static final int MAX_REMOVALS_PER_ADD = 100;
|
|
|
|
-
|
|
|
|
private LocalDBStoredQueue auditDB;
|
|
private LocalDBStoredQueue auditDB;
|
|
private Settings settings;
|
|
private Settings settings;
|
|
private Date oldestRecord;
|
|
private Date oldestRecord;
|
|
|
|
|
|
|
|
+ private int maxBulkRemovals = 105;
|
|
|
|
+
|
|
|
|
+ private ScheduledExecutorService executorService;
|
|
|
|
+ private volatile PwmService.STATUS status = PwmService.STATUS.NEW;
|
|
|
|
+
|
|
|
|
+
|
|
public LocalDbAuditVault(
|
|
public LocalDbAuditVault(
|
|
- final PwmApplication pwmApplication,
|
|
|
|
- final LocalDB localDB
|
|
|
|
)
|
|
)
|
|
throws LocalDBException
|
|
throws LocalDBException
|
|
{
|
|
{
|
|
- this.auditDB = LocalDBStoredQueue.createLocalDBStoredQueue(pwmApplication, localDB, LocalDB.DB.AUDIT_EVENTS);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- public void init(final Settings settings) {
|
|
|
|
|
|
+ public void init(
|
|
|
|
+ final PwmApplication pwmApplication,
|
|
|
|
+ final LocalDB localDB,
|
|
|
|
+ final Settings settings
|
|
|
|
+ )
|
|
|
|
+ throws PwmException
|
|
|
|
+ {
|
|
this.settings = settings;
|
|
this.settings = settings;
|
|
|
|
+ this.auditDB = LocalDBStoredQueue.createLocalDBStoredQueue(pwmApplication, localDB, LocalDB.DB.AUDIT_EVENTS);
|
|
|
|
+ this.maxBulkRemovals = Integer.parseInt(pwmApplication.getConfig().readAppProperty(AppProperty.AUDIT_EVENTS_LOCALDB_MAX_BULK_REMOVALS));
|
|
|
|
+
|
|
|
|
+ readOldestRecord();
|
|
|
|
+
|
|
|
|
+ executorService = Executors.newSingleThreadScheduledExecutor(
|
|
|
|
+ Helper.makePwmThreadFactory(
|
|
|
|
+ Helper.makeThreadName(pwmApplication,this.getClass()) + "-",
|
|
|
|
+ true
|
|
|
|
+ ));
|
|
|
|
+
|
|
|
|
+ status = PwmService.STATUS.OPEN;
|
|
|
|
+ executorService.scheduleWithFixedDelay(new TrimmerThread(), 0, 10, TimeUnit.MINUTES);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public void close() {
|
|
|
|
+ executorService.shutdown();
|
|
|
|
+ status = PwmService.STATUS.CLOSED;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public PwmService.STATUS getStatus() {
|
|
|
|
+ return status;
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
@@ -94,6 +128,15 @@ public class LocalDbAuditVault implements AuditVault {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ @Override
|
|
|
|
+ public String sizeToDebugString() {
|
|
|
|
+ final long storedEvents = this.size();
|
|
|
|
+ final long maxEvents = settings.getMaxRecordCount();
|
|
|
|
+ final Percent percent = new Percent(storedEvents, maxEvents);
|
|
|
|
+
|
|
|
|
+ return storedEvents + " / " + maxEvents + " (" + percent.pretty(2) + ")";
|
|
|
|
+ }
|
|
|
|
+
|
|
private static AuditRecord deSerializeRecord(final String input) {
|
|
private static AuditRecord deSerializeRecord(final String input) {
|
|
final Map<String,String> tempMap = JsonUtil.deserializeStringMap(input);
|
|
final Map<String,String> tempMap = JsonUtil.deserializeStringMap(input);
|
|
String errorMsg = "";
|
|
String errorMsg = "";
|
|
@@ -101,19 +144,17 @@ public class LocalDbAuditVault implements AuditVault {
|
|
if (tempMap != null) {
|
|
if (tempMap != null) {
|
|
final String eventCode = tempMap.get("eventCode");
|
|
final String eventCode = tempMap.get("eventCode");
|
|
if (eventCode != null && eventCode.length() > 0) {
|
|
if (eventCode != null && eventCode.length() > 0) {
|
|
- final AuditEvent event = AuditEvent.valueOf(eventCode);
|
|
|
|
- if (event != null) {
|
|
|
|
- switch (event.getType()) {
|
|
|
|
- case USER:
|
|
|
|
- return JsonUtil.deserialize(input, UserAuditRecord.class);
|
|
|
|
- case SYSTEM:
|
|
|
|
- return JsonUtil.deserialize(input, SystemAuditRecord.class);
|
|
|
|
- case HELPDESK:
|
|
|
|
- return JsonUtil.deserialize(input, HelpdeskAuditRecord.class);
|
|
|
|
- default:
|
|
|
|
- throw new IllegalArgumentException("unknown audit record type: " + event.getType());
|
|
|
|
- }
|
|
|
|
|
|
+ final AuditEvent event;
|
|
|
|
+ try {
|
|
|
|
+ event = AuditEvent.valueOf(eventCode);
|
|
|
|
+ } catch (IllegalArgumentException e) {
|
|
|
|
+ errorMsg = "error de-serializing audit record: " + e.getMessage();
|
|
|
|
+ LOGGER.error(errorMsg);
|
|
|
|
+ return null;
|
|
}
|
|
}
|
|
|
|
+ final Class clazz = event.getType().getDataClass();
|
|
|
|
+ final com.google.gson.reflect.TypeToken typeToken = com.google.gson.reflect.TypeToken.get(clazz);
|
|
|
|
+ return JsonUtil.deserialize(input, typeToken);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
@@ -130,34 +171,74 @@ public class LocalDbAuditVault implements AuditVault {
|
|
|
|
|
|
final String jsonRecord = JsonUtil.serialize(record);
|
|
final String jsonRecord = JsonUtil.serialize(record);
|
|
auditDB.addLast(jsonRecord);
|
|
auditDB.addLast(jsonRecord);
|
|
- trim();
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private void trim() {
|
|
|
|
- if (oldestRecord != null && TimeDuration.fromCurrent(oldestRecord).isLongerThan(settings.getMaxRecordAge())) {
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
|
|
|
|
- if (auditDB.isEmpty()) {
|
|
|
|
- return;
|
|
|
|
|
|
+ if (auditDB.size() > settings.getMaxRecordCount()) {
|
|
|
|
+ removeRecords(1);
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- int workActions = 0;
|
|
|
|
- while (workActions < MAX_REMOVALS_PER_ADD && !auditDB.isEmpty()) {
|
|
|
|
|
|
+ private void readOldestRecord() {
|
|
|
|
+ if (auditDB != null && !auditDB.isEmpty()) {
|
|
final String stringFirstRecord = auditDB.getFirst();
|
|
final String stringFirstRecord = auditDB.getFirst();
|
|
final UserAuditRecord firstRecord = JsonUtil.deserialize(stringFirstRecord, UserAuditRecord.class);
|
|
final UserAuditRecord firstRecord = JsonUtil.deserialize(stringFirstRecord, UserAuditRecord.class);
|
|
oldestRecord = firstRecord.getTimestamp();
|
|
oldestRecord = firstRecord.getTimestamp();
|
|
- if (TimeDuration.fromCurrent(oldestRecord).isLongerThan(settings.getMaxRecordAge())) {
|
|
|
|
- auditDB.removeFirst();
|
|
|
|
- workActions++;
|
|
|
|
- } else {
|
|
|
|
- break;
|
|
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void removeRecords(int count) {
|
|
|
|
+ auditDB.removeFirst(count);
|
|
|
|
+ readOldestRecord();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private class TrimmerThread implements Runnable {
|
|
|
|
+
|
|
|
|
+ // keep transaction duration around 100ms if possible.
|
|
|
|
+ final TransactionSizeCalculator transactionSizeCalculator = new TransactionSizeCalculator(
|
|
|
|
+ new TransactionSizeCalculator.SettingsBuilder()
|
|
|
|
+ .setDurationGoal(new TimeDuration(101, TimeUnit.MILLISECONDS))
|
|
|
|
+ .setMaxTransactions(5003)
|
|
|
|
+ .setMinTransactions(3)
|
|
|
|
+ .createSettings()
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void run() {
|
|
|
|
+ long startTime = System.currentTimeMillis();
|
|
|
|
+ while (trim(transactionSizeCalculator.getTransactionSize())
|
|
|
|
+ && status == PwmService.STATUS.OPEN
|
|
|
|
+ ) {
|
|
|
|
+ final long executeTime = System.currentTimeMillis() - startTime;
|
|
|
|
+ transactionSizeCalculator.recordLastTransactionDuration(executeTime);
|
|
|
|
+ transactionSizeCalculator.pause();
|
|
|
|
+ startTime = System.currentTimeMillis();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- while (auditDB.size() > settings.getMaxRecordCount() && workActions < MAX_REMOVALS_PER_ADD) {
|
|
|
|
- auditDB.removeFirst();
|
|
|
|
- workActions++;
|
|
|
|
|
|
+ private boolean trim(final int maxRemovals) {
|
|
|
|
+ if (auditDB.isEmpty()) {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (auditDB.size() > settings.getMaxRecordCount() + maxRemovals) {
|
|
|
|
+ removeRecords(maxRemovals);
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ int workActions = 0;
|
|
|
|
+ while (oldestRecord != null
|
|
|
|
+ && workActions < maxRemovals
|
|
|
|
+ && !auditDB.isEmpty()
|
|
|
|
+ && status == PwmService.STATUS.OPEN
|
|
|
|
+ ) {
|
|
|
|
+ if (TimeDuration.fromCurrent(oldestRecord).isLongerThan(settings.getMaxRecordAge())) {
|
|
|
|
+ removeRecords(1);
|
|
|
|
+ workActions++;
|
|
|
|
+ } else {
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return workActions > 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|