Skip to content

Commit b5af2e9

Browse files
committed
DATACMNS-1020 - Improvements to revision API.
RevisionRepository now returns Optional for methods that could previously return null. Revision exposes additional methods to lookup required revision numbers and dates. Revisions now implements Streamable and exposes a ….none() factory method to create an empty instance. AnnotationBasedRevisionMetadata now uses Lazy to lookup the fields with revision annotations. AnnotationDetectionFieldCallback now also uses Optional in places it previously returned null. StreamUtils now exposes factory methods for Collector instances producing unmodifiable List and Set instances. Related ticket: DATACMNS-867.
1 parent 11dc67d commit b5af2e9

File tree

8 files changed

+148
-56
lines changed

8 files changed

+148
-56
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012 the original author or authors.
2+
* Copyright 2012-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
2020
import java.util.Optional;
2121

2222
import org.springframework.data.util.AnnotationDetectionFieldCallback;
23+
import org.springframework.data.util.Lazy;
2324
import org.springframework.util.Assert;
2425
import org.springframework.util.ReflectionUtils;
2526

@@ -32,55 +33,43 @@
3233
public class AnnotationRevisionMetadata<N extends Number & Comparable<N>> implements RevisionMetadata<N> {
3334

3435
private final Object entity;
35-
private final N revisionNumber;
36-
private final LocalDateTime revisionDate;
36+
private final Lazy<Optional<N>> revisionNumber;
37+
private final Lazy<Optional<LocalDateTime>> revisionDate;
3738

3839
/**
3940
* Creates a new {@link AnnotationRevisionMetadata} inspecting the given entity for the given annotations. If no
4041
* annotations will be provided these values will not be looked up from the entity and return {@literal null}.
4142
*
4243
* @param entity must not be {@literal null}.
43-
* @param revisionNumberAnnotation
44-
* @param revisionTimeStampAnnotation
44+
* @param revisionNumberAnnotation must not be {@literal null}.
45+
* @param revisionTimeStampAnnotation must not be {@literal null}.
4546
*/
46-
public AnnotationRevisionMetadata(final Object entity, Class<? extends Annotation> revisionNumberAnnotation,
47+
public AnnotationRevisionMetadata(Object entity, Class<? extends Annotation> revisionNumberAnnotation,
4748
Class<? extends Annotation> revisionTimeStampAnnotation) {
4849

4950
Assert.notNull(entity, "Entity must not be null!");
50-
this.entity = entity;
51-
52-
if (revisionNumberAnnotation != null) {
53-
AnnotationDetectionFieldCallback numberCallback = new AnnotationDetectionFieldCallback(revisionNumberAnnotation);
54-
ReflectionUtils.doWithFields(entity.getClass(), numberCallback);
55-
this.revisionNumber = numberCallback.getValue(entity);
56-
} else {
57-
this.revisionNumber = null;
58-
}
51+
Assert.notNull(revisionNumberAnnotation, "Revision number annotation must not be null!");
52+
Assert.notNull(revisionTimeStampAnnotation, "Revision time stamp annotation must not be null!");
5953

60-
if (revisionTimeStampAnnotation != null) {
61-
AnnotationDetectionFieldCallback revisionCallback = new AnnotationDetectionFieldCallback(
62-
revisionTimeStampAnnotation);
63-
ReflectionUtils.doWithFields(entity.getClass(), revisionCallback);
64-
this.revisionDate = revisionCallback.getValue(entity);
65-
} else {
66-
this.revisionDate = null;
67-
}
54+
this.entity = entity;
55+
this.revisionNumber = detectAnnotation(entity, revisionNumberAnnotation);
56+
this.revisionDate = detectAnnotation(entity, revisionTimeStampAnnotation);
6857
}
6958

7059
/*
7160
* (non-Javadoc)
7261
* @see org.springframework.data.repository.history.RevisionMetadata#getRevisionNumber()
7362
*/
7463
public Optional<N> getRevisionNumber() {
75-
return Optional.ofNullable(revisionNumber);
64+
return revisionNumber.get();
7665
}
7766

7867
/*
7968
* (non-Javadoc)
8069
* @see org.springframework.data.history.RevisionMetadata#getRevisionDate()
8170
*/
8271
public Optional<LocalDateTime> getRevisionDate() {
83-
return Optional.ofNullable(revisionDate);
72+
return revisionDate.get();
8473
}
8574

8675
/*
@@ -91,4 +80,14 @@ public Optional<LocalDateTime> getRevisionDate() {
9180
public <T> T getDelegate() {
9281
return (T) entity;
9382
}
83+
84+
private static <T> Lazy<Optional<T>> detectAnnotation(Object entity, Class<? extends Annotation> annotationType) {
85+
86+
return Lazy.of(() -> {
87+
88+
AnnotationDetectionFieldCallback numberCallback = new AnnotationDetectionFieldCallback(annotationType);
89+
ReflectionUtils.doWithFields(entity.getClass(), numberCallback);
90+
return numberCallback.getValue(entity);
91+
});
92+
}
9493
}

Diff for: src/main/java/org/springframework/data/history/Revision.java

+25-6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package org.springframework.data.history;
1717

18+
import static org.springframework.data.util.Optionals.*;
19+
1820
import lombok.AccessLevel;
1921
import lombok.NonNull;
2022
import lombok.RequiredArgsConstructor;
@@ -64,6 +66,15 @@ public Optional<N> getRevisionNumber() {
6466
return metadata.getRevisionNumber();
6567
}
6668

69+
/**
70+
* Returns the revision number of the revision, immediately failing on absence.
71+
*
72+
* @return the revision number.
73+
*/
74+
public N getRequiredRevisionNumber() {
75+
return metadata.getRequiredRevisionNumber();
76+
}
77+
6778
/**
6879
* Returns the revision date of the revision.
6980
*
@@ -73,16 +84,22 @@ public Optional<LocalDateTime> getRevisionDate() {
7384
return metadata.getRevisionDate();
7485
}
7586

87+
/**
88+
* Returns the revision date of the revision, immediately failing on absence.
89+
*
90+
* @return the revision date.
91+
*/
92+
public LocalDateTime getRequiredRevisionDate() {
93+
return metadata.getRequiredRevisionDate();
94+
}
95+
7696
/*
7797
* (non-Javadoc)
7898
* @see java.lang.Comparable#compareTo(java.lang.Object)
7999
*/
80100
public int compareTo(Revision<N, ?> that) {
81-
82-
Optional<N> thisRevisionNumber = getRevisionNumber();
83-
Optional<N> thatRevisionNumber = that.getRevisionNumber();
84-
85-
return thisRevisionNumber.map(left -> thatRevisionNumber.map(left::compareTo).orElse(1)).orElse(-1);
101+
return mapIfAllPresent(getRevisionNumber(), that.getRevisionNumber(), //
102+
(left, right) -> left.compareTo(right)).orElse(-1);
86103
}
87104

88105
/*
@@ -91,6 +108,8 @@ public int compareTo(Revision<N, ?> that) {
91108
*/
92109
@Override
93110
public String toString() {
94-
return String.format("Revision %s of entity %s - Revision metadata %s", getRevisionNumber(), entity, metadata);
111+
112+
return String.format("Revision %s of entity %s - Revision metadata %s",
113+
getRevisionNumber().map(Object::toString).orElse("<unknown>"), entity, metadata);
95114
}
96115
}

Diff for: src/main/java/org/springframework/data/history/RevisionMetadata.java

+25-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012 the original author or authors.
2+
* Copyright 2012-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,17 +29,39 @@ public interface RevisionMetadata<N extends Number & Comparable<N>> {
2929
/**
3030
* Returns the revision number of the revision.
3131
*
32-
* @return
32+
* @return will never be {@literal null}.
3333
*/
3434
Optional<N> getRevisionNumber();
3535

36+
/**
37+
* Returns the revision number of the revision, immediately failing on absence.
38+
*
39+
* @return will never be {@literal null}.
40+
* @throws IllegalStateException if no revision number is available.
41+
*/
42+
default N getRequiredRevisionNumber() {
43+
return getRevisionNumber()
44+
.orElseThrow(() -> new IllegalStateException(String.format("No revision number found on %s!", getDelegate())));
45+
}
46+
3647
/**
3748
* Returns the date of the revision.
3849
*
39-
* @return
50+
* @return will never be {@literal null}.
4051
*/
4152
Optional<LocalDateTime> getRevisionDate();
4253

54+
/**
55+
* Returns the revision date of the revision, immediately failing on absence.
56+
*
57+
* @return will never be {@literal null}.
58+
* @throw IllegalStateException if no revision date is available.
59+
*/
60+
default LocalDateTime getRequiredRevisionDate() {
61+
return getRevisionDate()
62+
.orElseThrow(() -> new IllegalStateException(String.format("No revision date found on %s!", getDelegate())));
63+
}
64+
4365
/**
4466
* Returns the underlying revision metadata which might provider more detailed implementation specific information.
4567
*

Diff for: src/main/java/org/springframework/data/history/Revisions.java

+18-3
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@
1919
import java.util.Comparator;
2020
import java.util.Iterator;
2121
import java.util.List;
22-
import java.util.stream.Collectors;
2322

23+
import org.springframework.data.util.StreamUtils;
24+
import org.springframework.data.util.Streamable;
2425
import org.springframework.util.Assert;
2526

2627
/**
@@ -30,7 +31,7 @@
3031
* @author Oliver Gierke
3132
* @author Christoph Strobl
3233
*/
33-
public class Revisions<N extends Number & Comparable<N>, T> implements Iterable<Revision<N, T>> {
34+
public class Revisions<N extends Number & Comparable<N>, T> implements Streamable<Revision<N, T>> {
3435

3536
private final Comparator<Revision<N, T>> NATURAL_ORDER = Comparator.naturalOrder();
3637

@@ -59,15 +60,29 @@ private Revisions(List<? extends Revision<N, T>> revisions, boolean latestLast)
5960

6061
this.revisions = revisions.stream()//
6162
.sorted(latestLast ? NATURAL_ORDER : NATURAL_ORDER.reversed())//
62-
.collect(Collectors.toList());
63+
.collect(StreamUtils.toUnmodifiableList());
6364

6465
this.latestLast = latestLast;
6566
}
6667

68+
/**
69+
* Creates a new {@link Revisions} instance for the given {@link Revision}s.
70+
*
71+
* @return will never be {@literal null}.
72+
*/
6773
public static <N extends Number & Comparable<N>, T> Revisions<N, T> of(List<? extends Revision<N, T>> revisions) {
6874
return new Revisions<>(revisions);
6975
}
7076

77+
/**
78+
* Creates a new empty {@link Revisions} instance.
79+
*
80+
* @return will never be {@literal null}.
81+
*/
82+
public static <N extends Number & Comparable<N>, T> Revisions<N, T> none() {
83+
return new Revisions<>(Collections.emptyList());
84+
}
85+
7186
/**
7287
* Returns the latest revision of the revisions backing the wrapper independently of the order.
7388
*

Diff for: src/main/java/org/springframework/data/repository/history/RevisionRepository.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.repository.history;
1717

1818
import java.io.Serializable;
19+
import java.util.Optional;
1920

2021
import org.springframework.data.domain.Page;
2122
import org.springframework.data.domain.Pageable;
@@ -41,7 +42,7 @@ public interface RevisionRepository<T, ID extends Serializable, N extends Number
4142
* @param id must not be {@literal null}.
4243
* @return
4344
*/
44-
Revision<N, T> findLastChangeRevision(ID id);
45+
Optional<Revision<N, T>> findLastChangeRevision(ID id);
4546

4647
/**
4748
* Returns all {@link Revisions} of an entity with the given id.
@@ -70,5 +71,5 @@ public interface RevisionRepository<T, ID extends Serializable, N extends Number
7071
* @return the entity with the given ID in the given revision number.
7172
* @since 1.12
7273
*/
73-
Revision<N, T> findRevision(ID id, N revisionNumber);
74+
Optional<Revision<N, T>> findRevision(ID id, N revisionNumber);
7475
}

Diff for: src/main/java/org/springframework/data/util/AnnotationDetectionFieldCallback.java

+25-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2016 the original author or authors.
2+
* Copyright 2012-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717

1818
import java.lang.annotation.Annotation;
1919
import java.lang.reflect.Field;
20+
import java.util.Optional;
2021

2122
import org.springframework.core.annotation.AnnotatedElementUtils;
2223
import org.springframework.util.Assert;
@@ -33,7 +34,7 @@
3334
public class AnnotationDetectionFieldCallback implements FieldCallback {
3435

3536
private final Class<? extends Annotation> annotationType;
36-
private Field field;
37+
private Optional<Field> field = Optional.empty();
3738

3839
/**
3940
* Creates a new {@link AnnotationDetectionFieldCallback} scanning for an annotation of the given type.
@@ -43,6 +44,7 @@ public class AnnotationDetectionFieldCallback implements FieldCallback {
4344
public AnnotationDetectionFieldCallback(Class<? extends Annotation> annotationType) {
4445

4546
Assert.notNull(annotationType, "AnnotationType must not be null!");
47+
4648
this.annotationType = annotationType;
4749
}
4850

@@ -52,16 +54,14 @@ public AnnotationDetectionFieldCallback(Class<? extends Annotation> annotationTy
5254
*/
5355
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
5456

55-
if (this.field != null) {
57+
if (this.field.isPresent()) {
5658
return;
5759
}
5860

59-
Annotation annotation = AnnotatedElementUtils.findMergedAnnotation(field, annotationType);
60-
61-
if (annotation != null) {
61+
if (AnnotatedElementUtils.findMergedAnnotation(field, annotationType) != null) {
6262

63-
this.field = field;
64-
ReflectionUtils.makeAccessible(this.field);
63+
ReflectionUtils.makeAccessible(field);
64+
this.field = Optional.of(field);
6565
}
6666
}
6767

@@ -70,8 +70,20 @@ public void doWith(Field field) throws IllegalArgumentException, IllegalAccessEx
7070
*
7171
* @return
7272
*/
73-
public Class<?> getType() {
74-
return field == null ? null : field.getType();
73+
public Optional<Class<?>> getType() {
74+
return field.map(Field::getType);
75+
}
76+
77+
/**
78+
* Returns the type of the field or throws an {@link IllegalArgumentException} if no field could be found.
79+
*
80+
* @return
81+
* @throws IllegalStateException
82+
*/
83+
public Class<?> getRequiredType() {
84+
85+
return getType().orElseThrow(() -> new IllegalStateException(
86+
String.format("Unable to obtain type! Didn't find field with annotation %s!", annotationType)));
7587
}
7688

7789
/**
@@ -81,9 +93,10 @@ public Class<?> getType() {
8193
* @return
8294
*/
8395
@SuppressWarnings("unchecked")
84-
public <T> T getValue(Object source) {
96+
public <T> Optional<T> getValue(Object source) {
8597

8698
Assert.notNull(source, "Source object must not be null!");
87-
return field == null ? null : (T) ReflectionUtils.getField(field, source);
99+
100+
return field.map(it -> (T) ReflectionUtils.getField(it, source));
88101
}
89102
}

0 commit comments

Comments
 (0)