I have an auditing entity which defined like this:
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.io.Serializable;
import java.time.Instant;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
/**
* Base abstract class for entities which will hold definitions for created, last modified, created by,
* last modified by attributes.
*/
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class AbstractAuditingEntity implements Serializable {
private static final long serialVersionUID = 1L;
@CreatedBy
@Column(name = "created_by", nullable = false, length = 50, updatable = false)
@JsonIgnore
private String createdBy;
@CreatedDate
@Column(name = "created_date", updatable = false)
@JsonIgnore
private Instant createdDate = Instant.now();
@LastModifiedBy
@Column(name = "last_modified_by", length = 50)
@JsonIgnore
private String lastModifiedBy;
@LastModifiedDate
@Column(name = "last_modified_date")
@JsonIgnore
private Instant lastModifiedDate = Instant.now();
public String getCreatedBy() {
return createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
public Instant getCreatedDate() {
return createdDate;
}
public void setCreatedDate(Instant createdDate) {
this.createdDate = createdDate;
}
public String getLastModifiedBy() {
return lastModifiedBy;
}
public void setLastModifiedBy(String lastModifiedBy) {
this.lastModifiedBy = lastModifiedBy;
}
public Instant getLastModifiedDate() {
return lastModifiedDate;
}
public void setLastModifiedDate(Instant lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate;
}
}
And this is the implementation:
import com.app.derin.uaa.config.Constants;
import java.util.Optional;
import org.springframework.data.domain.AuditorAware;
import org.springframework.stereotype.Component;
/**
* Implementation of {@link AuditorAware} based on Spring Security.
*/
@Component
public class SpringSecurityAuditorAware implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
return Optional.of(SecurityUtils.getCurrentUserName().orElse(Constants.SYSTEM_ACCOUNT));
}
}
But I also need to store Hostname of request ip ( if it can be acquired from browser/dns if not it can be null) and request ip.
I found some examples and i changed my class to this:
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.hibernate.annotations.ColumnDefault;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class AbstractAuditingEntity<T> implements Serializable {
@CreatedDate
@JsonIgnore
@Column(name = "created_date", updatable = false)
private Instant createdDate = Instant.now();
@CreatedBy
@JsonIgnore
@Column(name = "created_by", updatable = false)
@Embedded
private T createdBy;
@LastModifiedDate
@Column(name = "modified_date")
@JsonIgnore
private Instant modifiedDate = Instant.now();;
@LastModifiedBy
@Column(name = "modified_by")
@JsonIgnore
@Embedded
private T modifiedBy;
public Instant getCreatedDate() {
return createdDate;
}
public void setCreatedDate(Instant createdDate) {
this.createdDate = createdDate;
}
public T getCreatedBy() {
return createdBy;
}
public void setCreatedBy(T createdBy) {
this.createdBy = createdBy;
}
public Instant getModifiedDate() {
return modifiedDate;
}
public void setModifiedDate(Instant modifiedDate) {
this.modifiedDate = modifiedDate;
}
public T getModifiedBy() {
return modifiedBy;
}
public void setModifiedBy(T modifiedBy) {
this.modifiedBy = modifiedBy;
}
}
And my implementation to this:
import com.app.derin.configuration.config.Constants;
import java.util.Optional;
import com.app.derin.configuration.ext.AuditorDetails;
import org.springframework.data.domain.AuditorAware;
import org.springframework.stereotype.Component;
/**
* Implementation of {@link AuditorAware} based on Spring Security.
*/
@Component
public class SpringSecurityAuditorAware implements AuditorAware<AuditorDetails> {
@Override
public Optional<AuditorDetails> getCurrentAuditor() {
AuditorDetails currentAuditor = new AuditorDetails();
currentAuditor.setLoggedUser(SecurityUtils.getCurrentUserLogin().orElse(Constants.SYSTEM_ACCOUNT));
currentAuditor.setHostIp("ip");
return Optional.of(currentAuditor);
}
}
But if I am right hibernate doesn't support generic types. I am getting this error:
[ERROR] Error setting up or running Liquibase:
[ERROR] org.hibernate.AnnotationException: Property com.app.derin.configuration.ext.extendedFields.AbstractAuditingEntity.createdBy has an unbound type and no explicit target entity. Resolve this Generic usage issue or set an explicit targe
t attribute (eg @OneToMany(target=) or use an explicit @Type
Is there any way to achieve this?
I found solution and here is how did:
After a lot of googling i found that my problem is with hibernate annotation. I am trying to send Datatype that hibernate doesn't know about. So I changed my class to custom type for hibernate.
More info you can check this link. It helped me.
My AuditorDetails class:
import java.io.Serializable;
import java.util.Objects;
public final class AuditorDetails implements Serializable {
private final String loggedUser;
private final String hostName;
private final String hostIp;
public AuditorDetails(String loggedUser, String hostName, String hostIp) {
this.loggedUser = loggedUser;
this.hostName = hostName;
this.hostIp = hostIp;
}
public String getLoggedUser() {
return loggedUser;
}
public String getHostName() {
return hostName;
}
public String getHostIp() {
return hostIp;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AuditorDetails that = (AuditorDetails) o;
return Objects.equals(loggedUser, that.loggedUser) &&
Objects.equals(hostName, that.hostName) &&
Objects.equals(hostIp, that.hostIp);
}
@Override
public int hashCode() {
return Objects.hash(loggedUser, hostName, hostIp);
}
}
AuditorDetailsType class:
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.usertype.UserType;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Objects;
public class AuditorDetailsType implements UserType {
@Override
public int[] sqlTypes() {
return new int[]{Types.VARCHAR, Types.VARCHAR, Types.VARCHAR};
}
@Override
public Class returnedClass() {
return AuditorDetails.class;
}
@Override
public boolean equals(Object o, Object o1) throws HibernateException {
if(o == o1)
return true;
if (Objects.isNull(o) || Objects.isNull(o1))
return false;
return o.equals(o1);
}
@Override
public int hashCode(Object o) throws HibernateException {
return o.hashCode();
}
@Override
public Object nullSafeGet(ResultSet resultSet, String[] strings, SharedSessionContractImplementor sharedSessionContractImplementor, Object o) throws HibernateException, SQLException {
String loggedUser = resultSet.getString(strings[0]);
if(resultSet.wasNull())
return null;
String hostName = resultSet.getString(strings[1]);
String hostIp = resultSet.getString(strings[2]);
AuditorDetails currentAuditor = new AuditorDetails(loggedUser, hostName, hostIp);
return currentAuditor;
}
@Override
public void nullSafeSet(PreparedStatement preparedStatement, Object o, int i, SharedSessionContractImplementor sharedSessionContractImplementor) throws HibernateException, SQLException {
if (Objects.isNull(o)){
preparedStatement.setNull(i,Types.VARCHAR);
}
else {
AuditorDetails currentAuditor = (AuditorDetails) o;
preparedStatement.setString(i,currentAuditor.getLoggedUser());
preparedStatement.setString(i+1,currentAuditor.getHostName());
preparedStatement.setString(i+2,currentAuditor.getHostIp());
}
}
@Override
public Object deepCopy(Object o) throws HibernateException {
if (Objects.isNull(o))
return null;
AuditorDetails currentAuditor = (AuditorDetails) o;
return new AuditorDetails(currentAuditor.getLoggedUser(), currentAuditor.getHostName(), currentAuditor.getHostIp());
}
@Override
public boolean isMutable() {
return false;
}
@Override
public Serializable disassemble(Object o) throws HibernateException {
return (Serializable) o;
}
@Override
public Object assemble(Serializable serializable, Object o) throws HibernateException {
return serializable;
}
@Override
public Object replace(Object o, Object o1, Object o2) throws HibernateException {
return o;
}
}
AbstractAuditingEntity:
import com.app.derin.uaa.ext.AuditorDetails;
import com.app.derin.uaa.ext.AuditorDetailsType;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.Columns;
import org.hibernate.annotations.TypeDef;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.Instant;
@TypeDef(name = "AuditorDetails",
typeClass = AuditorDetailsType.class,
defaultForType = AuditorDetails.class)
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class AbstractAuditingEntity{
@CreatedDate
@JsonIgnore
@Column(name = "created_date", updatable = false)
private Instant createdDate = Instant.now();
@CreatedBy
@Columns(columns = {@Column(name = "created_by", updatable = false),
@Column(name = "created_host_name", updatable = false),
@Column(name = "created_host_ip", updatable = false)})
private AuditorDetails createdBy;
@LastModifiedDate
@Column(name = "modified_date")
@JsonIgnore
private Instant modifiedDate = Instant.now();
@LastModifiedBy
@Columns(columns = {@Column(name = "modified_by"),
@Column(name = "modified_host_name"),
@Column(name = "modified_host_ip")})
private AuditorDetails modifiedBy;
@Column(name = "row_status")
@JsonIgnore
@ColumnDefault("1")
private Integer rowStatus = 1;
protected AbstractAuditingEntity() {
}
public AuditorDetails getModifiedBy() {
return modifiedBy;
}
public Instant getCreatedDate() {
return createdDate;
}
public void setCreatedDate(Instant createdDate) {
this.createdDate = createdDate;
}
public AuditorDetails getCreatedBy() {
return createdBy;
}
public void setCreatedBy(AuditorDetails createdBy) {
this.createdBy = createdBy;
}
public Instant getModifiedDate() {
return modifiedDate;
}
public void setModifiedDate(Instant modifiedDate) {
this.modifiedDate = modifiedDate;
}
public Integer getRowStatus() {
return rowStatus;
}
public void setRowStatus(Integer rowStatus) {
this.rowStatus = rowStatus;
}
}
SpringSecurityAuditorAware:
import com.app.derin.uaa.config.Constants;
import java.util.Optional;
import com.app.derin.uaa.ext.AuditorDetails;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.AuditorAware;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* Implementation of {@link AuditorAware} based on Spring Security.
*/
@Component
public class SpringSecurityAuditorAware implements AuditorAware<AuditorDetails> {
private final Logger log = LoggerFactory.getLogger(SpringSecurityAuditorAware.class);
@Override
public Optional<AuditorDetails> getCurrentAuditor() {
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = sra.getRequest();
if(request != null) {
String hostIp = getClientIpAddress(request);
String hostName = "";
AuditorDetails currentAuditor = new AuditorDetails(SecurityUtils.getCurrentUserName().orElse(Constants.SYSTEM_ACCOUNT),
hostName, hostIp);
return Optional.of(currentAuditor);
}
return Optional.of(currentAuditor);
}
private static final String[] IP_HEADER_CANDIDATES = {
"X-Forwarded-For",
"Proxy-Client-IP",
"WL-Proxy-Client-IP",
"HTTP_X_FORWARDED_FOR",
"HTTP_X_FORWARDED",
"HTTP_X_CLUSTER_CLIENT_IP",
"HTTP_CLIENT_IP",
"HTTP_FORWARDED_FOR",
"HTTP_FORWARDED",
"HTTP_VIA",
"REMOTE_ADDR" };
public String getClientIpAddress(HttpServletRequest request) {
for (String header : IP_HEADER_CANDIDATES) {
String ip = request.getHeader(header);
log.info("ip : {}", ip);
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
return ip;
}
}
return request.getRemoteAddr();
}
}