Skip to content

Instantly share code, notes, and snippets.

@tanapoln
Created October 5, 2016 04:45
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tanapoln/af4b4a40e5472f0c8e9625d31f7d2153 to your computer and use it in GitHub Desktop.
Save tanapoln/af4b4a40e5472f0c8e9625d31f7d2153 to your computer and use it in GitHub Desktop.
package org.springframework.data.envers.repository.support;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import org.hibernate.envers.AuditReader;
import org.hibernate.envers.AuditReaderFactory;
import org.hibernate.envers.DefaultRevisionEntity;
import org.hibernate.envers.RevisionNumber;
import org.hibernate.envers.RevisionTimestamp;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.history.AnnotationRevisionMetadata;
import org.springframework.data.history.Revision;
import org.springframework.data.history.RevisionMetadata;
import org.springframework.data.history.Revisions;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.QueryDslJpaRepository;
import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.repository.history.support.RevisionEntityInformation;
import org.springframework.util.Assert;
/**
* @author Tanapol Nearunchorn
*/
public class EnversQueryDslRepositoryImpl<T, ID extends Serializable, N extends Number & Comparable<N>>
extends QueryDslJpaRepository<T, ID>
implements EnversRevisionRepository<T, ID, N> {
private final EntityInformation<T, ?> entityInformation;
private final RevisionEntityInformation revisionEntityInformation;
private final EntityManager entityManager;
/**
* Constructor.
*
* @param entityInformation
* entity information
* @param revisionEntityInformation
* revision entity information
* @param entityManager
* entity manager
*/
public EnversQueryDslRepositoryImpl(JpaEntityInformation<T, ID> entityInformation,
RevisionEntityInformation revisionEntityInformation,
EntityManager entityManager) {
super(entityInformation, entityManager);
Assert.notNull(revisionEntityInformation);
this.entityInformation = entityInformation;
this.revisionEntityInformation = revisionEntityInformation;
this.entityManager = entityManager;
}
/**
* Another Constructor.
*
* @param entityInformation
* entity information
* @param entityManager
* entity manager
* @param revisionEntityInformation
* revision entity information
* @param resolver
* resolver
*/
public EnversQueryDslRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager,
RevisionEntityInformation revisionEntityInformation,
EntityPathResolver resolver) {
super(entityInformation, entityManager, resolver);
Assert.notNull(revisionEntityInformation);
this.entityInformation = entityInformation;
this.entityManager = entityManager;
this.revisionEntityInformation = revisionEntityInformation;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.history.RevisionRepository#findLastChangeRevision(java.io.Serializable)
*/
@SuppressWarnings("unchecked")
public Revision<N, T> findLastChangeRevision(ID id) {
Class<T> type = entityInformation.getJavaType();
AuditReader reader = AuditReaderFactory.get(entityManager);
List<Number> revisions = reader.getRevisions(type, id);
if (revisions.isEmpty()) {
return null;
}
N latestRevision = (N) revisions.get(revisions.size() - 1);
Class<?> revisionEntityClass = revisionEntityInformation.getRevisionEntityClass();
Object revisionEntity = reader.findRevision(revisionEntityClass, latestRevision);
RevisionMetadata<N> metadata = (RevisionMetadata<N>) getRevisionMetadata(revisionEntity);
return new Revision<N, T>(metadata, reader.find(type, id, latestRevision));
}
/*
* (non-Javadoc)
* @see org.springframework.data.envers.repository.support.EnversRevisionRepository#findRevision(java.io.Serializable, java.lang.Number)
*/
@Override
public Revision<N, T> findRevision(ID id, N revisionNumber) {
Assert.notNull(id, "Identifier must not be null!");
Assert.notNull(revisionNumber, "Revision number must not be null!");
return getEntityForRevision(revisionNumber, id, AuditReaderFactory.get(entityManager));
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.history.RevisionRepository#findRevisions(java.io.Serializable)
*/
@SuppressWarnings("unchecked")
public Revisions<N, T> findRevisions(ID id) {
Class<T> type = entityInformation.getJavaType();
AuditReader reader = AuditReaderFactory.get(entityManager);
List<? extends Number> revisionNumbers = reader.getRevisions(type, id);
return revisionNumbers.isEmpty() ? new Revisions<N, T>(Collections.EMPTY_LIST) : getEntitiesForRevisions(
(List<N>) revisionNumbers, id, reader);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.history.RevisionRepository#findRevisions(java.io.Serializable, org.springframework.data.domain.Pageable)
*/
@SuppressWarnings("unchecked")
public Page<Revision<N, T>> findRevisions(ID id, Pageable pageable) {
Class<T> type = entityInformation.getJavaType();
AuditReader reader = AuditReaderFactory.get(entityManager);
List<Number> revisionNumbers = reader.getRevisions(type, id);
if (pageable.getOffset() > revisionNumbers.size()) {
return new PageImpl<Revision<N, T>>(Collections.<Revision<N, T>>emptyList(), pageable, 0);
}
int upperBound = pageable.getOffset() + pageable.getPageSize();
upperBound = upperBound > revisionNumbers.size() ? revisionNumbers.size() : upperBound;
List<? extends Number> subList = revisionNumbers.subList(pageable.getOffset(), upperBound);
Revisions<N, T> revisions = getEntitiesForRevisions((List<N>) subList, id, reader);
return new PageImpl<Revision<N, T>>(revisions.getContent(), pageable, revisionNumbers.size());
}
/**
* Returns the entities in the given revisions for the entitiy with the given id.
*
* @param revisionNumbers
* @param id
* @param reader
* @return
*/
@SuppressWarnings("unchecked")
private Revisions<N, T> getEntitiesForRevisions(List<N> revisionNumbers, ID id, AuditReader reader) {
Class<T> type = entityInformation.getJavaType();
Map<N, T> revisions = new HashMap<N, T>(revisionNumbers.size());
Class<?> revisionEntityClass = revisionEntityInformation.getRevisionEntityClass();
Map<Number, Object> revisionEntities = (Map<Number, Object>) reader.findRevisions(revisionEntityClass,
new HashSet<Number>(revisionNumbers));
for (Number number : revisionNumbers) {
revisions.put((N) number, reader.find(type, id, number));
}
return new Revisions<N, T>(toRevisions(revisions, revisionEntities));
}
/**
* Returns an entity in the given revision for the given entity-id.
*
* @param revisionNumber
* @param id
* @param reader
* @return
*/
@SuppressWarnings("unchecked")
private Revision<N, T> getEntityForRevision(N revisionNumber, ID id, AuditReader reader) {
Class<?> type = revisionEntityInformation.getRevisionEntityClass();
T revision = (T) reader.findRevision(type, revisionNumber);
Object entity = reader.find(entityInformation.getJavaType(), id, revisionNumber);
return new Revision<N, T>((RevisionMetadata<N>) getRevisionMetadata(revision), (T) entity);
}
@SuppressWarnings("unchecked")
private List<Revision<N, T>> toRevisions(Map<N, T> source, Map<Number, Object> revisionEntities) {
List<Revision<N, T>> result = new ArrayList<Revision<N, T>>();
for (Map.Entry<N, T> revision : source.entrySet()) {
N revisionNumber = revision.getKey();
T entity = revision.getValue();
RevisionMetadata<N> metadata = (RevisionMetadata<N>) getRevisionMetadata(
revisionEntities.get(revisionNumber));
result.add(new Revision<N, T>(metadata, entity));
}
Collections.sort(result);
return Collections.unmodifiableList(result);
}
/**
* Returns the {@link RevisionMetadata} wrapper depending on the type of the given object.
*
* @param object
* @return
*/
private RevisionMetadata<?> getRevisionMetadata(Object object) {
if (object instanceof DefaultRevisionEntity) {
return new DefaultRevisionMetadata((DefaultRevisionEntity) object);
} else {
return new AnnotationRevisionMetadata<N>(object, RevisionNumber.class, RevisionTimestamp.class);
}
}
}
package org.springframework.data.envers.repository.support;
import java.io.Serializable;
import javax.persistence.EntityManager;
import org.hibernate.envers.DefaultRevisionEntity;
import org.springframework.core.GenericTypeResolver;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.data.repository.history.RevisionRepository;
import org.springframework.data.repository.history.support.RevisionEntityInformation;
/**
* @author Tanapol Nearunchorn
*/
public class EnversRevisionQueryDslRepositoryFactoryBean extends EnversRevisionRepositoryFactoryBean {
private Class<?> revisionEntityClass;
/**
* Configures the revision entity class. Will default to {@link DefaultRevisionEntity}.
*
* @param revisionEntityClass
*/
public void setRevisionEntityClass(Class<?> revisionEntityClass) {
this.revisionEntityClass = revisionEntityClass;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean#createRepositoryFactory(javax.persistence.EntityManager)
*/
@Override
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new RevisionRepositoryFactory(entityManager, revisionEntityClass);
}
/**
* Repository factory creating {@link RevisionRepository} instances.
*
* @author Oliver Gierke
*/
private static class RevisionRepositoryFactory<T, ID extends Serializable, N extends Number & Comparable<N>>
extends JpaRepositoryFactory {
private final RevisionEntityInformation revisionEntityInformation;
private final EntityManager entityManager;
/**
* Creates a new {@link RevisionRepositoryFactory} using the given {@link EntityManager} and revision entity
* class.
*
* @param entityManager
* must not be {@literal null}.
* @param revisionEntityClass
* can be {@literal null}, will default to {@link DefaultRevisionEntity}.
*/
public RevisionRepositoryFactory(EntityManager entityManager, Class<?> revisionEntityClass) {
super(entityManager);
this.entityManager = entityManager;
revisionEntityClass = revisionEntityClass == null ? DefaultRevisionEntity.class : revisionEntityClass;
this.revisionEntityInformation = DefaultRevisionEntity.class.equals(
revisionEntityClass) ? new DefaultRevisionEntityInformation()
: new ReflectionRevisionEntityInformation(revisionEntityClass);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.support.JpaRepositoryFactory#getTargetRepository(org.springframework.data.repository.core.RepositoryMetadata, javax.persistence.EntityManager)
*/
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
protected EnversQueryDslRepositoryImpl getTargetRepository(RepositoryInformation information) {
JpaEntityInformation<T, ID> entityInformation = (JpaEntityInformation<T, ID>) getEntityInformation(
information.getDomainType());
return new EnversQueryDslRepositoryImpl<T, ID, N>(entityInformation, revisionEntityInformation, entityManager);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.support.JpaRepositoryFactory#getRepositoryBaseClass(org.springframework.data.repository.core.RepositoryMetadata)
*/
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return EnversQueryDslRepositoryImpl.class;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getRepository(java.lang.Class, java.lang.Object)
*/
@Override
public <T> T getRepository(Class<T> repositoryInterface, Object customImplementation) {
if (RevisionRepository.class.isAssignableFrom(repositoryInterface)) {
Class<?>[] typeArguments = GenericTypeResolver.resolveTypeArguments(repositoryInterface,
RevisionRepository.class);
Class<?> revisionNumberType = typeArguments[2];
if (!revisionEntityInformation.getRevisionNumberType().equals(revisionNumberType)) {
throw new IllegalStateException(String.format(
"Configured a revision entity type of %s with a revision type of %s "
+ "but the repository interface is typed to a revision type of %s!",
repositoryInterface,
revisionEntityInformation.getRevisionNumberType(), revisionNumberType));
}
}
return super.getRepository(repositoryInterface, customImplementation);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment