Skip to content

Commit 6b3c5da

Browse files
committed
added Entity Audit Log
1 parent 9b5eccc commit 6b3c5da

23 files changed

+633
-11
lines changed

pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,10 @@
175175
<groupId>org.hibernate.validator</groupId>
176176
<artifactId>hibernate-validator</artifactId>
177177
</dependency>
178+
<dependency>
179+
<groupId>org.hibernate</groupId>
180+
<artifactId>hibernate-envers</artifactId>
181+
</dependency>
178182
<dependency>
179183
<groupId>org.liquibase</groupId>
180184
<artifactId>liquibase-core</artifactId>

src/main/java/com/cevheri/blog/config/CacheConfiguration.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ public JCacheManagerCustomizer cacheManagerCustomizer() {
5757
createCache(cm, com.cevheri.blog.domain.PostView.class.getName());
5858
createCache(cm, com.cevheri.blog.domain.PostLike.class.getName());
5959
createCache(cm, com.cevheri.blog.domain.ThirdPartyApp.class.getName());
60-
createCache(cm, com.cevheri.blog.domain.IntegrationLog.class.getName());
6160
// jhipster-needle-ehcache-add-entry
6261
};
6362
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package com.cevheri.blog.config.audit;
2+
3+
import com.cevheri.blog.domain.audit.AbstractEntityAuditEvent;
4+
import com.cevheri.blog.domain.audit.EntityAuditEvent;
5+
import com.cevheri.blog.repository.audit.EntityAuditEventRepository;
6+
import com.fasterxml.jackson.databind.ObjectMapper;
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
import org.springframework.scheduling.annotation.Async;
10+
import org.springframework.stereotype.Component;
11+
12+
import java.io.IOException;
13+
import java.lang.reflect.Field;
14+
15+
/**
16+
* Async Entity Audit Event writer
17+
* This is invoked by Hibernate entity listeners to write audit event for entitities
18+
*/
19+
@Component
20+
public class AsyncEntityAuditEventWriter {
21+
22+
private final Logger log = LoggerFactory.getLogger(AsyncEntityAuditEventWriter.class);
23+
24+
private final EntityAuditEventRepository auditingEntityRepository;
25+
26+
private final ObjectMapper objectMapper; //Jackson object mapper
27+
28+
public AsyncEntityAuditEventWriter(EntityAuditEventRepository auditingEntityRepository,
29+
ObjectMapper objectMapper) {
30+
this.auditingEntityRepository = auditingEntityRepository;
31+
this.objectMapper = objectMapper;
32+
}
33+
34+
/**
35+
* Writes audit events to DB asynchronously in a new thread
36+
*/
37+
@Async
38+
public void writeAuditEvent(Object target, EntityAuditAction action) {
39+
log.debug("-------------- Post {} audit --------------", action.value());
40+
try {
41+
EntityAuditEvent auditedEntity = prepareAuditEntity(target, action);
42+
if (auditedEntity != null) {
43+
auditingEntityRepository.save(auditedEntity);
44+
}
45+
} catch (Exception e) {
46+
log.error("Exception while persisting audit entity for {} error: {}", target, e);
47+
}
48+
}
49+
50+
/**
51+
* Method to prepare auditing entity
52+
*
53+
* @param entity
54+
* @param action
55+
* @return
56+
*/
57+
private EntityAuditEvent prepareAuditEntity(final Object entity, EntityAuditAction action) {
58+
EntityAuditEvent auditedEntity = new EntityAuditEvent();
59+
Class<?> entityClass = entity.getClass(); // Retrieve entity class with reflection
60+
auditedEntity.setAction(action.value());
61+
auditedEntity.setEntityType(entityClass.getName());
62+
Long entityId;
63+
String entityData;
64+
log.trace("Getting Entity Id and Content");
65+
try {
66+
Field privateLongField = entityClass.getDeclaredField("id");
67+
privateLongField.setAccessible(true);
68+
entityId = (Long) privateLongField.get(entity);
69+
privateLongField.setAccessible(false);
70+
entityData = objectMapper.writeValueAsString(entity);
71+
} catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException | IOException e) {
72+
log.error("Exception while getting entity ID and content {}", e);
73+
// returning null as we dont want to raise an application exception here
74+
return null;
75+
}
76+
auditedEntity.setEntityId(entityId);
77+
auditedEntity.setEntityValue(entityData);
78+
final AbstractEntityAuditEvent abstractAuditEntity = (AbstractEntityAuditEvent) entity;
79+
if (EntityAuditAction.CREATE.equals(action)) {
80+
auditedEntity.setModifiedBy(abstractAuditEntity.getCreatedBy());
81+
auditedEntity.setModifiedDate(abstractAuditEntity.getCreatedDate());
82+
auditedEntity.setCommitVersion(1);
83+
} else {
84+
auditedEntity.setModifiedBy(abstractAuditEntity.getLastModifiedBy());
85+
auditedEntity.setModifiedDate(abstractAuditEntity.getLastModifiedDate());
86+
calculateVersion(auditedEntity);
87+
}
88+
log.trace("Audit Entity --> {} ", auditedEntity.toString());
89+
return auditedEntity;
90+
}
91+
92+
private void calculateVersion(EntityAuditEvent auditedEntity) {
93+
log.trace("Version calculation. for update/remove");
94+
Integer lastCommitVersion = auditingEntityRepository.findMaxCommitVersion(
95+
auditedEntity.getEntityType(),
96+
auditedEntity.getEntityId()
97+
);
98+
log.trace("Last commit version of entity => {}", lastCommitVersion);
99+
if (lastCommitVersion != null && lastCommitVersion != 0) {
100+
log.trace("Present. Adding version..");
101+
auditedEntity.setCommitVersion(lastCommitVersion + 1);
102+
} else {
103+
log.trace("No entities.. Adding new version 1");
104+
auditedEntity.setCommitVersion(1);
105+
}
106+
}
107+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.cevheri.blog.config.audit;
2+
3+
/**
4+
* Enum for the different audit actions
5+
*/
6+
public enum EntityAuditAction {
7+
CREATE("CREATE"),
8+
UPDATE("UPDATE"),
9+
DELETE("DELETE");
10+
11+
private String value;
12+
13+
EntityAuditAction(final String value) {
14+
this.value = value;
15+
}
16+
17+
public String value() {
18+
return value;
19+
}
20+
21+
@Override
22+
public String toString() {
23+
return this.value();
24+
}
25+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.cevheri.blog.config.audit;
2+
3+
import org.springframework.beans.factory.BeanFactory;
4+
import org.springframework.beans.factory.BeanFactoryAware;
5+
import org.springframework.context.annotation.Configuration;
6+
7+
@Configuration
8+
public class EntityAuditEventConfig implements BeanFactoryAware {
9+
10+
@Override
11+
public void setBeanFactory(BeanFactory beanFactory) {
12+
EntityAuditEventListener.setBeanFactory(beanFactory);
13+
}
14+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.cevheri.blog.config.audit;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
import org.springframework.beans.factory.BeanFactory;
6+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
7+
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
8+
9+
import javax.persistence.PostPersist;
10+
import javax.persistence.PostRemove;
11+
import javax.persistence.PostUpdate;
12+
13+
public class EntityAuditEventListener extends AuditingEntityListener {
14+
15+
private final Logger log = LoggerFactory.getLogger(EntityAuditEventListener.class);
16+
17+
private static BeanFactory beanFactory;
18+
19+
@PostPersist
20+
public void onPostCreate(Object target) {
21+
try {
22+
AsyncEntityAuditEventWriter asyncEntityAuditEventWriter = beanFactory.getBean(AsyncEntityAuditEventWriter.class);
23+
asyncEntityAuditEventWriter.writeAuditEvent(target, EntityAuditAction.CREATE);
24+
} catch (NoSuchBeanDefinitionException e) {
25+
log.error("No bean found for AsyncEntityAuditEventWriter");
26+
} catch (Exception e) {
27+
log.error("Exception while persisting create audit entity {}", e);
28+
}
29+
}
30+
31+
@PostUpdate
32+
public void onPostUpdate(Object target) {
33+
try {
34+
AsyncEntityAuditEventWriter asyncEntityAuditEventWriter = beanFactory.getBean(AsyncEntityAuditEventWriter.class);
35+
asyncEntityAuditEventWriter.writeAuditEvent(target, EntityAuditAction.UPDATE);
36+
} catch (NoSuchBeanDefinitionException e) {
37+
log.error("No bean found for AsyncEntityAuditEventWriter");
38+
} catch (Exception e) {
39+
log.error("Exception while persisting update audit entity {}", e);
40+
}
41+
}
42+
43+
@PostRemove
44+
public void onPostRemove(Object target) {
45+
try {
46+
AsyncEntityAuditEventWriter asyncEntityAuditEventWriter = beanFactory.getBean(AsyncEntityAuditEventWriter.class);
47+
asyncEntityAuditEventWriter.writeAuditEvent(target, EntityAuditAction.DELETE);
48+
} catch (NoSuchBeanDefinitionException e) {
49+
log.error("No bean found for AsyncEntityAuditEventWriter");
50+
} catch (Exception e) {
51+
log.error("Exception while persisting delete audit entity {}", e);
52+
}
53+
}
54+
55+
static void setBeanFactory(BeanFactory beanFactory) {
56+
EntityAuditEventListener.beanFactory = beanFactory;
57+
}
58+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* Entity audit listener
3+
* automatic created_date, created_by, updated_date, updated_by values by current user.
4+
* entity logs: insert, update, delete logs.
5+
*/
6+
package com.cevheri.blog.config.audit;

src/main/java/com/cevheri/blog/domain/Blog.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import javax.persistence.*;
55
import javax.validation.constraints.*;
66

7+
import com.cevheri.blog.domain.audit.AbstractEntityAuditEvent;
78
import org.hibernate.annotations.Cache;
89
import org.hibernate.annotations.CacheConcurrencyStrategy;
910

@@ -13,7 +14,7 @@
1314
@Entity
1415
@Table(name = "blog")
1516
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
16-
public class Blog extends AbstractAuditingEntity
17+
public class Blog extends AbstractEntityAuditEvent
1718
implements Serializable {
1819

1920
private static final long serialVersionUID = 1L;

src/main/java/com/cevheri/blog/domain/Post.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.cevheri.blog.domain;
22

3+
import com.cevheri.blog.domain.audit.AbstractEntityAuditEvent;
34
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
45

56
import java.io.Serializable;
@@ -18,7 +19,7 @@
1819
@Entity
1920
@Table(name = "post")
2021
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
21-
public class Post extends AbstractAuditingEntity
22+
public class Post extends AbstractEntityAuditEvent
2223
implements Serializable {
2324

2425
private static final long serialVersionUID = 1L;

src/main/java/com/cevheri/blog/domain/PostComment.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.cevheri.blog.domain;
22

3+
import com.cevheri.blog.domain.audit.AbstractEntityAuditEvent;
34
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
45

56
import java.io.Serializable;
@@ -15,7 +16,7 @@
1516
@Entity
1617
@Table(name = "post_comment")
1718
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
18-
public class PostComment extends AbstractAuditingEntity
19+
public class PostComment extends AbstractEntityAuditEvent
1920
implements Serializable {
2021

2122
private static final long serialVersionUID = 1L;

0 commit comments

Comments
 (0)