Friday, September 27, 2013

log4j 2.0 configuration with Spring


Log4j 2.x has several improvements over Log4j 1.x to catch up with other log frameworks like slf4j, logback.

Spring reference documentation has details on how to configure logging including details on configuration for log4j.  Though the reference documentation mentions that the only logging dependency is JCL, there are few spring projects that use slf4j (for e.g., spring-data-* projects). And also many popular frameworks use slf4j.  Hence it is most likely that slf4j is used in any application directly or indirectly.

As per the spring reference documentation, to configure log4j with spring and slf4j, we need  four jars included as maven dependency viz., jcl-over-slf4j, slf4j-api, slf4j-log4j12, log4j.  The maven dependencies are specified as below.
 <dependency>  
      <groupId>org.slf4j</groupId>  
      <artifactId>jcl-over-slf4j</artifactId>  
      <version>${slf4j.version}</version>  
      <scope>runtime</scope>  
     </dependency>  
     <dependency>  
      <groupId>org.slf4j</groupId>  
      <artifactId>slf4j-api</artifactId>  
      <version>${slf4j.version}</version>  
      <scope>runtime</scope>  
     </dependency>  
     <dependency>  
      <groupId>org.slf4j</groupId>  
      <artifactId>slf4j-log4j12</artifactId>  
      <version>${slf4j.version}</version>  
      <scope>runtime</scope>  
     </dependency>  
     <dependency>  
      <groupId>log4j</groupId>  
      <artifactId>log4j</artifactId>  
      <version>1.2.14</version>  
      <scope>runtime</scope>  
     </dependency>  


However, log4j 2.x has an adapter for slf4j.  To achieve the same, log4j 2.x require to configure 3 jars as maven dependency.   The required jars are jcl-over-slf4j, log4j-slf4j-impl, log4j-core. The maven dependencies are as specified below.
                <dependency>  
                     <groupId>org.slf4j</groupId>  
                     <artifactId>jcl-over-slf4j</artifactId>  
                     <version>${slf4j.version}</version>  
                     <scope>runtime</scope>  
                </dependency>  
                <dependency>  
                     <groupId>org.apache.logging.log4j</groupId>  
                     <artifactId>log4j-slf4j-impl</artifactId>  
                     <version>${log4j.version}</version>  
                </dependency>  
                <dependency>  
                     <groupId>org.apache.logging.log4j</groupId>  
                     <artifactId>log4j-core</artifactId>  
                     <version>${log4j.version}</version>  
                </dependency>  

Saturday, June 22, 2013

Spring MongoDB Auditing and Extensions


In this blog I will explain how to do update of audit fields (create/modified) and version in MongoDB using spring-data-mongodb library. I would also show how to workaround some of the limitations. 


Basic

A Model can be made Auditable by one of the following ways.
  1. Model implementing org.springframework.data.domain.Auditable interface.
  2. use @CreatedBy, @LastModifiedBy, @CreatedDate, @LastModifiedDate annotations defined in package org.springframework.data.annotation
For maintaining version use @Version annotation.

To make it simple, create a base class as shown below  which all Model classes will extend.

 public class Audit implements Auditable<String,String> {  
      @Id  
      private String id;  
      @Version  
      private Long version;  
      private String createdBy;  
      private DateTime createdDate;  
      private String lastModifiedBy;  
      private DateTime lastModifiedDate;  
      @Override  
      public boolean isNew() {  
      /**Assuming the Id is generated by database. If not, then need to override in each model depending on how to identify if a Model is a new one or already saved Model.  
 **/  
           return id == null;  
      }  
 // remaining getters & setters.  
 }  
 public class MyModel extends Audit{  
   private String firstName;  
      private String lastName;  
 //getters & setters.  
 }  

In above, isNew() is crucial and used to differentiate if a model is new or already saved model. The auditing listeners will use this method to decide if created fields to be updated or not. The above implementation is on assumption that the Ids are generated by DB and hence it will be NULL for models created newly.

We also require to define beans which provide auditing principal and current time.

Auditing principal

The auditing principal can be obtained by implementing the interface org.springframework.data.domain.AuditorAware. A sample implementation is as below.

 public class MyAuditor implements AuditorAware<String> {  
      @Override  
      public String getCurrentAuditor() {  
 //If you are using spring-security, you may get this from SecurityContext.  
           return "System";  
      }  
 }  

Date Provider

To get the auditing time, we need an implemenation of interface org.springframework.data.auditing.DateTimeProvider. Spring provides a defulat implementation for current time is org.springframework.data.auditing.CurrentDateTimeProvider

Spring configuration to make all this work is defined as below.

 <mongo:auditing auditor-aware-ref="myAuditor" date-time-provider-ref="datTimeProvider"/>  
      <bean id="myAuditor" class="poorna.samples.mongo.audit.MyAuditor"/>  
      <bean id="datTimeProvider" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">  
           <property name="staticField" value="org.springframework.data.auditing.CurrentDateTimeProvider.INSTANCE"></property>  
      </bean>  
This configuration will create listener bean org.springframework.data.mongodb.core.mapping.event.AuditingEventListener which listens for BeforConvertEvent that is fired by MongoTemplate(MongoOperations) during save operations.

Gaps

  1. If due to some reason <mongo:auditing> is included multiple times in your application context, then spring creates multiple instances of AuditingEventListener i.e., the audit update logic gets executed multiple times. This happens as the spring NameSpaceHandler creates a different listener bean each time.  
  2. MongoTemplate fires BeforeConvert event only during save and insert operations i.e., the AuditEventListener and hence the audit fields are updated only if the whole entity is being saved (either by insert or update). But MongoTemplate provides many other methods like update*(**) and findAndModify(**) methods which will update the model, but not the audit and version.

Custom AuditEventListener

The first gap can be overcome by creating custom AuditEventListener and registering this bean instead of <mongo:auditing>. A Sample implementation is defined as below.
 public class MyAuditingEventListner implements  
           ApplicationListener<BeforeConvertEvent<Object>> {  
      private AuditorAware<String> auditorAware;  
      private DateTimeProvider dateTimeProvider;  
      public MyAuditingEventListner(AuditorAware<String> auditorAware,  
                DateTimeProvider dateTimeProvider) {  
           this.auditorAware = auditorAware;  
           this.dateTimeProvider = dateTimeProvider;  
      }  
      @Override  
      public void onApplicationEvent(BeforeConvertEvent<Object> event) {  
           Object obj = event.getSource();  
           if (Audit.class.isAssignableFrom(obj.getClass())) {  
                Audit entity = (Audit) obj;  
                if (entity.isNew()) {  
                     entity.setCreatedBy(auditorAware.getCurrentAuditor());  
                     entity.setCreatedDate(dateTimeProvider.getDateTime());  
                }  
                entity.setLastModifiedBy(auditorAware.getCurrentAuditor());  
                entity.setLastModifiedDate(dateTimeProvider.getDateTime());  
           }  
      }  
 }  
     

Following is updated bean configuration. Note that we no longer require <mongo:auditing>. As bean id is specified for MyAuditEventListener, the bean will be registered only once even if the file containing this bean is imported multiple times.

 <bean id="myAuditor" class="poorna.samples.mongo.audit.myAuditor" />  
      <bean id="dateTimeProvider"  
           class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">  
           <property name="staticField"  
                value="org.springframework.data.auditing.CurrentDateTimeProvider.INSTANCE"></property>  
      </bean>  
      <bean id="myAuditingEventListener"  
           class="poorna.samples.mongo.audit.MyAuditingEventListner">  
           <constructor-arg ref="myAuditor"></constructor-arg>  
           <constructor-arg ref="dateTimeProvider"></constructor-arg>  
      </bean>  


Aspects for Audit Update

The second gap can be overcome by writing some Aspects that inject the audit fields before invoking update methods of MongoTemplate. Following is implementation for Aspect.

 @Aspect  
 public class MyMongoAuditAspect {  
      @Autowired  
      private AuditorAware<String> auditorAware;  
      @Autowired  
      private DateTimeProvider dateTimeProvider;  
 @Before(value = "(execution(public * org.springframework.data.mongodb.core.MongoOperations.update*(..)) ||"  
                + "execution(public * org.springframework.data.mongodb.core.MongoOperations.findAndModify(..))) && args(query,update,.. )")  
      public void setAuditFields(Query query, Update update) {  
           if (update != null) {  
                update.inc(“version”, 1);  
                update.set(“lastModifiedBy”, auditorAware.getCurrentAuditor())  
                     .set(“lastModifiedDate”, dateTimeProvider.getDateTime());  
           }  
      }  
 }  



Note the following from above Aspect code.

  • The Aspect works on MongoOperations interface and hence on MongoTemplate.
  • PointCut expression specifies to work on all update and findAndModify methods.
  • This is @Before aspect and updates Update object with auditing fields.
  • The same dateTimeProvider and myAuditor used in MyAuditingEventListener are used in MyMongoAuditAspect also.
The application context should be configured to register this aspect as bean and also configured for AOP using <aop:aspectj-autoproxy/>

Conclusion

I have tried to explain how to achieve auditing while using spring framework for mongodb. I have also explained with code snippets on how AOP can be used to fill some gaps. I have explained AOP for few methods. The same can be extended to work for upsert method and also for update method when upsert flag is true.