Using Envers
authorGustavo Martin Morcuende <gu.martinm@gmail.com>
Mon, 18 Jul 2016 21:01:43 +0000 (23:01 +0200)
committerGustavo Martin Morcuende <gu.martinm@gmail.com>
Mon, 18 Jul 2016 21:01:43 +0000 (23:01 +0200)
SpringJava/JPA/src/main/java/de/spring/example/context/UsernameThreadContext.java [new file with mode: 0644]
SpringJava/JPA/src/main/java/de/spring/example/persistence/domain/Ad.java
SpringJava/JPA/src/main/java/de/spring/example/persistence/domain/AdDescription.java
SpringJava/JPA/src/main/java/de/spring/example/persistence/domain/audit/MyCustomRevision.java [new file with mode: 0644]
SpringJava/JPA/src/main/java/de/spring/example/persistence/domain/audit/MyCustomRevisionListener.java [new file with mode: 0644]
SpringJava/JPA/src/main/java/de/spring/example/rest/handler/UsernameHandler.java [new file with mode: 0644]
SpringJava/JPA/src/main/resources/spring-configuration/jpa-configuration.xml
SpringJava/JPA/src/main/resources/spring-configuration/spring-configuration.xml

diff --git a/SpringJava/JPA/src/main/java/de/spring/example/context/UsernameThreadContext.java b/SpringJava/JPA/src/main/java/de/spring/example/context/UsernameThreadContext.java
new file mode 100644 (file)
index 0000000..4d698ee
--- /dev/null
@@ -0,0 +1,26 @@
+package de.spring.example.context;
+
+import javax.inject.Named;
+
+import org.springframework.util.Assert;
+
+@Named("userNameThreadContext")
+public class UsernameThreadContext {
+       public static final String USERNAME_HEADER = "USERNAME";
+       
+       private final ThreadLocal<String> contextHolder = new ThreadLocal<>();
+       
+       public void setUsername(String username) {
+               Assert.notNull(username);
+               
+               contextHolder.set(username);
+       }
+       
+       public String getUsername() {
+               return contextHolder.get();
+       }
+       
+       public void clearUsername() {
+               contextHolder.remove();
+       }
+}
index 7d90e0c..0f91253 100644 (file)
@@ -23,6 +23,8 @@ import javax.validation.constraints.Max;
 import javax.validation.constraints.NotNull;
 import javax.validation.constraints.Size;
 
+import org.hibernate.envers.Audited;
+
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonIdentityInfo;
 import com.fasterxml.jackson.annotation.ObjectIdGenerators;
@@ -30,6 +32,7 @@ import com.fasterxml.jackson.annotation.ObjectIdGenerators;
 import de.spring.example.persistence.converters.OffsetDateTimeAttributeConverter;
 
 @Entity
+@Audited(withModifiedFlag=true)
 @Table(name="ad", schema="mybatis_example")
 @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="jsonId")
 // 1. Named query is JPL. It is portable.
index b933c42..2142b39 100644 (file)
@@ -16,10 +16,13 @@ import javax.validation.constraints.Max;
 import javax.validation.constraints.NotNull;
 import javax.validation.constraints.Size;
 
+import org.hibernate.envers.Audited;
+
 import com.fasterxml.jackson.annotation.JsonIdentityInfo;
 import com.fasterxml.jackson.annotation.ObjectIdGenerators;
 
 @Entity
+@Audited
 @Table(name="ad_description", schema="mybatis_example")
 @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="jsonId")
 public class AdDescription implements Serializable {
diff --git a/SpringJava/JPA/src/main/java/de/spring/example/persistence/domain/audit/MyCustomRevision.java b/SpringJava/JPA/src/main/java/de/spring/example/persistence/domain/audit/MyCustomRevision.java
new file mode 100644 (file)
index 0000000..f8ebfe9
--- /dev/null
@@ -0,0 +1,35 @@
+package de.spring.example.persistence.domain.audit;
+
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import org.hibernate.envers.RevisionEntity;
+
+@Entity
+@RevisionEntity(MyCustomRevisionListener.class)
+@Table(name="CUSTOM_REVISION", schema="mybatis_example")
+public class MyCustomRevision {
+       private String username;
+       
+       // It will be used by JPA when filling the property fields with data coming from data base.
+       protected MyCustomRevision() {
+               
+       }
+       
+       // It will be used by my code (for example by Unit Tests)
+       public MyCustomRevision(String username) {
+               this.username = username;
+       }
+
+       /**
+        * WARNING: JPA REQUIRES GETTERS!!!
+        */
+       
+    public String getUsername() {
+       return username;
+    }
+    
+    public void setUsername(String username) {
+       this.username = username;
+    }
+}
diff --git a/SpringJava/JPA/src/main/java/de/spring/example/persistence/domain/audit/MyCustomRevisionListener.java b/SpringJava/JPA/src/main/java/de/spring/example/persistence/domain/audit/MyCustomRevisionListener.java
new file mode 100644 (file)
index 0000000..f4c63c7
--- /dev/null
@@ -0,0 +1,33 @@
+package de.spring.example.persistence.domain.audit;
+
+import org.hibernate.envers.RevisionListener;
+
+import de.spring.example.context.UsernameThreadContext;
+
+public class MyCustomRevisionListener implements RevisionListener {
+       private final UsernameThreadContext userNameThreadContext;
+       
+       public MyCustomRevisionListener(UsernameThreadContext userNameThreadContext) {
+               this.userNameThreadContext = userNameThreadContext;
+       }
+       
+       @Override
+       public void newRevision(Object revisionEntity) {
+               MyCustomRevision myCustomRevision = (MyCustomRevision) revisionEntity;
+               
+               final String username = getSafeUsername();
+               myCustomRevision.setUsername(username);
+               
+       }
+       
+       private String getSafeUsername() {
+               String userName = userNameThreadContext.getUserName();
+               
+               if (userName == null) {
+                       userName = "NO_USER";
+               }
+               
+               return userName;
+       }
+
+}
diff --git a/SpringJava/JPA/src/main/java/de/spring/example/rest/handler/UsernameHandler.java b/SpringJava/JPA/src/main/java/de/spring/example/rest/handler/UsernameHandler.java
new file mode 100644 (file)
index 0000000..9878f3d
--- /dev/null
@@ -0,0 +1,32 @@
+package de.spring.example.rest.handler;
+
+import javax.inject.Inject;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+import de.spring.example.context.UsernameThreadContext;
+
+public class UsernameHandler extends HandlerInterceptorAdapter {
+       private final UsernameThreadContext usernameThreadContext;
+       
+       @Inject
+       public UsernameHandler(UsernameThreadContext userNameThreadContext) {
+               this.usernameThreadContext = userNameThreadContext;
+       }
+       
+       @Override
+       public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
+               throws Exception {
+               final String userName = request.getHeader(UsernameThreadContext.USERNAME_HEADER);
+               
+               if (userName != null) {
+                       usernameThreadContext.setUsername(userName);
+               } else {
+                       usernameThreadContext.clearUsername();
+               }
+               
+               return super.preHandle(request, response, handler);
+       }
+}
index a80d040..b460a27 100644 (file)
                        </bean>\r
                </property>\r
                <property name="packagesToScan" value="de.spring.example.persistence.**.domain" />\r
+               <property name="jpaProperties">\r
+               <props>\r
+                       <!--\r
+               <prop key="hibernate.hbm2ddl.auto">create-drop</prop>\r
+               -->\r
+               <prop key="org.hibernate.envers.audit_table_suffix">_AUDITED</prop>\r
+               <prop key="org.hibernate.envers.revision_field_name">REVISION</prop>\r
+               <prop key="org.hibernate.envers.revision_type_field_name">REVISION_TYPE</prop>\r
+               <prop key="org.hibernate.envers.store_data_at_delete">true</prop>\r
+               <prop key="org.hibernate.envers.audit_strategy">org.hibernate.envers.strategy.ValidityAuditStrategy</prop>\r
+               <prop key="org.hibernate.envers.audit_strategy_validity_end_rev_field_name">REVISION_END</prop>\r
+               <prop key="org.hibernate.envers.audit_strategy_validity_store_revend_timestamp">true</prop>\r
+               <prop key="org.hibernate.envers.audit_strategy_validity_revend_timestamp_field_name">REVISION_END_TIMESTAMP</prop>\r
+               <prop key="org.hibernate.envers.modified_flag_suffix">_MODIFIED</prop>  \r
+               </props>\r
+       </property>\r
        </bean>\r
 \r
        <jpa:repositories entity-manager-factory-ref="entityManagerFactory"\r
index ae40307..505576c 100644 (file)
@@ -8,7 +8,7 @@
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context.xsd">
         
-       <context:component-scan base-package="de.spring.example.services"/>
+       <context:component-scan base-package="de.spring.example.services, de.spring.example.context"/>
        
        <context:property-placeholder location="classpath:jpa.properties" />