springspring-bootspring-aopspring-aspects

Storing old and new value in mongoDb using Spring AOP


I am new to Spring AOP world. I am trying to build the Spring boot logging using AOP. I have created the collection in MongoDB named "Audit report" which is storing old and updated value of object. I am using @Before and @After Annotations of Spring AOP for this. Suppose I have to edit the object, so its previous name and updated names should be entered into the database.

This is my Audit Report class,

@Data
@Document(collection="auditReport")
public class AuditReport extends AuditEntity {
    private String description;
    private String old_value;
    private String new_value;
}

This id entity class,

public class AuditEntity {
     @Field("createdDate")
     @CreatedDate
     private Date createdDate;

     @Field("createdBy")
     @CreatedBy
     private String createdBy;

     @Field("updatedDate")
     @LastModifiedDate
     private Date updatedDate;

     @Field("updatedBy")
     @LastModifiedBy
     private String updatedBy;

     @Field("version")
     @Version
     private Long version;
}

This is my aspect class,

@Before("execution(* com.controller.DeliveryMethodsController.edit*(..)) && target(instance) && args(deliveryMethodObject) ")
    public void before(JoinPoint joinPoint,Object instance,DeliveryMethods deliveryMethodObject) throws Exception {
        LOGGER.info("Entering inside Aspect "+ joinPoint.getSignature().getName());

        Object[] signatureArgs = joinPoint.getArgs();
        LOGGER.info("Input: ",signatureArgs.toString());
        String methodName = joinPoint.getSignature().getName();
        Object previous = instance.getClass().getMethod(methodName.replaceFirst("edit", "get")).invoke(deliveryMethodObject);

        AuditReport report = new AuditReport();
        String prop = joinPoint.getSignature().getName().substring(4);
        Object target = joinPoint.getTarget();
        Object before = target.getClass().getMethod("get" + prop).invoke(deliveryMethodObject);
        Object now = joinPoint.getArgs()[0];
        System.out.println(prop + " changed from " + before + " to " + now);
        report.setDescription(joinPoint.getSignature().getName());
        System.out.println("DM object "+deliveryMethodObject);
        System.out.println(" report "+report);
        audiRepo.save(report);
    }

When I am trying to edit the DeliveryMethod, I am fetching previous record using get method with same parameter that is Id. But it gives me error like,

java.lang.NoSuchMethodException: com.exelatech.printshop.controller.DeliveryMethodsController.getDeliveryMethodById()
    at java.lang.Class.getMethod(Class.java:1778) ~[na:1.8.0_25]
    at com.exelatech.printshop.auditDetails.PrintshopAspect.before(PrintshopAspect.java:67) ~[classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_25]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_25]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_25]
    at java.lang.reflect.Method.invoke(Method.java:483) ~[na:1.8.0_25]
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:629) ~[spring-aop-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:611) ~[spring-aop-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.aop.aspectj.AspectJMethodBeforeAdvice.before(AspectJMethodBeforeAdvice.java:43) ~[spring-aop-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:51) ~[spring-aop-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:168) ~[spring-aop-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:62) ~[spring-aop-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) ~[spring-aop-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673) ~[spring-aop-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at com.exelatech.printshop.controller.DeliveryMethodsController$$EnhancerBySpringCGLIB$$9478e58b.editDeliveryMethodById(<generated>) [classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_25]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_25]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_25]
    at java.lang.reflect.Method.invoke(Method.java:483) ~[na:1.8.0_25]
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) [spring-web-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133) [spring-web-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97) [spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) [spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) [spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) [spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967) [spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901) [spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) [spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872) [spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:661) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) [spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:742) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) [tomcat-embed-websocket-8.5.16.jar:8.5.16]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55) [spring-boot-1.5.6.RELEASE.jar:1.5.6.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at com.exelatech.printshop.SimpleCORSFilter.doFilter(SimpleCORSFilter.java:38) [classes/:na]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:110) [spring-boot-actuator-1.5.6.RELEASE.jar:1.5.6.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) [spring-web-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:105) [spring-web-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81) [spring-web-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) [spring-web-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:106) [spring-boot-actuator-1.5.6.RELEASE.jar:1.5.6.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:478) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:80) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:799) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1455) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_25]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_25]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.16.jar:8.5.16]
    at java.lang.Thread.run(Thread.java:745) [na:1.8.0_25]

This is my Controller Class,

@RestController
@RequestMapping(value="/deliveryMethods")
public class DeliveryMethodsController {

    private static final Logger logger = LoggerFactory.getLogger(DeliveryMethodsController.class);

    @Autowired
    private DeliveryMethodService deliveryMethodService;

    @Autowired
    public DeliveryMethodsController(DeliveryMethodService deliveryMethodService){
        super();
    }

    @RequestMapping(value = "/createDeliveryMethod", method = RequestMethod.POST, consumes = "application/json;charset=UTF-8",produces = "application/json;charset=UTF-8")
    public ResponseEntity<BaseEntityResponse<DeliveryMethods>> createDeliveryMethod(@RequestBody DeliveryMethods deliveryMethodObject) {
        logger.debug("Entering inside create delivery Method", deliveryMethodObject);
        return deliveryMethodService.createDeliveryMethod(deliveryMethodObject);
    }

    @RequestMapping(value = "/getActiveDeliveryMethod", method = RequestMethod.POST, consumes = "application/json;charset=UTF-8",produces = "application/json;charset=UTF-8")
    public ResponseEntity<BaseEntityResponse<DeliveryMethods>> getActiveDeliveryMethod(@RequestBody DeliveryMethods deliveryMethodObject) {
        logger.debug("Entering inside getAll active delivery methods method", deliveryMethodObject);
        return deliveryMethodService.getActiveDeliveryMethod(deliveryMethodObject);
    }

    @RequestMapping(value = "/getDeliveryMethod", method = RequestMethod.POST, consumes = "application/json;charset=UTF-8",produces = "application/json;charset=UTF-8")
    public ResponseEntity<BaseEntityResponse<DeliveryMethods>> getDeliveryMethod(@RequestBody DeliveryMethods deliveryMethodObject) {
        logger.debug("Entering inside getAll delivery methods method", deliveryMethodObject);
        return deliveryMethodService.getDeliveryMethod(deliveryMethodObject);
    }

    @RequestMapping(value = "/getDeliveryMethodById", method = RequestMethod.POST, consumes = "application/json;charset=UTF-8",produces = "application/json;charset=UTF-8")
    public ResponseEntity<BaseEntityResponse<DeliveryMethods>> getDeliveryMethodById(@RequestBody DeliveryMethods deliveryMethodObject) {
        logger.debug("Entering inside get delivery methods by Id method", deliveryMethodObject);
        return deliveryMethodService.getDeliveryMethodById  (deliveryMethodObject);
    }

    @RequestMapping(value = "/editDeliveryMethodById", method = RequestMethod.POST, consumes = "application/json;charset=UTF-8",produces = "application/json;charset=UTF-8")
    public ResponseEntity<BaseEntityResponse<DeliveryMethods>> editDeliveryMethodById(@RequestBody DeliveryMethods deliveryMethodObject) {
        logger.debug("Entering inside edit delivery methods by Id method", deliveryMethodObject);
        return deliveryMethodService.editDeliveryMethodById  (deliveryMethodObject);
    }

    @RequestMapping(value = "/deleteDeliveryMethod", method = RequestMethod.POST, consumes = "application/json;charset=UTF-8",produces = "application/json;charset=UTF-8")
    public ResponseEntity<BaseEntityResponse<DeliveryMethods>> deleteDeliveryMethod(@RequestBody DeliveryMethods deliveryMethodObject) {
        logger.debug("Entering inside delete delivery methods by Id method", deliveryMethodObject);
        return deliveryMethodService.deleteDeliveryMethod(deliveryMethodObject);
    }

    @RequestMapping(value = "/enableOrDisabledDeliveryMethod", method = RequestMethod.POST, consumes = "application/json;charset=UTF-8",produces = "application/json;charset=UTF-8")
    public ResponseEntity<BaseEntityResponse<DeliveryMethods>> enableOrDisabledDeliveryMethod(@RequestBody DeliveryMethods deliveryMethodObject) {
        logger.debug("Entering inside enable or disable delivery methods by Id method", deliveryMethodObject);
        return deliveryMethodService.enableOrDisabledDeliveryMethod(deliveryMethodObject);
    }
}

This is Model Class,

public class DeliveryMethods extends AuditEntity implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    @Id
    @Field("methodId")
    private String methodId;

    @Field("deliveryMethodName")
    private String deliveryMethodName;

    @Field("displayLabel")
    private String displayLabel;

    @Field("serviceType")
    private String serviceType;

    @Field("courierService")
    private List<Object> courierService;

    @Field("address")
    private List<Object> address;


    @Field("isActive")
    private boolean isActive;

    @Field("deliveryAddress")
    private String deliveryAddress;


    @Field("isDeleted")
    private boolean isDeleted;

    @Field("selectedIncart")
    private boolean selectedIncart;

    @Field("createdOn")
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    private LocalDateTime createdOn;
}

But this method exist in my controller. Can anyone suggest me the solution for this?. Or Suggest me the alternative way to get old value while editing the Object. Thanks in advance.


Solution

  • It was hard to get this running as there are so many classes and annotations missing and there is no MCVE to clone from. Also, your code seems to retrieve the old value twice in different ways and store them in different variables before and previous. I removed the redundancy, renamed and reorganised the variables a bit in order to make it a little clearer which is what and changed logger usage to just print on the console (you can easily change it back). Also, you don't show the full aspect, so I have no idea where audiRepo comes from. I created lots of dummy classes and annotations to get everything running.

      @Before("execution(* com.controller.DeliveryMethodsController.edit*(..)) && target(instance) && args(deliveryMethods)")
      public void beforeAdvice(JoinPoint joinPoint, Object instance, DeliveryMethods deliveryMethods) throws Exception {
        System.out.println(joinPoint);
        String editMethodName = joinPoint.getSignature().getName();
        String getterMethodName = editMethodName.replaceFirst("edit", "get");
        Method getterMethod = instance.getClass().getMethod(getterMethodName, DeliveryMethods.class);
        Object oldValue = getterMethod.invoke(instance, deliveryMethods);
        String propertyName = editMethodName.substring(4);
    
        AuditReport report = new AuditReport();
        report.setDescription(editMethodName);
        report.setOld_value(oldValue.toString());
        report.setNew_value(deliveryMethods.toString());
        System.out.println("Report " + report);
        audiRepo.save(report);
      }
    

    For my dummy objects this prints:

    execution(ResponseEntity com.controller.DeliveryMethodsController.editDeliveryMethodById(DeliveryMethods))
    Entering inside get delivery methods by Id method = com.controller.DeliveryMethods@63961c42
    Report AuditReport [description=editDeliveryMethodById, old_value=com.controller.ResponseEntity@65b54208, new_value=com.controller.DeliveryMethods@63961c42]
    Entering inside edit delivery methods by Id method = com.controller.DeliveryMethods@63961c42
    

    For you the output looks a bit different, of course, because old and new value are not just dummy objects.

    Your main problem was reflection usage:

    Please also note that