3
votes

I need to show history of modifications on entities on UI. Something like this -

    |-------------------------------------------------------------------------------------------------------------------------------------|
    |Entity name  |  Entity ID  |  Action by  |  Action on           |  Action Type  |  Old value               |  New value              |
    |-------------|-------------|-------------|----------------------|---------------|--------------------------|-------------------------|
    |Person       |     1       |   John Doe  | 2017-04-12 12:00:00  |   Add         |                          | name: Lily, Role: Admin |
    |Person       |     1       |   John Doe  | 2017-04-15 12:00:00  |   Update      |name: Lily                | name: Lily Smith        |
    |Subject      |     5       |   Mary      | 2017-04-17 12:00:00  |   Update      |name: Maths               | name: Mathematics       |
    |Subject      |     6       |   Mary      | 2017-04-17 12:00:00  |   Delete      |name: Science, credits: 5 |                         |
    |-------------------------------------------------------------------------------------------------------------------------------------|

I used Hibernate envers to store all the data I need. But I'm having problem reading up all the data to show something like I need above. I search a lot of blogs, Questions articles, documents, javadocs on the internet, but none of them shows how to do this. All of them show how to retrieve entities based on revision or return revisions. None of these entities have following information in addition to entities returned -

  1. Who did the change
  2. When did that change happen
  3. What type of change was it. ADD/UPDATE/DELETE

Please note that I want as least number of queries possible to speed up the history API response.

I'm using hibernate and envers version: '5.2.9.Final'

Any help in highly appreciated.

Thank you!

Update

Here's the code -

Annotations above each entity class-


    @Audited(withModifiedFlag = true)

Custom revision listener -


    public class CustomRevisionListener implements RevisionListener {

        private static final Logger logger = 
        LogManager.getLogger(CustomRevisionListener.class);

        public void newRevision(Object revisionEntity) {
            logger.info("newRevision, starts revisionEntity="+revisionEntity);

            CustomRevisionEntity revision = (CustomRevisionEntity) revisionEntity;

            String name = "unknown";

            try{
                //user custom userdetails.User to store id and name both? 
                //or just store id in string format here?
                org.springframework.security.core.userdetails.User springUser = 
                        (org.springframework.security.core.userdetails.User)  SecurityContextHolder
                        .getContext().getAuthentication().getPrincipal();
                name = springUser.getUsername(); //get logged in username
            }catch(Exception e){
                logger.error("error getting username", e);
            }

            logger.info("newRevision, name="+name);

            User user = new User();
            user.setName(name);
            revision.setUser(user); //for testing

            logger.info("newRevision, setting userId="+user.getId());
        }
    }

Custom revision entity -


    @Entity
    @Table(name="REVISIONS")
    @RevisionEntity(CustomRevisionListener.class)
    public class CustomRevisionEntity extends DefaultRevisionEntity {

        private static final long serialVersionUID = -6113123831136684807L;

        @ManyToOne
        @JoinColumn(name="USER_ID")
        private User user;

        public User getUser() {
            return user;
        }

        public void setUser(User user) {
            this.user = user;
        }

    }

envers config -


    org.hibernate.envers.track_entities_changed_in_revision=true
    org.hibernate.envers.global_with_modified_flag=true
    org.hibernate.envers.store_data_at_delete=true
    org.hibernate.envers.audit_strategy=org.hibernate.envers.strategy.ValidityAuditStrategy
    org.hibernate.envers.audit_strategy_validity_store_revend_timestamp=true

Reading audits -


        public List getAllAudits() {
            logger.debug("getAllAudits starts");

            //HERE IS WHERE I NEED HELP.
            //below code is just for trials. I need something which will read all audits and relevant info I described above

            ArrayList list = (ArrayList) AuditReaderFactory.get(entityManager) 
                    .createQuery() 
                    .forRevisionsOfEntity(Organization.class, true, true) 
    //              .add(AuditEntity.id().eq(bitacoraControlId)) 
                    .addOrder(AuditEntity.revisionNumber().asc()) 
                    .getResultList();

            for(int i=0; i  revisions = reader.getRevisions(Organization.class, 2);
            for(Number revNum:revisions){
                Organization article =reader.find(Organization.class, 2, revNum);
                System.out.println("Revision No: " + revNum);
                System.out.println("Title: " + article.getId());
                System.out.println("Content: " + article.getName());        

            }

            logger.debug("getAllAudits list size="+list.size());

            return list;
        }

1
Can you share your code?utkusonmez
updated the post with code. @utkusonmez - hopefully now you'll be able to help?Medha

1 Answers

5
votes

Ok, so I found my answer. Here is how I have to read audits for each type of Entity separately and merge them into one list -

    AuditQuery query = AuditReaderFactory.get(entityManager)
            .createQuery()
            .forRevisionsOfEntity(clazz, false /*IMP*/, true)
            .addOrder(AuditEntity.revisionNumber().desc());

    //This return a list of array triplets of changes concerning the specified revision.
    // The array triplet contains the entity, entity revision information and at last the revision type.
    ArrayList<Object[]> list = (ArrayList) query.getResultList();

    for(int i=0; i < list.size(); i++){
        Object[] triplet = list.get(i);

        BaseEntity entity = (BaseEntity) triplet[0];
        CustomRevisionEntity revisionEntity = (CustomRevisionEntity) triplet[1];
        RevisionType revisionType = (RevisionType) triplet[2];

        ...//populate DTO
    }

If the RevisionType was "MOD" To get old value and new value with only the fields which were changed, I had to get the previous version and compare them field by field. Though Envers does record this field modified information in tables, but doesn't provide a good way to read.

 public BaseEntity getPreviousVersion(BaseEntity entity, int current_rev) {

    AuditReader reader = AuditReaderFactory.get(entityManager);

    Number prior_revision = (Number) reader.createQuery()
            .forRevisionsOfEntity(entity.getClass(), false, true)
            .addProjection(AuditEntity.revisionNumber().max())
            .add(AuditEntity.id().eq(entity.getId()))
            .add(AuditEntity.revisionNumber().lt(current_rev))
            .getSingleResult();

    if (prior_revision != null)
        return reader.find(entity.getClass(), entity.getId(), prior_revision);
    else
        return null;
}