Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Genesys PGR
App Blocks
Commits
807de2c2
Commit
807de2c2
authored
Jun 16, 2021
by
Matija Obreza
Browse files
Audits: New approach to recording changes
parent
b973e590
Changes
7
Expand all
Hide whitespace changes
Inline
Side-by-side
auditlog/src/main/java/org/genesys/blocks/auditlog/component/AuditTrailInterceptor.java
View file @
807de2c2
This diff is collapsed.
Click to expand it.
auditlog/src/main/java/org/genesys/blocks/auditlog/model/AuditLog.java
View file @
807de2c2
...
...
@@ -40,6 +40,7 @@ import org.genesys.blocks.model.EmptyModel;
import
org.genesys.blocks.util.JsonSidConverter
;
import
org.hibernate.annotations.Type
;
import
org.springframework.data.annotation.CreatedBy
;
import
org.springframework.data.annotation.CreatedDate
;
import
com.fasterxml.jackson.annotation.JsonProperty
;
...
...
@@ -69,6 +70,7 @@ public class AuditLog extends EmptyModel {
/** The log date. */
@Temporal
(
TemporalType
.
TIMESTAMP
)
@Column
(
name
=
"logdate"
,
nullable
=
false
)
@CreatedDate
private
Date
logDate
;
/** Class name of the referenced entity. */
...
...
@@ -358,4 +360,14 @@ public class AuditLog extends EmptyModel {
public
Object
getNewEntity
()
{
return
newEntity
;
}
@Override
public
String
toString
()
{
StringBuffer
sb
=
new
StringBuffer
();
sb
.
append
(
this
.
logDate
).
append
(
" "
).
append
(
this
.
action
).
append
(
" "
);
sb
.
append
(
this
.
classPk
.
getClassname
()).
append
(
"#"
).
append
(
this
.
entityId
);
sb
.
append
(
this
.
propertyName
).
append
(
" "
);
sb
.
append
(
this
.
previousState
).
append
(
" -> "
).
append
(
this
.
newState
);
return
sb
.
toString
();
}
}
auditlog/src/main/java/org/genesys/blocks/auditlog/model/TransactionAuditLog.java
View file @
807de2c2
...
...
@@ -28,24 +28,6 @@ public class TransactionAuditLog {
/** ID of the referenced entity. */
private
long
entityId
;
/** The name of the property modified. */
private
String
propertyName
;
/** The type of entity referenced in the changed property. */
private
ClassPK
referencedEntity
;
/** String representation of the previous state. */
private
String
previousState
;
/** String representation of the updated state. */
private
String
newState
;
/** The previous object */
private
Object
previousObject
;
/** The new object */
private
Object
newObject
;
/** The action. */
private
AuditAction
action
;
...
...
@@ -85,78 +67,6 @@ public class TransactionAuditLog {
this
.
entityId
=
entityId
;
}
/**
* Sets the property name.
*
* @param propertyName the new property name
*/
public
void
setPropertyName
(
final
String
propertyName
)
{
this
.
propertyName
=
propertyName
;
}
/**
* Gets the property name.
*
* @return the property name
*/
public
String
getPropertyName
()
{
return
propertyName
;
}
/**
* Sets the referenced entity.
*
* @param referencedEntity the new referenced entity
*/
public
void
setReferencedEntity
(
final
ClassPK
referencedEntity
)
{
this
.
referencedEntity
=
referencedEntity
;
}
/**
* Gets the referenced entity.
*
* @return the referenced entity
*/
public
ClassPK
getReferencedEntity
()
{
return
referencedEntity
;
}
/**
* Sets the previous state.
*
* @param previousState the new previous state
*/
public
void
setPreviousState
(
final
String
previousState
)
{
this
.
previousState
=
previousState
;
}
/**
* Gets the previous state.
*
* @return the previous state
*/
public
String
getPreviousState
()
{
return
previousState
;
}
/**
* Sets the new state.
*
* @param newState the new new state
*/
public
void
setNewState
(
final
String
newState
)
{
this
.
newState
=
newState
;
}
/**
* Gets the new state.
*
* @return the new state
*/
public
String
getNewState
()
{
return
newState
;
}
/**
* Sets the action.
*
...
...
@@ -175,34 +85,6 @@ public class TransactionAuditLog {
return
action
;
}
/**
* @return the previousObject
*/
public
Object
getPreviousObject
()
{
return
previousObject
;
}
/**
* @param previousObject the previousObject to set
*/
public
void
setPreviousObject
(
Object
previousObject
)
{
this
.
previousObject
=
previousObject
;
}
/**
* @return the newObject
*/
public
Object
getNewObject
()
{
return
newObject
;
}
/**
* @param newObject the newObject to set
*/
public
void
setNewObject
(
Object
newObject
)
{
this
.
newObject
=
newObject
;
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
...
...
@@ -213,7 +95,6 @@ public class TransactionAuditLog {
int
result
=
1
;
result
=
prime
*
result
+
((
classPk
==
null
)
?
0
:
classPk
.
hashCode
());
result
=
prime
*
result
+
(
int
)
(
entityId
^
(
entityId
>>>
32
));
result
=
prime
*
result
+
((
propertyName
==
null
)
?
0
:
propertyName
.
hashCode
());
return
result
;
}
...
...
@@ -237,20 +118,7 @@ public class TransactionAuditLog {
return
false
;
if
(
entityId
!=
other
.
entityId
)
return
false
;
if
(
propertyName
==
null
)
{
if
(
other
.
propertyName
!=
null
)
return
false
;
}
else
if
(!
propertyName
.
equals
(
other
.
propertyName
))
return
false
;
return
true
;
}
@Override
public
String
toString
()
{
StringBuffer
sb
=
new
StringBuffer
();
sb
.
append
(
action
.
toString
()).
append
(
" shortname="
).
append
(
classPk
.
getShortName
()).
append
(
" id="
).
append
(
entityId
).
append
(
" "
).
append
(
propertyName
).
append
(
"='"
).
append
(
previousState
).
append
(
"' -> '"
).
append
(
newState
).
append
(
"'"
);
return
sb
.
toString
();
}
}
auditlog/src/main/java/org/genesys/blocks/auditlog/service/AuditTrailService.java
View file @
807de2c2
...
...
@@ -15,14 +15,13 @@
*/
package
org.genesys.blocks.auditlog.service
;
import
java.util.Collection
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Set
;
import
org.
genesys.blocks.auditlog.component.AuditTrailIntercepto
r
;
import
org.
apache.commons.lang3.tuple.Pai
r
;
import
org.genesys.blocks.auditlog.model.AuditAction
;
import
org.genesys.blocks.auditlog.model.AuditLog
;
import
org.genesys.blocks.auditlog.model.TransactionAuditLog
;
import
org.genesys.blocks.auditlog.model.filters.AuditLogFilter
;
import
org.genesys.blocks.model.EntityId
;
import
org.springframework.data.domain.Page
;
...
...
@@ -44,30 +43,13 @@ public interface AuditTrailService {
*/
Page
<
AuditLog
>
listAuditLogs
(
AuditLogFilter
filters
,
Pageable
page
);
/**
* Create a temporary audit log entry. This is used by the
* {@link AuditTrailInterceptor}.
*
* @param action the action
* @param entity the entity
* @param id the id
* @param propertyName the property name
* @param previousState the previous state
* @param currentState the current state
* @param referencedEntity the referenced entity
* @param previousObject the previous object (not persisted)
* @param currentObject the current object (not persisted)
* @return the transaction audit log
*/
TransactionAuditLog
auditLogEntry
(
AuditAction
action
,
Object
entity
,
long
id
,
String
propertyName
,
String
previousState
,
String
currentState
,
Class
<?>
referencedEntity
,
Object
previousObject
,
Object
currentObject
);
/**
* Record audit logs in the database.
*
* @param
a
uditLogs the audit logs
* @param
currentA
uditLogs the audit logs
* @return the list
*/
List
<
AuditLog
>
addAuditLogs
(
Set
<
TransactionAuditLog
>
a
uditLogs
);
List
<
AuditLog
>
addAuditLogs
(
Map
<
ChangeKey
,
Map
<
String
,
PropertyChange
<?>>>
currentA
uditLogs
);
/**
* List audit logs for the specified entity. Logs are sorted by log date
...
...
@@ -91,4 +73,89 @@ public interface AuditTrailService {
*/
Map
<
String
,
List
<
AuditLog
>>
auditLogs
(
EntityId
entity
);
static
class
ChangeKey
{
public
Class
<?>
clazz
;
public
long
id
;
public
AuditAction
type
;
private
ChangeKey
(
AuditAction
type
,
Class
<?
extends
Object
>
clazz
,
long
id
)
{
this
.
clazz
=
clazz
;
this
.
id
=
id
;
this
.
type
=
type
;
}
@Override
public
int
hashCode
()
{
final
int
prime
=
31
;
int
result
=
1
;
result
=
prime
*
result
+
((
clazz
==
null
)
?
0
:
clazz
.
hashCode
());
result
=
prime
*
result
+
(
int
)
(
id
^
(
id
>>>
32
));
result
=
prime
*
result
+
((
type
==
null
)
?
0
:
type
.
hashCode
());
return
result
;
}
@Override
public
boolean
equals
(
Object
obj
)
{
if
(
this
==
obj
)
{
return
true
;
}
if
(
obj
==
null
)
{
return
false
;
}
if
(
getClass
()
!=
obj
.
getClass
())
{
return
false
;
}
ChangeKey
other
=
(
ChangeKey
)
obj
;
if
(
clazz
==
null
)
{
if
(
other
.
clazz
!=
null
)
{
return
false
;
}
}
else
if
(!
clazz
.
equals
(
other
.
clazz
))
{
return
false
;
}
if
(
id
!=
other
.
id
)
{
return
false
;
}
if
(
type
!=
other
.
type
)
{
return
false
;
}
return
true
;
}
public
static
ChangeKey
of
(
AuditAction
type
,
Class
<?
extends
Object
>
clazz
,
long
id
)
{
return
new
ChangeKey
(
type
,
clazz
,
id
);
}
}
static
class
PropertyChange
<
T
>
{
public
Class
<?
extends
T
>
clazz
;
public
Pair
<
Object
,
Object
>
change
;
private
PropertyChange
(
Class
<
T
>
clazz
,
Collection
<?>
previous
,
Collection
<?>
remaining
)
{
this
.
clazz
=
clazz
;
this
.
change
=
Pair
.
of
(
previous
,
remaining
);
}
private
PropertyChange
(
Class
<
T
>
clazz
,
Object
previous
,
Object
remaining
)
{
this
.
clazz
=
clazz
;
this
.
change
=
Pair
.
of
(
previous
,
remaining
);
}
public
static
<
T
>
PropertyChange
<
T
>
of
(
Class
<
T
>
clazz
,
Collection
<?>
previous
,
Collection
<?>
remaining
)
{
return
new
PropertyChange
<
T
>(
clazz
,
previous
,
remaining
);
}
public
static
<
T
>
PropertyChange
<
T
>
of
(
Class
<
T
>
propertyType
,
Object
prev
,
Object
curr
)
{
return
new
PropertyChange
<>(
propertyType
,
prev
,
curr
);
}
@Override
public
String
toString
()
{
StringBuffer
sb
=
new
StringBuffer
();
sb
.
append
(
"["
).
append
(
this
.
clazz
).
append
(
"]: "
);
sb
.
append
(
this
.
change
);
return
sb
.
toString
();
}
}
}
auditlog/src/main/java/org/genesys/blocks/auditlog/service/impl/AuditTrailServiceImpl.java
View file @
807de2c2
...
...
@@ -15,18 +15,35 @@
*/
package
org.genesys.blocks.auditlog.service.impl
;
import
java.lang.reflect.Field
;
import
java.lang.reflect.Type
;
import
java.text.Format
;
import
java.util.ArrayList
;
import
java.util.Calendar
;
import
java.util.Collection
;
import
java.util.Collections
;
import
java.util.Date
;
import
java.util.HashMap
;
import
java.util.HashSet
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Map.Entry
;
import
java.util.Set
;
import
java.util.UUID
;
import
java.util.stream.Collectors
;
import
java.util.stream.Stream
;
import
javax.persistence.Temporal
;
import
javax.persistence.TemporalType
;
import
org.apache.commons.lang3.StringUtils
;
import
org.apache.commons.lang3.math.NumberUtils
;
import
org.apache.commons.lang3.time.FastDateFormat
;
import
org.genesys.blocks.auditlog.annotations.Audited
;
import
org.genesys.blocks.auditlog.annotations.HideAuditValue
;
import
org.genesys.blocks.auditlog.annotations.NotAudited
;
import
org.genesys.blocks.auditlog.model.AuditAction
;
import
org.genesys.blocks.auditlog.model.AuditLog
;
import
org.genesys.blocks.auditlog.model.TransactionAuditLog
;
import
org.genesys.blocks.auditlog.model.filters.AuditLogFilter
;
import
org.genesys.blocks.auditlog.persistence.AuditLogRepository
;
import
org.genesys.blocks.auditlog.service.AuditTrailService
;
...
...
@@ -35,13 +52,15 @@ import org.genesys.blocks.model.ClassPK;
import
org.genesys.blocks.model.EntityId
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
org.springframework.beans.factory.InitializingBean
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.data.domain.Page
;
import
org.springframework.data.domain.Pageable
;
import
org.springframework.stereotype.Service
;
import
org.springframework.transaction.annotation.Isolation
;
import
org.springframework.transaction.annotation.Propagation
;
import
org.springframework.transaction.annotation.Transactional
;
import
org.springframework.util.CollectionUtils
;
import
org.springframework.util.ReflectionUtils
;
/**
* Implementation of the {@link AuditTrailService} with JPA.
...
...
@@ -50,7 +69,7 @@ import org.springframework.transaction.annotation.Transactional;
*/
@Service
@Transactional
(
readOnly
=
true
)
public
class
AuditTrailServiceImpl
implements
AuditTrailService
{
public
class
AuditTrailServiceImpl
implements
AuditTrailService
,
InitializingBean
{
/** The Constant LOG. */
private
static
final
Logger
LOG
=
LoggerFactory
.
getLogger
(
AuditTrailServiceImpl
.
class
);
...
...
@@ -63,67 +82,100 @@ public class AuditTrailServiceImpl implements AuditTrailService {
@Autowired
private
AuditLogRepository
auditLogRepository
;
/*
* (non-Javadoc)
* @see
* org.genesys.blocks.auditlog.service.AuditTrailService#addAuditLogs(java.util.
* Set)
*/
@Override
@Transactional
(
isolation
=
Isolation
.
READ_UNCOMMITTED
,
propagation
=
Propagation
.
REQUIRED
)
public
List
<
AuditLog
>
addAuditLogs
(
final
Set
<
TransactionAuditLog
>
auditLogs
)
{
return
auditLogRepository
.
saveAll
(
auditLogs
.
stream
().
map
(
tlog
->
toAuditLog
(
tlog
)).
collect
(
Collectors
.
toList
()));
}
/** The date format. */
private
final
String
dateFormat
=
"dd-MMM-yyyy"
;
/*
* (non-Javadoc)
* @see org.genesys.blocks.auditlog.service.AuditTrailService#auditLogEntry(org.
* genesys.blocks.auditlog.model.AuditAction, java.lang.Object, long,
* java.lang.String, java.lang.String, java.lang.String, java.lang.Class)
*/
@Override
public
TransactionAuditLog
auditLogEntry
(
final
AuditAction
action
,
final
Object
entity
,
final
long
id
,
final
String
propertyName
,
final
String
previousState
,
final
String
currentState
,
final
Class
<?>
referencedEntity
,
final
Object
previousObject
,
final
Object
currentObject
)
{
/** The time format. */
private
final
String
timeFormat
=
"HH:mm:ss"
;
final
TransactionAuditLog
log
=
new
TransactionAuditLog
();
log
.
setAction
(
action
)
;
/** The date time format. */
private
final
String
dateTimeFormat
=
"dd-MMM-yyyy HH:mm:ss"
;
log
.
setClassPk
(
classPkService
.
getClassPk
(
entity
.
getClass
()));
log
.
setEntityId
(
id
);
log
.
setPropertyName
(
propertyName
);
log
.
setPreviousState
(
previousState
);
log
.
setNewState
(
currentState
);
log
.
setPreviousObject
(
previousObject
);
// Transient
log
.
setNewObject
(
currentObject
);
// Transient
if
(
referencedEntity
!=
null
)
{
log
.
setReferencedEntity
(
classPkService
.
getClassPk
(
referencedEntity
));
}
/** The date formatter. */
private
Format
dateFormatter
=
FastDateFormat
.
getInstance
(
dateFormat
);
/** The date time formatter. */
private
Format
dateTimeFormatter
=
FastDateFormat
.
getInstance
(
dateTimeFormat
);
/** The time formatter. */
private
Format
timeFormatter
=
FastDateFormat
.
getInstance
(
timeFormat
);
/** The audited classes. */
private
Set
<
Class
<?>>
auditedClasses
=
new
HashSet
<>();
/** The Constant DEFAULT_IGNORED_PROPERTIES. */
private
static
final
Set
<
String
>
DEFAULT_IGNORED_PROPERTIES
=
Stream
.
of
(
"serialVersionUID"
,
"id"
,
"createdDate"
,
"lastModifiedDate"
,
"version"
,
"lastModifiedBy"
)
.
collect
(
Collectors
.
toSet
());
/** The ignored properties. */
private
Set
<
String
>
ignoredProperties
=
new
HashSet
<>(
DEFAULT_IGNORED_PROPERTIES
);
LOG
.
trace
(
"Creating {} audit log entity={} id={} prop={} old={} new={} ref={}"
,
action
,
entity
.
getClass
().
getName
(),
id
,
propertyName
,
previousState
,
currentState
,
referencedEntity
==
null
?
null
:
referencedEntity
.
getName
());
/** The secured properties of audited entities. */
private
final
Map
<
Class
<?>,
Set
<
String
>>
securedClassFields
=
Collections
.
synchronizedMap
(
new
HashMap
<>
());
return
log
;
/** The ignored properties of audited entities. */
private
final
Map
<
Class
<?>,
Set
<
String
>>
ignoredClassFields
;
public
AuditTrailServiceImpl
()
{
// make synchronized local caches
ignoredClassFields
=
Collections
.
synchronizedMap
(
new
HashMap
<>());
}
/**
* To audit log.
* Explicitly set the list of classes that should be audited. Note that any
* class with {@link Audited} annotation will be included, even if not on this
* list.
*
* @param tlog the tlog
* @return the audit log
* @param auditedClasses entity classes to audit
* @return
* @see Audited
*/
private
AuditLog
toAuditLog
(
final
TransactionAuditLog
tlog
)
{
final
AuditLog
auditLog
=
new
AuditLog
();
auditLog
.
setLogDate
(
new
Date
());
auditLog
.
setAction
(
tlog
.
getAction
());
auditLog
.
setClassPk
(
tlog
.
getClassPk
());
auditLog
.
setEntityId
(
tlog
.
getEntityId
());
auditLog
.
setNewState
(
tlog
.
getNewState
());
auditLog
.
setPreviousState
(
tlog
.
getPreviousState
());
auditLog
.
setPropertyName
(
tlog
.
getPropertyName
());
auditLog
.
setReferencedEntity
(
tlog
.
getReferencedEntity
());
auditLog
.
setPreviousEntity
(
tlog
.
getPreviousObject
());
auditLog
.
setNewEntity
(
tlog
.
getNewObject
());
return
auditLog
;
public
Set
<
Class
<?>>
setAuditedClasses
(
final
Set
<
Class
<?>>
auditedClasses
)
{
return
this
.
auditedClasses
=
auditedClasses
;
}
/**
* Gets the audited classes.
*
* @return the audited classes
*/
public
Set
<
Class
<?>>
getAuditedClasses
()
{
return
auditedClasses
;
}
/**
* Set the list of properties to ignore on all entities (e.g. "password").
* Defaults to {@link #DEFAULT_IGNORED_PROPERTIES}. Note that you can explicitly
* exclude fields by annotating them with <code>@NotAudited</code> annotation
* (see {@link NotAudited}).
*
* @param ignoredProperties entity property names to exclude from audit trail
* @see NotAudited
*/
public
void
setIgnoredProperties
(
final
Set
<
String
>
ignoredProperties
)
{
this
.
ignoredProperties
=
ignoredProperties
;
}
/**
* Gets the ignored properties.
*
* @return the ignored properties
*/
public
Set
<
String
>
getIgnoredProperties
()
{
return
ignoredProperties
;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override
public
void
afterPropertiesSet
()
throws
Exception
{
assert
ignoredProperties
!=
null
;
// Make them unmodifiable
ignoredProperties
=
Collections
.
unmodifiableSet
(
ignoredProperties
);
auditedClasses
=
Collections
.
unmodifiableSet
(
auditedClasses
);
}
/*
...
...
@@ -179,4 +231,248 @@ public class AuditTrailServiceImpl implements AuditTrailService {
});
return
logMap
;
}
@Override
@Transactional
(
propagation
=
Propagation
.
MANDATORY
)
public
List
<
AuditLog
>
addAuditLogs
(
Map
<
ChangeKey
,
Map
<
String
,
PropertyChange
<?>>>
currentAuditLogs
)
{
LOG
.
debug
(
"Registering changes"
);
var
changeDate
=
new
Date
();
var
auditLogs
=
new
ArrayList
<
AuditLog
>();
for
(
Entry
<
ChangeKey
,
Map
<
String
,
PropertyChange
<?>>>
entry
:
currentAuditLogs
.
entrySet
())
{
var
key
=
entry
.
getKey
();
var
val
=
entry
.
getValue
();
final
Set
<
String
>
entityIgnoredFields
=
ignoredClassFields
.
get
(
key
.
clazz
);
LOG
.
trace
(
"{} {}#{}"
,
key
.
type
,
key
.
clazz
,
key
.
id
);
val
.
forEach
((
propertyName
,
change
)
->
{
if
(
ignoredProperties
.
contains
(
propertyName
)
||
(
entityIgnoredFields
!=
null
&&
entityIgnoredFields
.
contains
(
propertyName
)))
{
LOG
.
trace
(
"{}.{} property is not audited."
,
key
.
clazz
.
getSimpleName
(),
propertyName
);
return
;
}
// LOG.warn("\t{} {}: {} -> {}", key.type, propertyName, change.change.getLeft(), change.change.getRight());
var
auditLog
=
toAuditLog
(
changeDate
,
key
,
propertyName
,
change
);
if
(
auditLog
!=
null
)
{