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

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

21 Jan 2025 11:55PM UTC coverage: 75.383% (+3.0%) from 72.4%
#444

Pull #218

github

web-flow
Merge 0aada098b into 459f0c036
Pull Request #218: Type-Api support and validation speedup

257 of 322 new or added lines in 18 files covered. (79.81%)

3 existing lines in 2 files now uncovered.

885 of 1174 relevant lines covered (75.38%)

0.75 hits per line

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

78.43
/src/main/java/edu/kit/datamanager/pit/pitservice/impl/EmbeddedStrictValidatorStrategy.java
1
package edu.kit.datamanager.pit.pitservice.impl;
2

3
import edu.kit.datamanager.pit.common.ExternalServiceException;
4
import edu.kit.datamanager.pit.common.RecordValidationException;
5
import edu.kit.datamanager.pit.common.TypeNotFoundException;
6
import edu.kit.datamanager.pit.configuration.ApplicationProperties;
7
import edu.kit.datamanager.pit.domain.PIDRecord;
8
import edu.kit.datamanager.pit.pitservice.IValidationStrategy;
9
import edu.kit.datamanager.pit.typeregistry.AttributeInfo;
10
import edu.kit.datamanager.pit.typeregistry.ITypeRegistry;
11

12
import java.util.Arrays;
13
import java.util.List;
14
import java.util.Set;
15
import java.util.concurrent.*;
16

17
import org.slf4j.Logger;
18
import org.slf4j.LoggerFactory;
19

20
/**
21
 * Validates a PID record using embedded profile(s).
22
 * <p>
23
 * - checks if all mandatory attributes are present
24
 * - validates all available attributes
25
 * - fails if an attribute is not defined within the profile
26
 */
27
public class EmbeddedStrictValidatorStrategy implements IValidationStrategy {
28

29
    private static final Logger LOG = LoggerFactory.getLogger(EmbeddedStrictValidatorStrategy.class);
1✔
30

31
    protected final ITypeRegistry typeRegistry;
32
    protected final boolean alwaysAcceptAdditionalAttributes;
33
    protected final Set<String> profileKeys;
34

35
    public EmbeddedStrictValidatorStrategy(
36
            ITypeRegistry typeRegistry,
37
            ApplicationProperties config
38
    ) {
1✔
39
        this.typeRegistry = typeRegistry;
1✔
40
        this.profileKeys = config.getProfileKeys();
1✔
41
        this.alwaysAcceptAdditionalAttributes = config.isValidationAlwaysAllowAdditionalAttributes();
1✔
42
    }
1✔
43

44
    @Override
45
    public void validate(PIDRecord pidRecord)
46
            throws RecordValidationException, ExternalServiceException
47
    {
48
        if (pidRecord.getPropertyIdentifiers().isEmpty()) {
1✔
49
            throw new RecordValidationException(pidRecord, "Record is empty!");
1✔
50
        }
51

52
        // For each attribute in record, resolve schema and check the value
53
        List<CompletableFuture<AttributeInfo>> attributeInfoFutures = pidRecord.getPropertyIdentifiers().stream()
1✔
54
                // resolve attribute info (type and schema)
55
                .map(this.typeRegistry::queryAttributeInfo)
1✔
56
                // validate values using schema
57
                .map(attributeInfoFuture -> attributeInfoFuture.thenApply(attributeInfo -> {
1✔
58
                    for (String value : pidRecord.getPropertyValues(attributeInfo.pid())) {
1✔
59
                        boolean isValid = attributeInfo.validate(value);
1✔
60
                        if (!isValid) {
1✔
NEW
61
                            throw new RecordValidationException(
×
62
                                    pidRecord,
63
                                    "Attribute %s has a non-complying value %s"
NEW
64
                                            .formatted(attributeInfo.pid(), value));
×
65
                        }
66
                    }
67
                    return attributeInfo;
1✔
68
                }))
69
                // resolve profiles and apply their validation
70
                .map(attributeInfoFuture -> attributeInfoFuture.thenApply(attributeInfo -> {
1✔
71
                    boolean indicatesProfileValue = this.profileKeys.contains(attributeInfo.pid());
1✔
72
                    if (indicatesProfileValue) {
1✔
73
                        Arrays.stream(pidRecord.getPropertyValues(attributeInfo.pid()))
1✔
74
                                .map(this.typeRegistry::queryAsProfile)
1✔
75
                                .forEach(registeredProfileFuture -> registeredProfileFuture.thenApply(registeredProfile -> {
1✔
76
                                    registeredProfile.validateAttributes(pidRecord, this.alwaysAcceptAdditionalAttributes);
1✔
77
                                    return registeredProfile;
1✔
78
                                }));
79
                    }
80
                    return attributeInfo;
1✔
81
                }))
82
                .toList();
1✔
83

84

85
        try {
86
            LOG.trace("Processing all attributes in the record {}.", pidRecord.getPid());
1✔
87
            CompletableFuture.allOf(attributeInfoFutures.toArray(new CompletableFuture<?>[0])).join();
1✔
88
            LOG.trace("Finished processing all attributes in the record {}.", pidRecord.getPid());
1✔
89
        } catch (CompletionException e) {
1✔
90
            LOG.trace("Exception occurred during validation of record {}. Unpack Exception, if required.", pidRecord.getPid(), e);
1✔
NEW
91
            unpackAsyncExceptions(pidRecord, e);
×
NEW
92
            LOG.trace("Exception was not unpacked. Rethrowing.", e);
×
NEW
93
            throw new ExternalServiceException(this.typeRegistry.getRegistryIdentifier());
×
NEW
94
        } catch (CancellationException e) {
×
NEW
95
            unpackAsyncExceptions(pidRecord, e);
×
UNCOV
96
            throw new RecordValidationException(
×
97
                    pidRecord,
NEW
98
                    String.format("Validation task was cancelled for %s. Please report.", pidRecord.getPid()));
×
99
        }
1✔
100
    }
1✔
101

102
    /**
103
     * Checks Exceptions' causes for a RecordValidationExceptions, and throws them, if present.
104
     * <p>
105
     * Usually used to avoid exposing exceptions related to futures.
106
     * @param e the exception to unwrap.
107
     */
108
    private static void unpackAsyncExceptions(PIDRecord pidRecord, Throwable e) {
109
        final int MAX_LEVEL = 10;
1✔
110
        Throwable cause = e;
1✔
111

112
        for (int level = 0; level <= MAX_LEVEL && cause != null; level++) {
1✔
113
            cause = cause.getCause();
1✔
114
            if (cause instanceof RecordValidationException rve) {
1✔
NEW
115
                throw rve;
×
116
            } else if (cause instanceof TypeNotFoundException tnf) {
1✔
117
                throw new RecordValidationException(
1✔
118
                        pidRecord,
119
                        "Type not found: %s".formatted(tnf.getMessage()));
1✔
120
            }
121
        }
UNCOV
122
    }
×
123
}
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