-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 11 posts ] 
Author Message
 Post subject: How to get Rev from Audited entity for logging purpose
PostPosted: Thu Apr 06, 2017 10:21 am 
Newbie

Joined: Thu Apr 06, 2017 10:18 am
Posts: 4
Hello,
I have send this by mail to the mailing list but since there is no sign of life since augustus 2016 I'm posting here
We would like to keep trace of who change something into our database.
We have already activate Envers auditing to keep track of the different version of our record.
However when I do the log I would like to specify the revision of the entity when it was changed. I have added an EventListeners on the Entity that will call the logger after any change on the entity but can't find a way to get the version.
I have tried to use the @Version annotation but this one has different value than the REV column in the audit table.
Here is some part of the code:
Code:
@Entity(name = "Rules")
@EntityListeners(AuditListener.class)
@Audited(withModifiedFlag = true)
public class Rule  {
        @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name = "Id")
   private Integer id;

   @XmlTransient
   public Integer getId() {
      return id;
   }

   public void setId(Integer id) {
      this.id = id;
   }
   private String gufid;
   @NotNull
   @Column(name = "Code", nullable = false, columnDefinition = "NVARCHAR(55)")
   private String code;
   @NotNull
   @Lob
   @Column(name = "Expression", nullable = false, columnDefinition = "NVARCHAR(MAX)")
   private String expression;
   @NotNull
   @Column(name = "RuleType", nullable = false, columnDefinition = "VARCHAR(20)")
   @Enumerated(EnumType.STRING)
   private RuleType ruleType;
   @Column(name = "Description", columnDefinition = "NVARCHAR(255)")
   private String description;

    @Version
    private Long version;
public Long getVersion() {
        return version;
    }

    public void setVersion(Long version) {
        this.version = version;
    }

Code:
public class AuditListener {

   @PostPersist
   private void afterCreate(Rule rule) {
      logData("CREATE", rule);
   }
       private void logData(String action, Rule rule) {
      AuditUserData auditUserData = getAuditUserData();
      AuditData auditData = new AuditData();
      auditData.setUsername(auditUserData.getUsername());
      auditData.setAction(action);
      auditData.setApplication(auditUserData.getApplication());
      auditData.setResource("uuid: " + rule.getUuid() + ", version: "+rule.getVersion());
      auditData.setTimestamp(new Date().getTime());
      AuditLogger.log(auditData);

   }


Best Regards,
Claude


Top
 Profile  
 
 Post subject: Re: How to get Rev from Audited entity for logging purpose
PostPosted: Thu Apr 06, 2017 10:44 am 
Hibernate Team
Hibernate Team

Joined: Wed Jun 15, 2016 9:04 am
Posts: 24
While I look into options for logging the results as you described, is there a particular requirement that dictates logging the information instead of actually storing it with the audit revision entity instead, inside REVINFO?


Top
 Profile  
 
 Post subject: Re: How to get Rev from Audited entity for logging purpose
PostPosted: Thu Apr 06, 2017 12:22 pm 
Hibernate Team
Hibernate Team

Joined: Wed Jun 15, 2016 9:04 am
Posts: 24
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


Top
 Profile  
 
 Post subject: Re: How to get Rev from Audited entity for logging purpose
PostPosted: Thu Apr 06, 2017 2:03 pm 
Newbie

Joined: Thu Apr 06, 2017 10:18 am
Posts: 4
Well the problem is that I don't want to log only at persistence time.
My backend is expose through a Rest service which can be calles either by another backend or by the administration frontend application.
I want to log if someone did a change or read a data in database and also if someone tried to do a change event if it failed before reaching the persistance layer.
One solution could be to do a query to get that revision such as explained here: http://www.baeldung.com/database-auditing-jpa

Code:
AuditReader reader = AuditReaderFactory.get(session);
AuditQuery query = reader.createQuery().forRevisionsOfEntity(Bar.class, true, true);

I was wondering if performance wise it wouldn't be a bit overkilled though ....

About AuditUserData, I already did some Thread variable in the past but this time I have used CDI requestScoped object that use SecurityContext(for Principal) and HttpServletRequest(to get headers).
BTW CDI injection is not working with callback(EntityListeners) on weblogic 12.2.1.2(wichi is JPA 2.1 compliant) and hibernate 5.2.9Final.
I have found that BeanManager was not available in the jndi when my AuditListener is instanciated but well available when post and pre method are called. I have added a manual lookup on BeanManager on PostPersit method...
I didn't give this part to avoid confusion :p
My coworker found a property like "delayCdiInitXXXX" on hibernate configuration but that didn't work


Top
 Profile  
 
 Post subject: Re: How to get Rev from Audited entity for logging purpose
PostPosted: Thu Apr 06, 2017 2:24 pm 
Hibernate Team
Hibernate Team

Joined: Wed Jun 15, 2016 9:04 am
Posts: 24
Quote:
TBH I was wondering indeed if I shouldn't add those information in the audit table. I would probably need to add some other column like username, application, type of modification(maybe already there didn't check.),...
Is that possible and how could I add those information(a link to documentation is enough)

I touched on this on my earlier post, but you can find details in the documentation [url title="here"]http://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#envers-revisionlog[/url].

I want to clarify the revision type (aka REVTYPE). We store this value per entity in their associated audit table rather than in the revision entity. We do this because Envers is a transaction-commit audit framework, so a single revision can include any number of insert, update, or delete operations collectively.

The Envers Query API allows you to fetch back not only the detached entity instances with the revision state, but also the revision entity and the accompanied REVTYPE value so you can perform whatever business logic you need.

Code:
// Create the reader
final AuditReader auditReader = AuditReaderFactory.get( session );

// Create a query for your entity, specifying we want all information (detached entity, revision entity, and type) and include deletions.
final AuditQuery query = auditReader.createQuery().forRevisionsOfEntity( YourEntityClass.class, false, true );

// Restrict perhaps to a specific entity identifier
query.add( AuditEntity.id().eq( yourEntityId ) );

// get the results
List results = query.getResultList();
for ( Object row : results ) {
  final Object[] rowArray = (Object[]) row;

  // decompose each row into its three values
  // this is only applicable when a projection isn't specified and selectEntitiesOnly = false
  //
  final YourEntityClass entity = YourEntityClass.cast( rowArray[ 0 ] );
  final Object revisionEntity = rowArray[ 1 ];
  final RevisionType revisionType = rowArray[ 2 ];

  // do whatever you need to do here
}


What is notable to mention here is the fact that the second argument to #forRevisionsOfEntity must be false in order to have the result set return a 3 dimensional array of values; otherwise it will only return the entity instances when specified as true.

Secondly, the revision entity returned in the array index slot 1 is dependent on your configuration. This might be a custom revision entity in your case where you cast it to your instance type, but out of the box this may vary based on your configuration (e.g. might be a DefaultRevisionEntity or one of our enhanced versions).

Lastly, the third value in the row array represents the ADD (insert), MOD (update), and DEL (delete) operation that took place at the given revision number for that associated YourEntityClass instance.

Let me know if you have any further questions.


Top
 Profile  
 
 Post subject: Re: How to get Rev from Audited entity for logging purpose
PostPosted: Thu Apr 06, 2017 2:30 pm 
Newbie

Joined: Thu Apr 06, 2017 10:18 am
Posts: 4
Sorry I have change my original post while you were writting your answer because I forgot on the moment I had to do audit logging at service layer too.
I haved added some more questions and one remarks about cdi not working in callback entityListeners on weblogic 12.2.1.2(jee 7-> jpa 2.1)


Top
 Profile  
 
 Post subject: Re: How to get Rev from Audited entity for logging purpose
PostPosted: Thu Apr 06, 2017 2:43 pm 
Hibernate Team
Hibernate Team

Joined: Wed Jun 15, 2016 9:04 am
Posts: 24
Quote:
Well the problem is that I don't want to log only at persistence time.
My backend is expose through a Rest service which can be calles either by another backend or by the administration frontend application.
I want to log if someone did a change or read a data in database and also if someone tried to do a change event if it failed before reaching the persistance layer.

If you want to do something **before** it reaches the persistence layer, then if you're trying to integrate a solution that is based around any of the Hibernate portfolio of projects, haven't you defeated that goal? Hibernate is after all the persistence provider, is it not?

If you want to do any type of logging when you *read* data from the database, you'd have to consider some type of @PostLoad callback or implement your own Hibernate listener for that event type. Read operations by nature aren't within the scope of Envers and so none of what I have shown thus far address this need, nor was it presented until now ;).

For changes, Envers can certainly help, but its a reactive solution, not one that addressed your needs of a **before** the change occurs. If you need something to happen **before** the change, you'd again need to implement your own solution via a JPA @PreXXXX callback or implement your own equivalent Hibernate listener to accomplish the same task.

Perhaps a nice hybrid solution here would be something along the lines of:

1. Implement a @PostLoad callback that performs your necessary log operations for READs. Be aware that @PostLoad callbacks are only applicable within the scope of a managed entity; therefore since the Envers entity instances aren't managed and considered detached, their respective JPA event callbacks are never fired when you load them via the AuditReader.

2. Implement your @PreInsert/@PreUpdate/@PreRemove callbacks for logging all but the revision number attributes on changes within the scope of the JPA event lifecycle for the managed entity instance.

3. Implement a custom revision entity solution (as presented above) to log all change attributes.

Quote:
Code:
AuditReader reader = AuditReaderFactory.get(session);
AuditQuery query = reader.createQuery().forRevisionsOfEntity(Bar.class, true, true);

I was wondering if performance wise it wouldn't be a bit overkilled though ....


How exactly do you intend to use this API? In a listener or part of some service function?

Quote:
About AuditUserData, I already did some Thread variable in the past but this time I have used CDI requestScoped object that use SecurityContext(for Principal) and HttpServletRequest(to get headers).
BTW CDI injection is not working with callback(EntityListeners) on weblogic 12.2.1.2(wichi is JPA 2.1 compliant) and hibernate 5.2.9Final.
I have found that BeanManager was not available in the jndi when my AuditListener is instanciated but well available when post and pre method are called. I have added a manual lookup on BeanManager on PostPersit method...
I didn't give this part to avoid confusion :p
My coworker found a property like "delayCdiInitXXXX" on hibernate configuration but that didn't work

I need to check into this, but if something along CDI injection isn't working and the spec says it should, please file a JIRA bug for this so we can take a look into it and solve it.


Top
 Profile  
 
 Post subject: Re: How to get Rev from Audited entity for logging purpose
PostPosted: Thu Apr 06, 2017 2:57 pm 
Hibernate Team
Hibernate Team

Joined: Wed Jun 15, 2016 9:04 am
Posts: 24
One thing to consider about change logging, you could implement a custom AfterTransactionCompletionProcess implementation that uses your AuditUserDataHolder thread local to log the changes regardless of whether the transaction succeeded or not. In the entity JPA callbacks, you populate the thread local as needed with post-entity operation attributes, somewhat like I described above. Then from the Envers perspective, you do the same inside the revision entity's @PostInsert JPA lifecycle callback.

You can then test whether did the transaction succeed or fail by the callback's first argument value and log in either case. You can also inspect the thread local and log whatever attributes you need for your log file.

Yes this happens **after** the transaction takes place; however, you know whether the transaction succeeded or not and its always fired regardless of either case. Is there really a need to log this **before** so long as the data is consistent and represents what took place?


Top
 Profile  
 
 Post subject: Re: How to get Rev from Audited entity for logging purpose
PostPosted: Thu Apr 06, 2017 3:03 pm 
Newbie

Joined: Thu Apr 06, 2017 10:18 am
Posts: 4
Quote:
If you want to do something **before** it reaches the persistence layer, then if you're trying to integrate a solution that is based around any of the Hibernate portfolio of projects, haven't you defeated that goal? Hibernate is after all the persistence provider, is it not?

Quote:
How exactly do you intend to use this API? In a listener or part of some service function?

both in fact ^^.

Hibernate is our persistence layer through jpa. We had this morning a debate about weither to log at service layer or just consider that httpd apache access log could be enough to keep track of what/who tried to call the service event if it failed. Maybe that indeed we should only focus on auditing at persistence layer.

Quote:
If you want to do any type of logging when you *read* data from the database, you'd have to consider some type of @PostLoad callback or implement your own Hibernate listener for that event type. Read operations by nature aren't within the scope of Envers and so none of what I have shown thus far address this need, nor was it presented until now ;).

I have indeed used every callback(@PostPersist,@PostRemove,@PostUpdate,@PostLoad) but didn't include them in this example.


I think I will have a meeting tomorrow with my co-workers to check if logging at service level is really needed and if we need to do the audit log on read operation(though afair it was a "business" requirement).

Quote:
Is there really a need to log this **before** so long as the data is consistent and represents what took place?

That was my question this morning but the team thought so. I'm not quite sure though it's really usefull


Top
 Profile  
 
 Post subject: Re: How to get Rev from Audited entity for logging purpose
PostPosted: Thu Apr 06, 2017 3:33 pm 
Hibernate Team
Hibernate Team

Joined: Wed Jun 15, 2016 9:04 am
Posts: 24
thetrueavatar wrote:
Hibernate is our persistence layer through jpa. We had this morning a debate about weither to log at service layer or just consider that httpd apache access log could be enough to keep track of what/who tried to call the service event if it failed. Maybe that indeed we should only focus on auditing at persistence layer.

Or perhaps shift your perspective a tad.

In a prior project I worked on, we used a special annotation that caused an around advice to be applied to the annotated service method. The advice was designed to both log who and when the service method was called but went a step further and added a ThreadLocal variable for collection of data attributes through the methods execution. Any other code invoked behind the service method could ask for the ThreadLocal and add whatever it needed to it. When the service method exited, the around advice would determine if the method succeeded or failed as well as log whatever attributes were collected throughout the methods lifecycle. It would track the start/end times for the method for performance reasons so we could potentially identify methods that might need performance tuning over time.

So in your case, you have a hybrid solution. Your entity JPA callbacks populate your ThreadLocal with all the pertinent entity lifecycle callbacks as needed, the custom revision entity does the same for Envers, and your custom AfterTransactionCompletionProcess helps add information about whether hibernate succeeded / failed or you gather that from the Hibernate or JPA exception thrown.

My point here is so long as you **capture** the data and its stamped with the right timestamp/user/etc, its less important **when** it gets logged so long as the right attributes are in fact logged.


Top
 Profile  
 
 Post subject: Re: How to get Rev from Audited entity for logging purpose
PostPosted: Mon Apr 10, 2017 12:17 pm 
Hibernate Team
Hibernate Team

Joined: Wed Jun 15, 2016 9:04 am
Posts: 24
What did your team decide, thetrueavatar? Is there is a need for any specific feature request or did you guys find a solution you all agree with?


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 11 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.