diff --git a/src/main/java/org/springframework/data/history/AnnotationRevisionMetadata.java b/src/main/java/org/springframework/data/history/AnnotationRevisionMetadata.java index b8830ebe8c..a7871240ad 100755 --- a/src/main/java/org/springframework/data/history/AnnotationRevisionMetadata.java +++ b/src/main/java/org/springframework/data/history/AnnotationRevisionMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 the original author or authors. + * Copyright 2012-2017 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. @@ -20,6 +20,7 @@ import java.util.Optional; import org.springframework.data.util.AnnotationDetectionFieldCallback; +import org.springframework.data.util.Lazy; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; @@ -32,39 +33,27 @@ public class AnnotationRevisionMetadata> implements RevisionMetadata { private final Object entity; - private final N revisionNumber; - private final LocalDateTime revisionDate; + private final Lazy> revisionNumber; + private final Lazy> revisionDate; /** * Creates a new {@link AnnotationRevisionMetadata} inspecting the given entity for the given annotations. If no * annotations will be provided these values will not be looked up from the entity and return {@literal null}. * * @param entity must not be {@literal null}. - * @param revisionNumberAnnotation - * @param revisionTimeStampAnnotation + * @param revisionNumberAnnotation must not be {@literal null}. + * @param revisionTimeStampAnnotation must not be {@literal null}. */ - public AnnotationRevisionMetadata(final Object entity, Class revisionNumberAnnotation, + public AnnotationRevisionMetadata(Object entity, Class revisionNumberAnnotation, Class revisionTimeStampAnnotation) { Assert.notNull(entity, "Entity must not be null!"); - this.entity = entity; - - if (revisionNumberAnnotation != null) { - AnnotationDetectionFieldCallback numberCallback = new AnnotationDetectionFieldCallback(revisionNumberAnnotation); - ReflectionUtils.doWithFields(entity.getClass(), numberCallback); - this.revisionNumber = numberCallback.getValue(entity); - } else { - this.revisionNumber = null; - } + Assert.notNull(revisionNumberAnnotation, "Revision number annotation must not be null!"); + Assert.notNull(revisionTimeStampAnnotation, "Revision time stamp annotation must not be null!"); - if (revisionTimeStampAnnotation != null) { - AnnotationDetectionFieldCallback revisionCallback = new AnnotationDetectionFieldCallback( - revisionTimeStampAnnotation); - ReflectionUtils.doWithFields(entity.getClass(), revisionCallback); - this.revisionDate = revisionCallback.getValue(entity); - } else { - this.revisionDate = null; - } + this.entity = entity; + this.revisionNumber = detectAnnotation(entity, revisionNumberAnnotation); + this.revisionDate = detectAnnotation(entity, revisionTimeStampAnnotation); } /* @@ -72,7 +61,7 @@ public AnnotationRevisionMetadata(final Object entity, Class getRevisionNumber() { - return Optional.ofNullable(revisionNumber); + return revisionNumber.get(); } /* @@ -80,7 +69,7 @@ public Optional getRevisionNumber() { * @see org.springframework.data.history.RevisionMetadata#getRevisionDate() */ public Optional getRevisionDate() { - return Optional.ofNullable(revisionDate); + return revisionDate.get(); } /* @@ -91,4 +80,14 @@ public Optional getRevisionDate() { public T getDelegate() { return (T) entity; } + + private static Lazy> detectAnnotation(Object entity, Class annotationType) { + + return Lazy.of(() -> { + + AnnotationDetectionFieldCallback numberCallback = new AnnotationDetectionFieldCallback(annotationType); + ReflectionUtils.doWithFields(entity.getClass(), numberCallback); + return numberCallback.getValue(entity); + }); + } } diff --git a/src/main/java/org/springframework/data/history/Revision.java b/src/main/java/org/springframework/data/history/Revision.java index 607e9824e0..da515e41ea 100755 --- a/src/main/java/org/springframework/data/history/Revision.java +++ b/src/main/java/org/springframework/data/history/Revision.java @@ -15,6 +15,8 @@ */ package org.springframework.data.history; +import static org.springframework.data.util.Optionals.*; + import lombok.AccessLevel; import lombok.NonNull; import lombok.RequiredArgsConstructor; @@ -64,6 +66,15 @@ public Optional getRevisionNumber() { return metadata.getRevisionNumber(); } + /** + * Returns the revision number of the revision, immediately failing on absence. + * + * @return the revision number. + */ + public N getRequiredRevisionNumber() { + return metadata.getRequiredRevisionNumber(); + } + /** * Returns the revision date of the revision. * @@ -73,16 +84,22 @@ public Optional getRevisionDate() { return metadata.getRevisionDate(); } + /** + * Returns the revision date of the revision, immediately failing on absence. + * + * @return the revision date. + */ + public LocalDateTime getRequiredRevisionDate() { + return metadata.getRequiredRevisionDate(); + } + /* * (non-Javadoc) * @see java.lang.Comparable#compareTo(java.lang.Object) */ public int compareTo(Revision that) { - - Optional thisRevisionNumber = getRevisionNumber(); - Optional thatRevisionNumber = that.getRevisionNumber(); - - return thisRevisionNumber.map(left -> thatRevisionNumber.map(left::compareTo).orElse(1)).orElse(-1); + return mapIfAllPresent(getRevisionNumber(), that.getRevisionNumber(), // + (left, right) -> left.compareTo(right)).orElse(-1); } /* @@ -91,6 +108,8 @@ public int compareTo(Revision that) { */ @Override public String toString() { - return String.format("Revision %s of entity %s - Revision metadata %s", getRevisionNumber(), entity, metadata); + + return String.format("Revision %s of entity %s - Revision metadata %s", + getRevisionNumber().map(Object::toString).orElse(""), entity, metadata); } } diff --git a/src/main/java/org/springframework/data/history/RevisionMetadata.java b/src/main/java/org/springframework/data/history/RevisionMetadata.java index cd32c10837..72cebaa785 100755 --- a/src/main/java/org/springframework/data/history/RevisionMetadata.java +++ b/src/main/java/org/springframework/data/history/RevisionMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 the original author or authors. + * Copyright 2012-2017 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. @@ -29,17 +29,39 @@ public interface RevisionMetadata> { /** * Returns the revision number of the revision. * - * @return + * @return will never be {@literal null}. */ Optional getRevisionNumber(); + /** + * Returns the revision number of the revision, immediately failing on absence. + * + * @return will never be {@literal null}. + * @throws IllegalStateException if no revision number is available. + */ + default N getRequiredRevisionNumber() { + return getRevisionNumber() + .orElseThrow(() -> new IllegalStateException(String.format("No revision number found on %s!", getDelegate()))); + } + /** * Returns the date of the revision. * - * @return + * @return will never be {@literal null}. */ Optional getRevisionDate(); + /** + * Returns the revision date of the revision, immediately failing on absence. + * + * @return will never be {@literal null}. + * @throw IllegalStateException if no revision date is available. + */ + default LocalDateTime getRequiredRevisionDate() { + return getRevisionDate() + .orElseThrow(() -> new IllegalStateException(String.format("No revision date found on %s!", getDelegate()))); + } + /** * Returns the underlying revision metadata which might provider more detailed implementation specific information. * diff --git a/src/main/java/org/springframework/data/history/Revisions.java b/src/main/java/org/springframework/data/history/Revisions.java index daad89bc76..e2925b4b18 100644 --- a/src/main/java/org/springframework/data/history/Revisions.java +++ b/src/main/java/org/springframework/data/history/Revisions.java @@ -19,8 +19,9 @@ import java.util.Comparator; import java.util.Iterator; import java.util.List; -import java.util.stream.Collectors; +import org.springframework.data.util.StreamUtils; +import org.springframework.data.util.Streamable; import org.springframework.util.Assert; /** @@ -30,7 +31,7 @@ * @author Oliver Gierke * @author Christoph Strobl */ -public class Revisions, T> implements Iterable> { +public class Revisions, T> implements Streamable> { private final Comparator> NATURAL_ORDER = Comparator.naturalOrder(); @@ -59,15 +60,29 @@ private Revisions(List> revisions, boolean latestLast) this.revisions = revisions.stream()// .sorted(latestLast ? NATURAL_ORDER : NATURAL_ORDER.reversed())// - .collect(Collectors.toList()); + .collect(StreamUtils.toUnmodifiableList()); this.latestLast = latestLast; } + /** + * Creates a new {@link Revisions} instance for the given {@link Revision}s. + * + * @return will never be {@literal null}. + */ public static , T> Revisions of(List> revisions) { return new Revisions<>(revisions); } + /** + * Creates a new empty {@link Revisions} instance. + * + * @return will never be {@literal null}. + */ + public static , T> Revisions none() { + return new Revisions<>(Collections.emptyList()); + } + /** * Returns the latest revision of the revisions backing the wrapper independently of the order. * diff --git a/src/main/java/org/springframework/data/repository/history/RevisionRepository.java b/src/main/java/org/springframework/data/repository/history/RevisionRepository.java index b9aa241e36..94b0bc376a 100755 --- a/src/main/java/org/springframework/data/repository/history/RevisionRepository.java +++ b/src/main/java/org/springframework/data/repository/history/RevisionRepository.java @@ -16,6 +16,7 @@ package org.springframework.data.repository.history; import java.io.Serializable; +import java.util.Optional; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -41,7 +42,7 @@ public interface RevisionRepository findLastChangeRevision(ID id); + Optional> findLastChangeRevision(ID id); /** * Returns all {@link Revisions} of an entity with the given id. @@ -70,5 +71,5 @@ public interface RevisionRepository findRevision(ID id, N revisionNumber); + Optional> findRevision(ID id, N revisionNumber); } diff --git a/src/main/java/org/springframework/data/util/AnnotationDetectionFieldCallback.java b/src/main/java/org/springframework/data/util/AnnotationDetectionFieldCallback.java index 84ba764496..de09f21071 100755 --- a/src/main/java/org/springframework/data/util/AnnotationDetectionFieldCallback.java +++ b/src/main/java/org/springframework/data/util/AnnotationDetectionFieldCallback.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 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. @@ -17,6 +17,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Field; +import java.util.Optional; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.util.Assert; @@ -33,7 +34,7 @@ public class AnnotationDetectionFieldCallback implements FieldCallback { private final Class annotationType; - private Field field; + private Optional field = Optional.empty(); /** * Creates a new {@link AnnotationDetectionFieldCallback} scanning for an annotation of the given type. @@ -43,6 +44,7 @@ public class AnnotationDetectionFieldCallback implements FieldCallback { public AnnotationDetectionFieldCallback(Class annotationType) { Assert.notNull(annotationType, "AnnotationType must not be null!"); + this.annotationType = annotationType; } @@ -52,16 +54,14 @@ public AnnotationDetectionFieldCallback(Class annotationTy */ public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { - if (this.field != null) { + if (this.field.isPresent()) { return; } - Annotation annotation = AnnotatedElementUtils.findMergedAnnotation(field, annotationType); - - if (annotation != null) { + if (AnnotatedElementUtils.findMergedAnnotation(field, annotationType) != null) { - this.field = field; - ReflectionUtils.makeAccessible(this.field); + ReflectionUtils.makeAccessible(field); + this.field = Optional.of(field); } } @@ -70,8 +70,20 @@ public void doWith(Field field) throws IllegalArgumentException, IllegalAccessEx * * @return */ - public Class getType() { - return field == null ? null : field.getType(); + public Optional> getType() { + return field.map(Field::getType); + } + + /** + * Returns the type of the field or throws an {@link IllegalArgumentException} if no field could be found. + * + * @return + * @throws IllegalStateException + */ + public Class getRequiredType() { + + return getType().orElseThrow(() -> new IllegalStateException( + String.format("Unable to obtain type! Didn't find field with annotation %s!", annotationType))); } /** @@ -81,9 +93,10 @@ public Class getType() { * @return */ @SuppressWarnings("unchecked") - public T getValue(Object source) { + public Optional getValue(Object source) { Assert.notNull(source, "Source object must not be null!"); - return field == null ? null : (T) ReflectionUtils.getField(field, source); + + return field.map(it -> (T) ReflectionUtils.getField(it, source)); } } diff --git a/src/main/java/org/springframework/data/util/StreamUtils.java b/src/main/java/org/springframework/data/util/StreamUtils.java index 1cb8fb4b00..ea84b25540 100644 --- a/src/main/java/org/springframework/data/util/StreamUtils.java +++ b/src/main/java/org/springframework/data/util/StreamUtils.java @@ -15,9 +15,15 @@ */ package org.springframework.data.util; +import static java.util.stream.Collectors.*; + +import java.util.Collections; import java.util.Iterator; +import java.util.List; +import java.util.Set; import java.util.Spliterator; import java.util.Spliterators; +import java.util.stream.Collector; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -53,4 +59,22 @@ public static Stream createStreamFromIterator(Iterator iterator) { ? stream.onClose(() -> ((CloseableIterator) iterator).close()) // : stream; } + + /** + * Returns a {@link Collector} to create an unmodifiable {@link List}. + * + * @return will never be {@literal null}. + */ + public static Collector> toUnmodifiableList() { + return collectingAndThen(toList(), Collections::unmodifiableList); + } + + /** + * Returns a {@link Collector} to create an unmodifiable {@link Set}. + * + * @return will never be {@literal null}. + */ + public static Collector> toUnmodifiableSet() { + return collectingAndThen(toSet(), Collections::unmodifiableSet); + } } diff --git a/src/test/java/org/springframework/data/util/AnnotationDetectionFieldCallbackUnitTests.java b/src/test/java/org/springframework/data/util/AnnotationDetectionFieldCallbackUnitTests.java index 53e52d0d08..f6432ba529 100755 --- a/src/test/java/org/springframework/data/util/AnnotationDetectionFieldCallbackUnitTests.java +++ b/src/test/java/org/springframework/data/util/AnnotationDetectionFieldCallbackUnitTests.java @@ -36,14 +36,13 @@ public void rejectsNullAnnotationType() { } @Test // DATACMNS-616 - @SuppressWarnings("rawtypes") public void looksUpValueFromPrivateField() { AnnotationDetectionFieldCallback callback = new AnnotationDetectionFieldCallback(Autowired.class); ReflectionUtils.doWithFields(Sample.class, callback); - assertThat(callback.getType()).isEqualTo(String.class); - assertThat(callback. getValue(new Sample("foo"))).isEqualTo("foo"); + assertThat(callback.getType()).hasValue(String.class); + assertThat(callback.getValue(new Sample("foo"))).hasValue("foo"); } @Test // DATACMNS-616 @@ -52,8 +51,8 @@ public void returnsNullForObjectNotContainingAFieldWithTheConfiguredAnnotation() AnnotationDetectionFieldCallback callback = new AnnotationDetectionFieldCallback(Autowired.class); ReflectionUtils.doWithFields(Empty.class, callback); - assertThat(callback.getType()).isNull(); - assertThat(callback. getValue(new Empty())).isNull(); + assertThat(callback.getType()).isNotPresent(); + assertThat(callback.getValue(new Empty())).isNotPresent(); } @Value