• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kit-data-manager / pit-service / #576

30 Sep 2025 10:09AM UTC coverage: 78.134% (+5.4%) from 72.712%
#576

Pull #264

github

web-flow
Merge 269776c6c into ef6582172
Pull Request #264: Development branch for v3.0.0

661 of 796 new or added lines in 25 files covered. (83.04%)

3 existing lines in 2 files now uncovered.

1072 of 1372 relevant lines covered (78.13%)

0.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.18
/src/main/java/edu/kit/datamanager/pit/domain/Operations.java
1
package edu.kit.datamanager.pit.domain;
2

3
import java.io.IOException;
4
import java.time.format.DateTimeFormatter;
5
import java.util.ArrayList;
6
import java.util.Arrays;
7
import java.util.Collection;
8
import java.util.Comparator;
9
import java.util.Date;
10
import java.util.List;
11
import java.util.Optional;
12
import java.util.concurrent.CompletableFuture;
13
import java.util.stream.Collectors;
14

15
import edu.kit.datamanager.pit.pidsystem.IIdentifierSystem;
16
import edu.kit.datamanager.pit.typeregistry.AttributeInfo;
17
import edu.kit.datamanager.pit.typeregistry.ITypeRegistry;
18
import org.apache.commons.lang3.stream.Streams;
19
import java.time.ZonedDateTime;
20

21
/**
22
 * Simple operations on PID records.
23
 * <p>
24
 * Caches results e.g. for type queries
25
 */
26
public class Operations {
27

28
    private static final String[] KNOWN_DATE_CREATED = {
1✔
29
        "21.T11148/29f92bd203dd3eaa5a1f",
30
        "21.T11148/aafd5fb4c7222e2d950a"
31
    };
32

33
    private static final String[] KNOWN_DATE_MODIFIED = {
1✔
34
        "21.T11148/397d831aa3a9d18eb52c"
35
    };
36

37
    private final ITypeRegistry typeRegistry;
38
    private final IIdentifierSystem identifierSystem;
39

40
    public Operations(ITypeRegistry typeRegistry, IIdentifierSystem identifierSystem) {
1✔
41
        this.typeRegistry = typeRegistry;
1✔
42
        this.identifierSystem = identifierSystem;
1✔
43
    }
1✔
44

45
    /**
46
     * Tries to get the date when a FAIR DO was created from a PID record.
47
     * <p>
48
     * Strategy:
49
     * - try to get it from known "dateCreated" types
50
     * - as a fallback, try to get it by its human-readable name
51
     * <p>
52
     * Semantic reasoning in some sense is planned but not yet supported.
53
     * 
54
     * @param pidRecord the record to extract the information from.
55
     * @return the date, if it could have been extracted.
56
     * @throws IOException on IO errors regarding resolving types.
57
     */
58
    public Optional<Date> findDateCreated(PIDRecord pidRecord) throws IOException {
59
        /* try known types */
60
        List<String> knownDateTypes = Arrays.asList(Operations.KNOWN_DATE_CREATED);
1✔
61
        Optional<Date> date = knownDateTypes
1✔
62
                .stream()
1✔
63
                .map(pidRecord::getPropertyValues)
1✔
64
                .map(Arrays::asList)
1✔
65
                .flatMap(List<String>::stream)
1✔
66
                .map(this::extractDate)
1✔
67
                .filter(Optional::isPresent)
1✔
68
                .map(Optional::get)
1✔
69
                .min(Comparator.comparingLong(Date::getTime));
1✔
70
        if (date.isPresent()) {
1✔
71
            return date;
1✔
72
        }
73

74
        Collection<AttributeInfo> types = new ArrayList<>();
1✔
75
        List<CompletableFuture<?>> futures = Streams.failableStream(
1✔
76
                pidRecord.getPropertyIdentifiers().stream())
1✔
77
                .filter(this.identifierSystem::isPidRegistered)
1✔
78
                .map(attributePid -> this.typeRegistry
1✔
NEW
79
                            .queryAttributeInfo(attributePid)
×
NEW
80
                            .thenAcceptAsync(types::add))
×
81
                .collect(Collectors.toList());
1✔
82
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
1✔
83

84
        /*
85
         * as a last fallback, try to find types with human-readable names containing
86
         * "dateCreated" or "createdAt" or "creationDate".
87
         * 
88
         * This can be removed as soon as we have some default FAIR DO types new type
89
         * definitions can refer to (e.g. "extend" them or declare the same meaning as
90
         * our known types, see above)
91
         */
92
        return types
1✔
93
                .stream()
1✔
94
                .filter(type ->
1✔
NEW
95
                        type.name().equalsIgnoreCase("dateCreated")
×
NEW
96
                                || type.name().equalsIgnoreCase("createdAt")
×
NEW
97
                                || type.name().equalsIgnoreCase("creationDate"))
×
98
                .map(type -> pidRecord.getPropertyValues(type.pid()))
1✔
99
                .map(Arrays::asList)
1✔
100
                .flatMap(List<String>::stream)
1✔
101
                .map(this::extractDate)
1✔
102
                .filter(Optional::isPresent)
1✔
103
                .map(Optional::get)
1✔
104
                .min(Comparator.comparingLong(Date::getTime));
1✔
105
    }
106

107
    /**
108
     * Tries to get the date when a FAIR DO was modified from a PID record.
109
     * <p>
110
     * Strategy:
111
     * - try to get it from known "dateModified" types
112
     * - as a fallback, try to get it by its human-readable name
113
     * <p>
114
     * Semantic reasoning in some sense is planned but not yet supported.
115
     * 
116
     * @param pidRecord the record to extract the information from.
117
     * @return the date, if it could have been extracted.
118
     * @throws IOException on IO errors regarding resolving types.
119
     */
120
    public Optional<Date> findDateModified(PIDRecord pidRecord) throws IOException {
121
        /* try known types */
122
        List<String> knownDateTypes = Arrays.asList(Operations.KNOWN_DATE_MODIFIED);
1✔
123
        Optional<Date> date = knownDateTypes
1✔
124
                .stream()
1✔
125
                .map(pidRecord::getPropertyValues)
1✔
126
                .map(Arrays::asList)
1✔
127
                .flatMap(List<String>::stream)
1✔
128
                .map(this::extractDate)
1✔
129
                .filter(Optional::isPresent)
1✔
130
                .map(Optional::get)
1✔
131
                .min(Comparator.comparingLong(Date::getTime));
1✔
132
        if (date.isPresent()) {
1✔
133
            return date;
1✔
134
        }
135

136
        Collection<AttributeInfo> types = new ArrayList<>();
1✔
137
        List<CompletableFuture<?>> futures = Streams.failableStream(pidRecord.getPropertyIdentifiers().stream())
1✔
138
                .filter(this.identifierSystem::isPidRegistered)
1✔
139
                .map(attributePid -> this.typeRegistry
1✔
NEW
140
                            .queryAttributeInfo(attributePid)
×
NEW
141
                            .thenAcceptAsync(types::add))
×
142
                .collect(Collectors.toList());
1✔
143
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
1✔
144

145
        /*
146
         * as a last fallback, try to find types with human-readable names containing
147
         * "dateModified" or "lastModified" or "modificationDate".
148
         * 
149
         * This can be removed as soon as we have some default FAIR DO types new type
150
         * definitions can refer to (e.g. "extend" them or declare the same meaning as
151
         * our known types, see above)
152
         */
153
        return types
1✔
154
                .stream()
1✔
155
                .filter(type ->
1✔
NEW
156
                        type.name().equalsIgnoreCase("dateModified")
×
NEW
157
                                || type.name().equalsIgnoreCase("lastModified")
×
NEW
158
                                || type.name().equalsIgnoreCase("modificationDate"))
×
159
                .map(type -> pidRecord.getPropertyValues(type.pid()))
1✔
160
                .map(Arrays::asList)
1✔
161
                .flatMap(List<String>::stream)
1✔
162
                .map(this::extractDate)
1✔
163
                .filter(Optional::isPresent)
1✔
164
                .map(Optional::get)
1✔
165
                .min(Comparator.comparingLong(Date::getTime));
1✔
166
    }
167

168
    /**
169
     * Tries to extract a Date object from a String.
170
     * 
171
     * @param dateString the date string to extract the date from.
172
     * @return the extracted Date object.
173
     */
174
    protected Optional<Date> extractDate(String dateString) {
175
        DateTimeFormatter dateFormatter = DateTimeFormatter.ISO_DATE_TIME;
1✔
176
        try {
177
            ZonedDateTime dateTime = ZonedDateTime.parse(dateString, dateFormatter);
1✔
178
            return Optional.of(Date.from(dateTime.toInstant()));
1✔
179
        } catch (Exception e) {
1✔
180
            return Optional.empty();
1✔
181
        }
182
    }
183
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc