AuditLogObserver.java
package com.university.bookstore.observer;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Observer that maintains an audit log of all material events.
* Provides a complete history of system activities for compliance and debugging.
*
* @author Navid Mohaghegh
* @version 3.0
* @since 2024-09-15
*/
public class AuditLogObserver implements MaterialObserver {
private static final Logger LOGGER = LoggerFactory.getLogger(AuditLogObserver.class);
private final List<AuditLogEntry> auditLog;
private final int maxLogSize;
/**
* Creates a new audit log observer with unlimited log size.
*/
public AuditLogObserver() {
this(Integer.MAX_VALUE);
}
/**
* Creates a new audit log observer with the specified maximum log size.
*
* @param maxLogSize the maximum number of log entries to keep
*/
public AuditLogObserver(int maxLogSize) {
this.auditLog = new ArrayList<>();
this.maxLogSize = maxLogSize;
}
@Override
public void onEvent(MaterialEvent event) {
if (event == null) {
return;
}
AuditLogEntry entry = new AuditLogEntry(
event.getTimestamp(),
event.getEventType(),
event.getMaterial().getId(),
event.getMaterial().getTitle(),
event.getDescription()
);
auditLog.add(entry);
// Maintain log size limit
if (auditLog.size() > maxLogSize) {
auditLog.remove(0); // Remove oldest entry
}
}
/**
* Gets all audit log entries.
*
* @return list of audit log entries
*/
public List<AuditLogEntry> getAuditLog() {
return new ArrayList<>(auditLog);
}
/**
* Gets audit log entries for a specific material.
*
* @param materialId the material ID to filter by
* @return list of audit log entries for the material
*/
public List<AuditLogEntry> getAuditLogForMaterial(String materialId) {
return auditLog.stream()
.filter(entry -> Objects.equals(entry.getMaterialId(), materialId))
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
}
/**
* Gets audit log entries for a specific event type.
*
* @param eventType the event type to filter by
* @return list of audit log entries for the event type
*/
public List<AuditLogEntry> getAuditLogForEventType(String eventType) {
return auditLog.stream()
.filter(entry -> Objects.equals(entry.getEventType(), eventType))
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
}
/**
* Gets audit log entries within a time range.
*
* @param startTime the start time in milliseconds
* @param endTime the end time in milliseconds
* @return list of audit log entries within the time range
*/
public List<AuditLogEntry> getAuditLogForTimeRange(long startTime, long endTime) {
return auditLog.stream()
.filter(entry -> entry.getTimestamp() >= startTime && entry.getTimestamp() <= endTime)
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
}
/**
* Prints the audit log to the console.
*/
public void printAuditLog() {
auditLog.forEach(entry -> LOGGER.info("Audit: {}", entry.toString()));
}
/**
* Gets the number of audit log entries.
*
* @return the log size
*/
public int getLogSize() {
return auditLog.size();
}
/**
* Clears the audit log.
*/
public void clearAuditLog() {
auditLog.clear();
}
/**
* Gets audit log statistics.
*
* @return audit log statistics
*/
public AuditLogStats getAuditLogStats() {
int totalEntries = auditLog.size();
int materialAddedCount = (int) auditLog.stream()
.filter(entry -> "MATERIAL_ADDED".equals(entry.getEventType()))
.count();
int priceChangedCount = (int) auditLog.stream()
.filter(entry -> "PRICE_CHANGED".equals(entry.getEventType()))
.count();
long oldestTimestamp = auditLog.stream()
.mapToLong(AuditLogEntry::getTimestamp)
.min()
.orElse(0L);
long newestTimestamp = auditLog.stream()
.mapToLong(AuditLogEntry::getTimestamp)
.max()
.orElse(0L);
return new AuditLogStats(
totalEntries,
materialAddedCount,
priceChangedCount,
oldestTimestamp,
newestTimestamp
);
}
@Override
public String getObserverName() {
return "AuditLogObserver";
}
/**
* Represents a single audit log entry.
*/
public static class AuditLogEntry {
private final long timestamp;
private final String eventType;
private final String materialId;
private final String materialTitle;
private final String description;
/**
* Creates an audit log entry.
*
* @param timestamp when the event occurred
* @param eventType type of event
* @param materialId ID of the material
* @param materialTitle title of the material
* @param description event description
*/
public AuditLogEntry(long timestamp, String eventType, String materialId,
String materialTitle, String description) {
this.timestamp = timestamp;
this.eventType = eventType;
this.materialId = materialId;
this.materialTitle = materialTitle;
this.description = description;
}
/** Gets the timestamp of the event. @return timestamp of the event */
public long getTimestamp() { return timestamp; }
/** Gets the type of event. @return type of event */
public String getEventType() { return eventType; }
/** Gets the ID of the material. @return ID of the material */
public String getMaterialId() { return materialId; }
/** Gets the title of the material. @return title of the material */
public String getMaterialTitle() { return materialTitle; }
/** Gets the event description. @return event description */
public String getDescription() { return description; }
@Override
public String toString() {
return String.format("[%s] %s: %s (ID: %s) - %s",
new Date(timestamp),
eventType,
materialTitle,
materialId,
description);
}
}
/**
* Statistics for the audit log.
*/
public static class AuditLogStats {
private final int totalEntries;
private final int materialAddedCount;
private final int priceChangedCount;
private final long oldestTimestamp;
private final long newestTimestamp;
/**
* Creates audit log statistics.
*
* @param totalEntries total number of entries
* @param materialAddedCount number of material added events
* @param priceChangedCount number of price changed events
* @param oldestTimestamp timestamp of oldest entry
* @param newestTimestamp timestamp of newest entry
*/
public AuditLogStats(int totalEntries, int materialAddedCount, int priceChangedCount,
long oldestTimestamp, long newestTimestamp) {
this.totalEntries = totalEntries;
this.materialAddedCount = materialAddedCount;
this.priceChangedCount = priceChangedCount;
this.oldestTimestamp = oldestTimestamp;
this.newestTimestamp = newestTimestamp;
}
/** Gets the total number of entries. @return total number of entries */
public int getTotalEntries() { return totalEntries; }
/** Gets the number of material added events. @return number of material added events */
public int getMaterialAddedCount() { return materialAddedCount; }
/** Gets the number of price changed events. @return number of price changed events */
public int getPriceChangedCount() { return priceChangedCount; }
/** Gets the timestamp of oldest entry. @return timestamp of oldest entry */
public long getOldestTimestamp() { return oldestTimestamp; }
/** Gets the timestamp of newest entry. @return timestamp of newest entry */
public long getNewestTimestamp() { return newestTimestamp; }
@Override
public String toString() {
return String.format("AuditLogStats[Total=%d, Added=%d, PriceChanged=%d, Oldest=%s, Newest=%s]",
totalEntries, materialAddedCount, priceChangedCount,
new Date(oldestTimestamp), new Date(newestTimestamp));
}
}
@Override
public String toString() {
return String.format("AuditLogObserver[LogSize=%d, MaxSize=%d]", getLogSize(), maxLogSize);
}
}