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

CeON / dataverse / 987

pending completion
987

push

jenkins

GitHub
Closes #2339: Change File Labels API (#2347)

124 of 124 new or added lines in 5 files covered. (100.0%)

21199 of 69091 relevant lines covered (30.68%)

0.31 hits per line

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

94.2
/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/dataset/FileLabelsService.java
1
package edu.harvard.iq.dataverse.dataset;
2

3
import edu.harvard.iq.dataverse.DataverseRequestServiceBean;
4
import edu.harvard.iq.dataverse.EjbDataverseEngine;
5
import edu.harvard.iq.dataverse.annotations.PermissionNeeded;
6
import edu.harvard.iq.dataverse.api.dto.FileLabelsChangeOptionsDTO;
7
import edu.harvard.iq.dataverse.engine.command.impl.UpdateDatasetVersionCommand;
8
import edu.harvard.iq.dataverse.ingest.IngestUtil;
9
import edu.harvard.iq.dataverse.interceptors.Restricted;
10
import edu.harvard.iq.dataverse.persistence.datafile.FileMetadata;
11
import edu.harvard.iq.dataverse.persistence.dataset.Dataset;
12
import edu.harvard.iq.dataverse.persistence.dataset.DatasetVersion;
13
import edu.harvard.iq.dataverse.persistence.dataset.DatasetVersionRepository;
14
import edu.harvard.iq.dataverse.persistence.user.Permission;
15

16
import javax.ejb.Stateless;
17
import javax.inject.Inject;
18
import java.util.ArrayList;
19
import java.util.Collections;
20
import java.util.HashMap;
21
import java.util.List;
22
import java.util.Map;
23
import java.util.function.Predicate;
24
import java.util.stream.Collectors;
25

26
@Stateless
27
public class FileLabelsService {
28

29
    private DatasetVersionRepository datasetVersionRepository;
30
    private EjbDataverseEngine dataverseEngine;
31
    private DataverseRequestServiceBean requestServiceBean;
32

33
    // -------------------- CONSTRUCTORS --------------------
34

35
    public FileLabelsService() { }
×
36

37
    @Inject
38
    public FileLabelsService(DatasetVersionRepository datasetVersionRepository, EjbDataverseEngine dataverseEngine,
39
                             DataverseRequestServiceBean requestServiceBean) {
1✔
40
        this.datasetVersionRepository = datasetVersionRepository;
1✔
41
        this.dataverseEngine = dataverseEngine;
1✔
42
        this.requestServiceBean = requestServiceBean;
1✔
43
    }
1✔
44

45
    // -------------------- LOGIC --------------------
46

47
    /**
48
     * Return all file labels from the latest version of dataset, and mark as
49
     * affected those matching the given pattern from {@link FileLabelsChangeOptionsDTO}.
50
     */
51
    @Restricted(@PermissionNeeded(needs = {Permission.ViewUnpublishedDataset}, allRequired = true))
52
    public List<FileLabelInfo> prepareFileLabels(@PermissionNeeded Dataset dataset, FileLabelsChangeOptionsDTO options) {
53
        DatasetVersion latestVersion = dataset.getLatestVersion();
1✔
54
        List<FileMetadata> fileMetadatas = latestVersion.getFileMetadatas();
1✔
55
        Predicate<String> labelMatcher = prepareFileLabelMatcher(options.getPattern());
1✔
56
        List<FileLabelInfo> result = new ArrayList<>();
1✔
57
        for (FileMetadata fileMetadata : fileMetadatas) {
1✔
58
            Long fileId = fileMetadata.getDataFile().getId();
1✔
59
            boolean labelMatches = labelMatcher.test(fileMetadata.getLabel());
1✔
60
            boolean affected = (labelMatches || options.getFilesToIncludeIds().contains(fileId))
1✔
61
                    && !options.getFilesToExcludeIds().contains(fileId);
1✔
62
            result.add(new FileLabelInfo(fileId, fileMetadata.getLabel(), affected));
1✔
63
        }
1✔
64
        Collections.sort(result);
1✔
65
        return result;
1✔
66
    }
67

68
    /**
69
     * The method receives the output of {@link FileLabelsService#prepareFileLabels(Dataset, FileLabelsChangeOptionsDTO)}
70
     * method, and for the entries marked as affected it tries to change the
71
     * label according to the given options. If the label after change differs
72
     * from the original label the entry is marked as affected. At the end
73
     * all new labels are checked against the rest in order to find and handle
74
     * duplicates.
75
     */
76
    public List<FileLabelInfo> changeLabels(List<FileLabelInfo> inputLabels, FileLabelsChangeOptionsDTO options) {
77
        List<FileLabelInfo> changedLabels = new ArrayList<>();
1✔
78
        for (FileLabelInfo labelInfo : inputLabels) {
1✔
79
            if (!labelInfo.isAffected()) {
1✔
80
                changedLabels.add(labelInfo);
1✔
81
                continue;
1✔
82
            }
83
            String fileLabel = labelInfo.getLabel();
1✔
84
            String changed = fileLabel.replaceAll(options.getFrom(), options.getTo());
1✔
85
            boolean affected = !fileLabel.equals(changed);
1✔
86
            changedLabels.add(new FileLabelInfo(labelInfo.getId(), fileLabel,
1✔
87
                    affected ? changed : labelInfo.getLabelAfterChange(), affected));
1✔
88
        }
1✔
89
        // Handle duplicated names: first extract labels that aren't going to change to a map, then for each
90
        // changed label check whether it's a duplicate – if so keep generating new labels until it's not.
91
        // Then add this new label into the map, and check the next label.
92
        Map<String, FileLabelInfo> finalLabels = new HashMap<>(changedLabels.stream()
1✔
93
                .filter(e -> !e.isAffected())
1✔
94
                .collect(Collectors.toMap(FileLabelInfo::getLabel, e -> e)));
1✔
95
        for (FileLabelInfo labelInfo : changedLabels) {
1✔
96
            if (!labelInfo.isAffected()) {
1✔
97
                continue;
1✔
98
            }
99
            String newLabel = labelInfo.getLabelAfterChange();
1✔
100
            while (finalLabels.containsKey(newLabel)) {
1✔
101
                newLabel = IngestUtil.generateNewFileName(newLabel);
1✔
102
            }
103
            finalLabels.put(newLabel, new FileLabelInfo(labelInfo.getId(), labelInfo.getLabel(), newLabel, true));
1✔
104
        }
1✔
105
        return finalLabels.values().stream()
1✔
106
                .sorted()
1✔
107
                .collect(Collectors.toList());
1✔
108
    }
109

110
    /**
111
     * The method receives the output from {@link FileLabelsService#changeLabels(List, FileLabelsChangeOptionsDTO)}
112
     * and saves the labels marked as affected into the draft version of the
113
     * given dataset.
114
     */
115
    @Restricted(@PermissionNeeded(needs = {Permission.EditDataset}, allRequired = true))
116
    public List<FileLabelInfo> updateDataset(@PermissionNeeded Dataset dataset, List<FileLabelInfo> labelsToChange,
117
                                             FileLabelsChangeOptionsDTO options) {
118
        boolean shouldProcess = labelsToChange.stream().anyMatch(FileLabelInfo::isAffected);
1✔
119
        if (options.isPreview() || !shouldProcess) {
1✔
120
            return labelsToChange;
×
121
        }
122
        if (!dataset.getLatestVersion().isWorkingCopy()) {
1✔
123
            dataset.getEditVersion();
×
124
            dataverseEngine.submit(new UpdateDatasetVersionCommand(dataset, requestServiceBean.getDataverseRequest()));
×
125
        }
126
        DatasetVersion editVersion = dataset.getEditVersion();
1✔
127
        Map<Long, FileLabelInfo> labelsToChangeById = labelsToChange.stream()
1✔
128
                .filter(FileLabelInfo::isAffected)
1✔
129
                .collect(Collectors.toMap(FileLabelInfo::getId, l -> l));
1✔
130
        List<FileMetadata> fileMetadatas = editVersion.getFileMetadatas();
1✔
131
        for (FileMetadata metadata : fileMetadatas) {
1✔
132
            Long fileId = metadata.getDataFile().getId();
1✔
133
            if (!labelsToChangeById.containsKey(fileId)) {
1✔
134
                continue;
1✔
135
            }
136
            metadata.setLabel(labelsToChangeById.get(fileId).getLabelAfterChange());
1✔
137
        }
1✔
138
        datasetVersionRepository.save(editVersion);
1✔
139
        return labelsToChange;
1✔
140
    }
141

142
    // -------------------- PRIVATE --------------------
143

144
    /**
145
     * Label matching works in two modes. When wildcard (*) is used, the
146
     * pattern is matched against the whole label and the wildcard stands
147
     * for any number (including zero) of any characters. When the wildcard
148
     * is not present, there is a check whether the label contains the given
149
     * pattern.
150
     */
151
    private Predicate<String> prepareFileLabelMatcher(String labelPattern) {
152
        if ("*".equals(labelPattern)) {
1✔
153
            // that is all-matching pattern, so
154
            return s -> true;
1✔
155
        } else if (labelPattern.contains("*")) {
1✔
156
            String pattern = labelPattern.replaceAll("\\*", ".*");
1✔
157
            return s -> s.matches(pattern);
1✔
158
        } else {
159
            return s -> s.contains(labelPattern);
1✔
160
        }
161
    }
162
}
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