--- /dev/null
+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();
+ }
+}
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;
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.
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 {
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+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);
+ }
+}
</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
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" />