package sample.context.audit;
import java.util.Optional;
import java.util.function.Supplier;
import org.slf4j.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.*;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import sample.*;
import sample.context.actor.*;
import sample.context.audit.AuditActor.RegAuditActor;
import sample.context.audit.AuditEvent.RegAuditEvent;
import sample.context.orm.SystemRepository;
/**
* 利用者監査やシステム監査(定時バッチや日次バッチ等)などを取り扱います。
* <p>暗黙的な適用を望む場合は、AOPとの連携も検討してください。
* <p>対象となるログはLoggerだけでなく、システムスキーマの監査テーブルへ書きだされます。
* (開始時と完了時で別TXにする事で応答無し状態を検知可能)
*/
@Slf4j
@Setter
public class AuditHandler {
public static final Logger LoggerActor = LoggerFactory.getLogger("Audit.Actor");
public static final Logger LoggerEvent = LoggerFactory.getLogger("Audit.Event");
@Autowired
private ActorSession session;
@Autowired
private AuditPersister persister;
/** 与えた処理に対し、監査ログを記録します。 */
public <T> T audit(String message, final Supplier<T> callable) {
return audit("default", message, callable);
}
/** 与えた処理に対し、監査ログを記録します。 */
public void audit(String message, final Runnable command) {
audit(message, () -> {
command.run();
return true;
});
}
/** 与えた処理に対し、監査ログを記録します。 */
public <T> T audit(String category, String message, final Supplier<T> callable) {
logger().trace(message(message, "[開始]", null));
long start = System.currentTimeMillis();
try {
T v = session.actor().getRoleType().isSystem() ? callEvent(category, message, callable)
: callAudit(category, message, callable);
logger().info(message(message, "[完了]", start));
return v;
} catch (ValidationException e) {
logger().warn(message(message, "[審例]", start));
throw e;
} catch (RuntimeException e) {
logger().error(message(message, "[例外]", start));
throw (RuntimeException) e;
} catch (Exception e) {
logger().error(message(message, "[例外]", start));
throw new InvocationException("error.Exception", e);
}
}
/** 与えた処理に対し、監査ログを記録します。 */
public void audit(String category, String message, final Runnable command) {
audit(category, message, () -> {
command.run();
return true;
});
}
private Logger logger() {
return session.actor().getRoleType().isSystem() ? LoggerEvent : LoggerActor;
}
private String message(String message, String prefix, Long startMillis) {
Actor actor = session.actor();
StringBuilder sb = new StringBuilder(prefix + " ");
if (actor.getRoleType().notSystem()) {
sb.append("[" + actor.getId() + "] ");
}
sb.append(message);
if (startMillis != null) {
sb.append(" [" + (System.currentTimeMillis() - startMillis) + "ms]");
}
return sb.toString();
}
public <T> T callAudit(String category, String message, final Supplier<T> callable) {
Optional<AuditActor> audit = Optional.empty();
try {
try { // システムスキーマの障害は本質的なエラーに影響を与えないように
audit = Optional.of(persister.start(RegAuditActor.of(category, message)));
} catch (Exception e) {
log.error(e.getMessage(), e);
}
T v = callable.get();
try {
audit.ifPresent(persister::finish);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return v;
} catch (ValidationException e) {
try {
audit.ifPresent((v) -> persister.cancel(v, e.getMessage()));
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
}
throw e;
} catch (RuntimeException e) {
try {
audit.ifPresent((v) -> persister.error(v, e.getMessage()));
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
}
throw e;
} catch (Exception e) {
try {
audit.ifPresent((v) -> persister.error(v, e.getMessage()));
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
}
throw new InvocationException(e);
}
}
public <T> T callEvent(String category, String message, final Supplier<T> callable) {
Optional<AuditEvent> audit = Optional.empty();
try {
try { // システムスキーマの障害は本質的なエラーに影響を与えないように
audit = Optional.of(persister.start(RegAuditEvent.of(category, message)));
} catch (Exception e) {
log.error(e.getMessage(), e);
}
T v = callable.get();
try {
audit.ifPresent(persister::finish);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return v;
} catch (ValidationException e) {
try {
audit.ifPresent((v) -> persister.cancel(v, e.getMessage()));
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
}
throw e;
} catch (RuntimeException e) {
try {
audit.ifPresent((v) -> persister.error(v, e.getMessage()));
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
}
throw (RuntimeException) e;
} catch (Exception e) {
try {
audit.ifPresent((v) -> persister.error(v, e.getMessage()));
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
}
throw new InvocationException(e);
}
}
/**
* 監査ログをシステムスキーマへ永続化します。
*/
@Setter
public static class AuditPersister {
@Autowired
private SystemRepository rep;
@Transactional(value = SystemRepository.BeanNameTx, propagation = Propagation.REQUIRES_NEW)
public AuditActor start(RegAuditActor p) {
return AuditActor.register(rep, p);
}
@Transactional(value = SystemRepository.BeanNameTx, propagation = Propagation.REQUIRES_NEW)
public AuditActor finish(AuditActor audit) {
return audit.finish(rep);
}
@Transactional(value = SystemRepository.BeanNameTx, propagation = Propagation.REQUIRES_NEW)
public AuditActor cancel(AuditActor audit, String errorReason) {
return audit.cancel(rep, errorReason);
}
@Transactional(value = SystemRepository.BeanNameTx, propagation = Propagation.REQUIRES_NEW)
public AuditActor error(AuditActor audit, String errorReason) {
return audit.error(rep, errorReason);
}
@Transactional(value = SystemRepository.BeanNameTx, propagation = Propagation.REQUIRES_NEW)
public AuditEvent start(RegAuditEvent p) {
return AuditEvent.register(rep, p);
}
@Transactional(value = SystemRepository.BeanNameTx, propagation = Propagation.REQUIRES_NEW)
public AuditEvent finish(AuditEvent event) {
return event.finish(rep);
}
@Transactional(value = SystemRepository.BeanNameTx, propagation = Propagation.REQUIRES_NEW)
public AuditEvent cancel(AuditEvent event, String errorReason) {
return event.cancel(rep, errorReason);
}
@Transactional(value = SystemRepository.BeanNameTx, propagation = Propagation.REQUIRES_NEW)
public AuditEvent error(AuditEvent event, String errorReason) {
return event.error(rep, errorReason);
}
}
}