commit 5cf65015925a66a81312a7e8a0cdf0c1dcf580f6 Author: Oliver Gierke Date: Wed Mar 21 14:53:45 2012 +0100 Initial draft of Envers support for Spring Data JPA. Added an implementation of the Spring Data Commons historiography API based on Hibernate Envers. The implementation is a contribution of @hygl to a large degree. diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..179fe17 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.springBeans +.settings/ +target/ diff --git a/etc/eclipse-formatter.xml b/etc/eclipse-formatter.xml new file mode 100644 index 0000000..c744687 --- /dev/null +++ b/etc/eclipse-formatter.xml @@ -0,0 +1,291 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100755 index 0000000..17f983f --- /dev/null +++ b/pom.xml @@ -0,0 +1,140 @@ + + 4.0.0 + org.springframework.data + spring-data-envers + 0.1.0.BUILD-SNAPSHOT + + + 3.1.1.RELEASE + 1.1.0.BUILD-SNAPSHOT + UTF-8 + + + + + spring-releases + http://repo.springsource.org/libs-release + + false + + + + + + + spring-plugins + http://repo.springsource.org/libs-plugin + + false + + + + + + + Oliver Gierke + ogierke@vmware.com + SpringSource, a division of VMware + www.springsource.org + + + Philip Huegelmeyer + philip.huegelmeyer@ble.de + BLE + www.ble.de + + + + + https://github.com/SpringSource/spring-data-envers + + + + + + org.springframework.data + spring-data-jpa + ${spring.data.jpa.version} + + + + org.springframework + spring-orm + ${spring.version} + + + + + junit + junit + 4.8.2 + test + + + + org.springframework + spring-test + ${spring.version} + + + + + + org.slf4j + slf4j-api + 1.6.1 + runtime + + + org.slf4j + jcl-over-slf4j + 1.6.1 + runtime + + + log4j + log4j + 1.2.16 + + + org.slf4j + slf4j-log4j12 + 1.6.1 + + + + org.hibernate + hibernate-envers + 4.0.1.Final + + + com.h2database + h2 + 1.3.157 + + + cglib + cglib + 2.2 + + + joda-time + joda-time + 2.1 + + + + + + + org.apache.maven.plugins + maven-war-plugin + 2.1.1 + + false + + + + + \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..1a8275a --- /dev/null +++ b/readme.md @@ -0,0 +1,37 @@ +# Spring Data Envers # + +This project is an extension of the [Spring Data JPA](http://github.com/SpringSource/spring-data-jpa) project to allow access to entity revisions managed by Hibernate Envers. The sources mostly originate from a contribution of Philip Hügelmeyer [@hygl](https://github.com/hygl). + +The core feature of the module consists of an implementation of the `RevisionRepository` of Spring Data Commons. + +```java +public interface RevisionRepository> { + + Revision findLastChangeRevision(ID id); + + Revisions findRevisions(ID id); + + Page> findRevisions(ID id, Pageable pageable); +} +``` + +You can pull in this functionality to your repositories by simply additionally extending the interface just mentioned: + + +```java +interface PersonRepository extend RevisionRepository, CrudRepository { + + // Your query methods go here +} +``` + +To successfully activate the Spring Data Envers repository factory use the Spring Data JPA repositories namespace element's `factory-class` attribute: + +```xml + +``` + +# Contributing to the project + +If you're an Eclipse user make sure you activate automatic application of the formatter (located at `etc/eclipse-formatter.xml`) and activate automatic formatting and organizing imports on save. \ No newline at end of file diff --git a/src/main/java/org/springframework/data/envers/repository/support/DefaultRevisionEntityInformation.java b/src/main/java/org/springframework/data/envers/repository/support/DefaultRevisionEntityInformation.java new file mode 100644 index 0000000..6bd9ef5 --- /dev/null +++ b/src/main/java/org/springframework/data/envers/repository/support/DefaultRevisionEntityInformation.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.envers.repository.support; + +import org.hibernate.envers.DefaultRevisionEntity; +import org.springframework.data.repository.history.support.RevisionEntityInformation; + +/** + * {@link RevisionEntityInformation} for {@link DefaultRevisionEntity}. + * + * @author Oliver Gierke + */ +class DefaultRevisionEntityInformation implements RevisionEntityInformation { + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.history.support.RevisionEntityInformation#getRevisionNumberType() + */ + public Class getRevisionNumberType() { + return Integer.class; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.history.support.RevisionEntityInformation#isDefaultRevisionEntity() + */ + public boolean isDefaultRevisionEntity() { + return true; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.history.support.RevisionEntityInformation#getRevisionEntityClass() + */ + public Class getRevisionEntityClass() { + return DefaultRevisionEntity.class; + } +} diff --git a/src/main/java/org/springframework/data/envers/repository/support/DefaultRevisionMetadata.java b/src/main/java/org/springframework/data/envers/repository/support/DefaultRevisionMetadata.java new file mode 100755 index 0000000..b309be2 --- /dev/null +++ b/src/main/java/org/springframework/data/envers/repository/support/DefaultRevisionMetadata.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.envers.repository.support; + +import org.hibernate.envers.DefaultRevisionEntity; +import org.joda.time.DateTime; +import org.springframework.data.history.RevisionMetadata; +import org.springframework.util.Assert; + +/** + * {@link RevisionMetadata} working with a {@link DefaultRevisionEntity}. + * + * @author Oliver Gierke + * @author Philip Huegelmeyer + */ +public class DefaultRevisionMetadata implements RevisionMetadata { + + private final DefaultRevisionEntity entity; + + /** + * Creates a new {@link DefaultRevisionMetadata}. + * + * @param entity must not be {@literal null}. + */ + public DefaultRevisionMetadata(DefaultRevisionEntity entity) { + + Assert.notNull(entity); + this.entity = entity; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.history.RevisionMetadata#getRevisionNumber() + */ + public Integer getRevisionNumber() { + return entity.getId(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.history.RevisionMetadata#getRevisionDate() + */ + public DateTime getRevisionDate() { + return new DateTime(entity.getTimestamp()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.history.RevisionMetadata#getDelegate() + */ + @SuppressWarnings("unchecked") + public T getDelegate() { + return (T) entity; + } +} diff --git a/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepository.java b/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepository.java new file mode 100644 index 0000000..da9d483 --- /dev/null +++ b/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepository.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.envers.repository.support; + +import java.io.Serializable; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.NoRepositoryBean; +import org.springframework.data.repository.history.RevisionRepository; + +/** + * Convenience interface to allow pulling in {@link JpaRepository} and {@link RevisionRepository} functionality in one + * go. + * + * @author Oliver Gierke + */ +@NoRepositoryBean +public interface EnversRevisionRepository> extends + RevisionRepository, JpaRepository { + +} diff --git a/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryFactoryBean.java b/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryFactoryBean.java new file mode 100755 index 0000000..1f6da16 --- /dev/null +++ b/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryFactoryBean.java @@ -0,0 +1,130 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.envers.repository.support; + +import java.io.Serializable; + +import javax.persistence.EntityManager; + +import org.hibernate.envers.DefaultRevisionEntity; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.core.GenericTypeResolver; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.support.JpaEntityInformation; +import org.springframework.data.jpa.repository.support.JpaRepositoryFactory; +import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean; +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; + +/** + * {@link FactoryBean} creating {@link RevisionRepository} instances. + * + * @author Oliver Gierke + */ +public class EnversRevisionRepositoryFactoryBean extends + JpaRepositoryFactoryBean, Object, Serializable> { + + 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 extends JpaRepositoryFactory { + + private final RevisionEntityInformation revisionEntityInformation; + + /** + * 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); + 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 JpaRepository getTargetRepository(RepositoryMetadata metadata, + EntityManager entityManager) { + + JpaEntityInformation entityInformation = (JpaEntityInformation) getEntityInformation(metadata + .getDomainClass()); + return new EnversRevisionRepositoryImpl(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 EnversRevisionRepositoryImpl.class; + } + + /* (non-Javadoc) + * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getRepository(java.lang.Class, java.lang.Object) + */ + @Override + public T getRepository(Class 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); + } + } +} diff --git a/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryImpl.java b/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryImpl.java new file mode 100755 index 0000000..b75dc92 --- /dev/null +++ b/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryImpl.java @@ -0,0 +1,199 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +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 java.util.Map.Entry; + +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.SimpleJpaRepository; +import org.springframework.data.repository.core.EntityInformation; +import org.springframework.data.repository.history.RevisionRepository; +import org.springframework.data.repository.history.support.RevisionEntityInformation; +import org.springframework.util.Assert; + +/** + * Repository implementation using Hibernate Envers to implement revision specific query methods. + * + * @author Oliver Gierke + * @author Philipp Huegelmeyer + */ +public class EnversRevisionRepositoryImpl> extends + SimpleJpaRepository implements RevisionRepository { + + private final EntityInformation entityInformation; + private final RevisionEntityInformation revisionEntityInformation; + private final EntityManager entityManager; + + /** + * Creates a new {@link EnversRevisionRepositoryImpl} using the given {@link JpaEntityInformation}, + * {@link RevisionEntityInformation} and {@link EntityManager}. + * + * @param entityInformation must not be {@literal null}. + * @param revisionEntityInformation must not be {@literal null}. + * @param entityManager must not be {@literal null}. + */ + public EnversRevisionRepositoryImpl(JpaEntityInformation entityInformation, + RevisionEntityInformation revisionEntityInformation, EntityManager entityManager) { + + super(entityInformation, entityManager); + + Assert.notNull(revisionEntityInformation); + + this.entityInformation = entityInformation; + this.revisionEntityInformation = revisionEntityInformation; + this.entityManager = entityManager; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.history.RevisionRepository#findLastChangeRevision(java.io.Serializable) + */ + @SuppressWarnings("unchecked") + public Revision findLastChangeRevision(ID id) { + + Class type = entityInformation.getJavaType(); + AuditReader reader = AuditReaderFactory.get(entityManager); + + List 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 metadata = (RevisionMetadata) getRevisionMetadata(revisionEntity); + return new Revision(metadata, reader.find(type, id, latestRevision)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.history.RevisionRepository#findRevisions(java.io.Serializable) + */ + @SuppressWarnings("unchecked") + public Revisions findRevisions(ID id) { + + Class type = entityInformation.getJavaType(); + AuditReader reader = AuditReaderFactory.get(entityManager); + List revisionNumbers = reader.getRevisions(type, id); + + return getEntitiesForRevisions((List) 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> findRevisions(ID id, Pageable pageable) { + + Class type = entityInformation.getJavaType(); + AuditReader reader = AuditReaderFactory.get(entityManager); + List revisionNumbers = reader.getRevisions(type, id); + + if (pageable.getOffset() > revisionNumbers.size()) { + return new PageImpl>(Collections.> emptyList(), pageable, 0); + } + + int upperBound = pageable.getOffset() + pageable.getPageSize(); + upperBound = upperBound > revisionNumbers.size() ? revisionNumbers.size() : upperBound; + + List subList = revisionNumbers.subList(pageable.getOffset(), upperBound); + Revisions revisions = getEntitiesForRevisions((List) subList, id, reader); + + return new PageImpl>(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 getEntitiesForRevisions(List revisionNumbers, ID id, AuditReader reader) { + + Class type = entityInformation.getJavaType(); + Map revisions = new HashMap(revisionNumbers.size()); + + Class revisionEntityClass = revisionEntityInformation.getRevisionEntityClass(); + Map revisionEntities = (Map) reader.findRevisions(revisionEntityClass, + new HashSet(revisionNumbers)); + + for (Number number : revisionNumbers) { + revisions.put((N) number, reader.find(type, id, number)); + } + + return new Revisions(toRevisions(revisions, revisionEntities)); + } + + @SuppressWarnings("unchecked") + private List> toRevisions(Map source, Map revisionEntities) { + + List> result = new ArrayList>(); + + for (Entry revision : source.entrySet()) { + + N revisionNumber = revision.getKey(); + T entity = revision.getValue(); + RevisionMetadata metadata = (RevisionMetadata) getRevisionMetadata(revisionEntities.get(revisionNumber)); + result.add(new Revision(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(object, RevisionNumber.class, RevisionTimestamp.class); + } + } +} diff --git a/src/main/java/org/springframework/data/envers/repository/support/ReflectionRevisionEntityInformation.java b/src/main/java/org/springframework/data/envers/repository/support/ReflectionRevisionEntityInformation.java new file mode 100644 index 0000000..a36fc5f --- /dev/null +++ b/src/main/java/org/springframework/data/envers/repository/support/ReflectionRevisionEntityInformation.java @@ -0,0 +1,75 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.envers.repository.support; + +import org.hibernate.envers.RevisionNumber; +import org.springframework.data.repository.history.support.RevisionEntityInformation; +import org.springframework.data.util.AnnotationDetectionFieldCallback; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; + +/** + * {@link RevisionEntityInformation} that uses reflection to inspect a property annotated with {@link RevisionNumber} to + * find out about the revision number type. + * + * @author Oliver Gierke + */ +public class ReflectionRevisionEntityInformation implements RevisionEntityInformation { + + private final Class revisionEntityClass; + private final Class revisionNumberType; + + /** + * Creates a new {@link ReflectionRevisionEntityInformation} inspecting the given revision entity class. + * + * @param revisionEntityClass must not be {@literal null}. + */ + public ReflectionRevisionEntityInformation(Class revisionEntityClass) { + + Assert.notNull(revisionEntityClass); + + AnnotationDetectionFieldCallback fieldCallback = new AnnotationDetectionFieldCallback(RevisionNumber.class); + ReflectionUtils.doWithFields(revisionEntityClass, fieldCallback); + + this.revisionNumberType = fieldCallback.getType(); + this.revisionEntityClass = revisionEntityClass; + + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.history.support.RevisionEntityInformation#getRevisionNumberType() + */ + public Class getRevisionNumberType() { + return revisionNumberType; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.history.support.RevisionEntityInformation#isDefaultRevisionEntity() + */ + public boolean isDefaultRevisionEntity() { + return false; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.history.support.RevisionEntityInformation#getRevisionEntityClass() + */ + public Class getRevisionEntityClass() { + return revisionEntityClass; + } +} diff --git a/src/test/java/org/springframework/data/envers/Config.java b/src/test/java/org/springframework/data/envers/Config.java new file mode 100755 index 0000000..ac55029 --- /dev/null +++ b/src/test/java/org/springframework/data/envers/Config.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.envers; + +import java.sql.SQLException; +import java.util.Collections; +import java.util.Map; + +import javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; + +import org.hibernate.envers.strategy.ValidityAuditStrategy; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.Database; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; +import org.springframework.transaction.PlatformTransactionManager; + +/** + * Spring JavaConfig configuration for general infrastructure. + * + * @author Oliver Gierke + */ +@Configuration +public class Config { + + @Bean + public DataSource dataSource() throws SQLException { + return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build(); + } + + @Bean + public PlatformTransactionManager transactionManager() throws SQLException { + + EntityManagerFactory entityManagerFactory = entityManagerFactoryBean().getObject(); + return new JpaTransactionManager(entityManagerFactory); + } + + @Bean + public AbstractEntityManagerFactoryBean entityManagerFactoryBean() throws SQLException { + + HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter(); + jpaVendorAdapter.setDatabase(Database.H2); + jpaVendorAdapter.setGenerateDdl(true); + + LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean(); + bean.setJpaVendorAdapter(jpaVendorAdapter); + bean.setPackagesToScan(Config.class.getPackage().getName()); + bean.setDataSource(dataSource()); + bean.setJpaPropertyMap(jpaProperties()); + + return bean; + } + + private Map jpaProperties() { + return Collections.singletonMap("org.hibernate.envers.audit_strategy", ValidityAuditStrategy.class.getName()); + } +} diff --git a/src/test/java/org/springframework/data/envers/repository/support/RepositoryIntegrationTest.java b/src/test/java/org/springframework/data/envers/repository/support/RepositoryIntegrationTest.java new file mode 100755 index 0000000..36257fe --- /dev/null +++ b/src/test/java/org/springframework/data/envers/repository/support/RepositoryIntegrationTest.java @@ -0,0 +1,105 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.envers.repository.support; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.HashSet; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportResource; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.envers.Config; +import org.springframework.data.envers.sample.Country; +import org.springframework.data.envers.sample.CountryRepository; +import org.springframework.data.envers.sample.License; +import org.springframework.data.envers.sample.LicenseRepository; +import org.springframework.data.history.Revision; +import org.springframework.data.history.Revisions; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +/** + * Integration tests for repositories. + * + * @author Oliver Gierke + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(loader = AnnotationConfigContextLoader.class) +public class RepositoryIntegrationTest { + + @ImportResource("classpath:application-context.xml") + @Configuration + static class RepoConfig extends Config { + + } + + @Autowired + LicenseRepository licenseRepository; + @Autowired + CountryRepository countryRepository; + + @Before + public void setUp() { + licenseRepository.deleteAll(); + countryRepository.deleteAll(); + } + + @Test + public void testname() { + + License license = new License(); + license.name = "Schnitzel"; + + licenseRepository.save(license); + + Country de = new Country(); + de.code = "de"; + de.name = "Deutschland"; + + countryRepository.save(de); + + Country se = new Country(); + se.code = "se"; + se.name = "Schweden"; + + countryRepository.save(se); + + license.laender = new HashSet(); + license.laender.addAll(Arrays.asList(de, se)); + + licenseRepository.save(license); + + de.name = "Daenemark"; + + countryRepository.save(de); + + Revision revision = licenseRepository.findLastChangeRevision(license.id); + assertThat(revision, is(notNullValue())); + + Page> revisions = licenseRepository.findRevisions(license.id, new PageRequest(0, 10)); + Revisions wrapper = new Revisions(revisions.getContent()); + assertThat(wrapper.getLatestRevision(), is(revision)); + } +} diff --git a/src/test/java/org/springframework/data/envers/sample/AbstractEntity.java b/src/test/java/org/springframework/data/envers/sample/AbstractEntity.java new file mode 100644 index 0000000..4ba3c0e --- /dev/null +++ b/src/test/java/org/springframework/data/envers/sample/AbstractEntity.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.envers.sample; + +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; + +import org.springframework.util.ObjectUtils; + +@MappedSuperclass +abstract class AbstractEntity { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + public Long id; + + /* + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + + if (this == obj) { + return true; + } + + if (!(obj instanceof AbstractEntity)) { + return false; + } + + AbstractEntity that = (AbstractEntity) obj; + + return ObjectUtils.nullSafeEquals(this.id, that.id); + } + +} \ No newline at end of file diff --git a/src/test/java/org/springframework/data/envers/sample/Country.java b/src/test/java/org/springframework/data/envers/sample/Country.java new file mode 100755 index 0000000..81596e3 --- /dev/null +++ b/src/test/java/org/springframework/data/envers/sample/Country.java @@ -0,0 +1,33 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.envers.sample; + +import javax.persistence.Entity; + +import org.hibernate.envers.Audited; + +/** + * Sample domain class. + * + * @author Oliver Gierke + */ +@Audited +@Entity +public class Country extends AbstractEntity { + + public String code; + public String name; +} diff --git a/src/test/java/org/springframework/data/envers/sample/CountryRepository.java b/src/test/java/org/springframework/data/envers/sample/CountryRepository.java new file mode 100755 index 0000000..26232d8 --- /dev/null +++ b/src/test/java/org/springframework/data/envers/sample/CountryRepository.java @@ -0,0 +1,28 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.envers.sample; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.history.RevisionRepository; + +/** + * Repository for {@link Country} objects. + * + * @author Oliver Gierke + */ +public interface CountryRepository extends RevisionRepository, JpaRepository { + +} diff --git a/src/test/java/org/springframework/data/envers/sample/License.java b/src/test/java/org/springframework/data/envers/sample/License.java new file mode 100755 index 0000000..e727577 --- /dev/null +++ b/src/test/java/org/springframework/data/envers/sample/License.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.envers.sample; + +import java.util.Set; + +import javax.persistence.Entity; +import javax.persistence.ManyToMany; +import javax.persistence.Version; + +import org.hibernate.envers.Audited; + +/** + * Sample domain class. + * + * @author Philip Huegelmeyer + */ +@Audited +@Entity +public class License extends AbstractEntity { + + @Version + public Integer version; + + public String name; + @ManyToMany + public Set laender; +} diff --git a/src/test/java/org/springframework/data/envers/sample/LicenseRepository.java b/src/test/java/org/springframework/data/envers/sample/LicenseRepository.java new file mode 100755 index 0000000..5d72e7b --- /dev/null +++ b/src/test/java/org/springframework/data/envers/sample/LicenseRepository.java @@ -0,0 +1,28 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.envers.sample; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.history.RevisionRepository; + +/** + * Repository for {@link License} objects. + * + * @author Oliver Gierke + */ +public interface LicenseRepository extends RevisionRepository, JpaRepository { + +} diff --git a/src/test/resources/application-context.xml b/src/test/resources/application-context.xml new file mode 100755 index 0000000..2386709 --- /dev/null +++ b/src/test/resources/application-context.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/src/test/resources/log4j.properties b/src/test/resources/log4j.properties new file mode 100755 index 0000000..8fe189a --- /dev/null +++ b/src/test/resources/log4j.properties @@ -0,0 +1,7 @@ +log4j.rootCategory=WARN, console + +log4j.appender.console.layout=org.apache.log4j.PatternLayout +log4j.appender.console.layout.ConversionPattern=%d{ABSOLUTE} %5p %40.40c:%4L - %m%n +log4j.appender.console=org.apache.log4j.ConsoleAppender + +log4j.logger.org.springframework=INFO \ No newline at end of file