What you are trying to accomplish won't work with the JPA lifecycle callbacks. This is because the point that those callbacks are invoked, the Envers integration has not yet happened, so the audit framework hasn't generated anything.
One solution uses often use to capture application state as a part of the audit operation is to use a custom revision entity implementation. You add this by using a special annotated entity:
Code:
@Entity
@RevisionEntity
public class CustomRevisionEntity extends DefaultRevisionEntity {
@PostPersist
public void logAuditData() {
final AuditUserData auditUserData = AuditUserDataHolder.getAuditUserData();
auditUserData.setRevisionNumber( getId() );
AuditLogger.log( auditUserData );
}
}
For your @RevisionEntity implementation, you can either extend any implementation from the Envers library as I have done here or you can write your own implementation as needed. The documentation covers this in detail should you want to take that route.
But the point here is that this entity acts as a placeholder for event handling so we can capture precisely **when** to write to the audit log. But before we get to that, I want to point out my use of AuditUserDataHolder#getAuditUserData().
Each entity in a transaction has various different lifecycle callbacks fired. For example, in one audit transaction, it might consist of inserting one entity, updating another, and removing a third. We're dealing with precisely 3 different lifecycle events for three different entities and you want that information captured at the time the audit operation happens.
I propose that your @PostPersist / @PostUpdate / @PostRemove JPA callbacks on your real entities merely access a thread local variable that the AuditUserDataHolder#getAuditUserData() manages. During the real entity callbacks, you populate information here and collect it across all your audited entities. Remember you'll have to account for multiple entities here, so perhaps something like this:
Code:
@PostPersist
public void cachePostPersistOp() {
final AuditUserData auditUserData = AuditUserDataHolder.getAuditUserData();
auditUserData.addAction( "CREATE", this.getClass().getName(), this.getId() );
}
This does mean your AuditLogger#log method will have to account for the fact its being called once per transaction for multiple entity operations rather than for one operation, but that should be trivial.
The only tricky part here is the fact you need to make sure the ThreadLocal is cleared. One way to insure this happens is to register a AfterTransactionCompletionProcess implementation that clears your thread local variable with the Hibernate session:
Code:
session.getActionQueue().registerProcess( new AfterTransactionCompletionProcess() {
@Override
public void doAfterTransactionCompletion(boolean success, SessionImplementor session) {
// clears the thread local regardless if commit succeeded (success=true) or rolled back (success=false)
AuditUserDataHolder#clear();
}
} );
But what you're trying to ultimately log is accessible through Envers' AuditReader Query API, its just an after-the-fact operation. You'd use the custom revision entity to stash the username and application name. That revision entity already stores the revision number and timestamp. The query API allows you to find all entities modified within a revision and fetch back:
1. The revision entity to get the details for who/when did the operation
2. The revision type, e.g. was it an insert, update, or delete
3. The populated detached entity instance that represents the entity state at that revision