Search code examples
javaspringjpaaudit-logging

Audit Property change - Spring MVC+ JPA


I have a class Client. I want to be able to audit the changes of property of this class(not entire class - just it's properties).

public class Client {
private Long id;
private String firstName;
private String lastName;
private String email;
private String mobileNumber;
private Branch companyBranch;

actually this is very easy to audit the whole entity with @Audited annotation.

But what I want is to audit this changes using my class structure.

here is my desired result class:

public class Action {
private String fieldName;
private String oldValue;
private String newValue;
private String action;
private Long modifiedBy;
private Date changeDate;
private Long clientID;

the result should look like this:

fieldName + "was changed from " + oldValue + "to" + newValue + "for" clientID +"by" modifiedBy;

  • mobileNumber was changed from 555 to 999 for Bill Gates by George.

The reason I'm doing this is that I need to store this changes into DB under Action table - because I will be Auditing properties from different Entities and I want to store that together and then have a ability to get them when I need.

How can I do this?

Thanks


Solution

  • Aop is right way to go. You can use AspectJ with field set() pointcut for your needs. With before aspect you can extract necessary info to populate Action object.

    Also you can use custom class Annotation @AopAudit to detect classes you want to audit. You must define such annotation in your classpath and place in under target classes which you want to audit.

    This approach can look like this:

    AopAudit.java

    @Retention(RUNTIME)
    @Target(TYPE)
    public @interface AopAudit {
    
    }
    

    Client.java

    @AopAudit
    public class Client {
        private Long id;
        private String firstName;
        private String lastName;
        private String email;
        private String mobileNumber;
    }
    

    AuditAnnotationAspect.aj

    import org.aspectj.lang.reflect.FieldSignature;
    
    import java.lang.reflect.Field;
    
    public aspect FieldAuditAspect {
    
    pointcut auditField(Object t, Object value): set(@(*.AopAudit) * *.*) && args(value) && target(t);
    
    pointcut auditType(Object t, Object value): set(* @(*.AopAudit) *.*) && args(value) && target(t);
    
    before(Object target, Object newValue): auditField(target, newValue) || auditType(target, newValue) {
            FieldSignature sig = (FieldSignature) thisJoinPoint.getSignature();
            Field field = sig.getField(); 
            field.setAccessible(true);
    
            Object oldValue;
            try
            {
                oldValue = field.get(target);
            }
            catch (IllegalAccessException e)
            {
                throw new RuntimeException("Failed to create audit Action", e);
            }
    
            Action a = new Action();
            a.setFieldName(sig.getName());
            a.setOldValue(oldValue == null ? null : oldValue.toString());
            a.setNewValue(newValue == null ? null : newValue.toString());
        }
    
    }
    

    This is AspectJ aspect that define auditField pointcut to capture field set operations and before logic to create Audit object.

    To enable AspectJ Compile Time Weaving you must do the following in case of Maven:

    pom.xml

    ...
    
    <dependencies>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
        </dependency>
    </dependencies>
    
    ...
    
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>aspectj-maven-plugin</artifactId>
            <version>1.6</version>
            <configuration>
                <showWeaveInfo>true</showWeaveInfo>
                <source>${java.source}</source>
                <target>${java.target}</target>
                <complianceLevel>${java.target}</complianceLevel>
                <encoding>UTF-8</encoding>
                <verbose>false</verbose>
                <XnoInline>false</XnoInline>
            </configuration>
            <executions>
                <execution>
                    <id>aspectj-compile</id>
                    <goals>
                        <goal>compile</goal>
                    </goals>
                </execution>
                <execution>
                    <id>aspectj-compile-test</id>
                    <goals>
                        <goal>test-compile</goal>
                    </goals>
                </execution>
            </executions>
            <dependencies>
                <dependency>
                    <groupId>org.aspectj</groupId>
                    <artifactId>aspectjrt</artifactId>
                    <version>${aspectj.version}</version>
                </dependency>
                <dependency>
                    <groupId>org.aspectj</groupId>
                    <artifactId>aspectjtools</artifactId>
                    <version>${aspectj.version}</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
    

    This Maven configuration enables AspectJ compiler that makes bytecode post processing of your classes.

    applicationContext.xml

    <bean class="AuditAnnotationAspect" factory-method="aspectOf"/>
    

    Also you may need to add aspect instance to Spring Application Context for dependency injection.

    UPD: Here is an example of such AspectJ project configuration