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

IQSS / dataverse / #23077

04 Sep 2024 06:52PM UTC coverage: 20.702% (-0.06%) from 20.759%
#23077

Pull #10781

github

web-flow
Merge 9d8bf0ef8 into 8fd8c18e4
Pull Request #10781: Improved handling of Globus uploads

4 of 417 new or added lines in 15 files covered. (0.96%)

417 existing lines in 9 files now uncovered.

17530 of 84679 relevant lines covered (20.7%)

0.21 hits per line

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

15.34
/src/main/java/edu/harvard/iq/dataverse/DatasetFieldServiceBean.java
1
package edu.harvard.iq.dataverse;
2

3
import java.io.IOException;
4
import java.io.StringReader;
5
import java.net.URI;
6
import java.net.URISyntaxException;
7
import java.net.URLEncoder;
8
import java.nio.charset.StandardCharsets;
9
import java.security.InvalidParameterException;
10
import java.sql.Timestamp;
11
import java.text.MessageFormat;
12
import java.time.Instant;
13
import java.util.ArrayList;
14
import java.util.Arrays;
15
import java.util.HashMap;
16
import java.util.HashSet;
17
import java.util.List;
18
import java.util.Map;
19
import java.util.Set;
20
import java.util.logging.Logger;
21

22
import jakarta.ejb.EJB;
23
import jakarta.ejb.Stateless;
24
import jakarta.inject.Named;
25
import jakarta.json.Json;
26
import jakarta.json.JsonArray;
27
import jakarta.json.JsonArrayBuilder;
28
import jakarta.json.JsonException;
29
import jakarta.json.JsonObject;
30
import jakarta.json.JsonObjectBuilder;
31
import jakarta.json.JsonReader;
32
import jakarta.json.JsonString;
33
import jakarta.json.JsonValue;
34
import jakarta.json.JsonValue.ValueType;
35
import jakarta.persistence.EntityManager;
36
import jakarta.persistence.NoResultException;
37
import jakarta.persistence.NonUniqueResultException;
38
import jakarta.persistence.PersistenceContext;
39
import jakarta.persistence.PersistenceException;
40
import jakarta.persistence.TypedQuery;
41

42
import jakarta.persistence.criteria.*;
43
import org.apache.commons.codec.digest.DigestUtils;
44
import org.apache.commons.httpclient.HttpException;
45
import org.apache.commons.lang3.StringUtils;
46
import org.apache.http.HttpResponse;
47
import org.apache.http.HttpResponseInterceptor;
48
import org.apache.http.client.methods.HttpGet;
49
import org.apache.http.impl.client.CloseableHttpClient;
50
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
51
import org.apache.http.impl.client.HttpClients;
52
import org.apache.http.protocol.HttpContext;
53
import org.apache.http.util.EntityUtils;
54
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
55

56
/**
57
 *
58
 * @author xyang
59
 */
60
@Stateless
61
@Named
62
public class DatasetFieldServiceBean implements java.io.Serializable {
1✔
63

64
    @PersistenceContext(unitName = "VDCNet-ejbPU")
65
    private EntityManager em;
66
    
67
    private static final Logger logger = Logger.getLogger(DatasetFieldServiceBean.class.getCanonicalName());
1✔
68

69
    @EJB
70
    SettingsServiceBean settingsService;
71

72
    private static final String NAME_QUERY = "SELECT dsfType from DatasetFieldType dsfType where dsfType.name= :fieldName";
73
    
74
    /*
75
     * External vocabulary support: These fields cache information from the CVocConf
76
     * setting which controls how Dataverse connects specific metadata block fields
77
     * to third-party Javascripts and external vocabulary services to allow users to
78
     * input values from a vocabulary(ies) those services manage.
79
     */
80
    
81
    //Configuration json keyed by the id of the 'parent' DatasetFieldType 
82
    Map <Long, JsonObject> cvocMap = null;
1✔
83
    
84
    //Configuration json keyed by the id of the child DatasetFieldType specified as the 'term-uri-field'
85
    //Note that for primitive fields, the prent and term-uri-field are the same and these maps have the same entry
86
    Map <Long, JsonObject> cvocMapByTermUri = null;
1✔
87
    
88
    //The hash of the existing CVocConf setting. Used to determine when the setting has changed and it needs to be re-parsed to recreate the cvocMaps
89
    String oldHash = null;
1✔
90

91
    public List<DatasetFieldType> findAllAdvancedSearchFieldTypes() {
92
        return em.createQuery("select object(o) from DatasetFieldType as o where o.advancedSearchFieldType = true and o.title != '' order by o.id", DatasetFieldType.class).getResultList();
×
93
    }
94

95
    public List<DatasetFieldType> findAllFacetableFieldTypes() {
96
         return em.createNamedQuery("DatasetFieldType.findAllFacetable", DatasetFieldType.class)
×
97
                .getResultList();   
×
98
    }
99

100
    public List<DatasetFieldType> findFacetableFieldTypesByMetadataBlock(Long metadataBlockId) {
101
        return em.createNamedQuery("DatasetFieldType.findFacetableByMetadaBlock", DatasetFieldType.class)
×
102
                .setParameter("metadataBlockId", metadataBlockId)
×
103
                .getResultList();
×
104
    }
105

106
    public List<DatasetFieldType> findAllRequiredFields() {
107
        return em.createQuery("select object(o) from DatasetFieldType as o where o.required = true order by o.id", DatasetFieldType.class).getResultList();
×
108
    }
109

110
    public List<DatasetFieldType> findAllOrderedById() {
111
        return em.createQuery("select object(o) from DatasetFieldType as o order by o.id", DatasetFieldType.class).getResultList();
×
112
    }
113

114
    public List<DatasetFieldType> findAllOrderedByName() {
115
        return em.createQuery("select object(o) from DatasetFieldType as o order by o.name", DatasetFieldType.class).getResultList();
×
116
    }
117

118
    public DatasetFieldType find(Object pk) {
119
        return em.find(DatasetFieldType.class, pk);
×
120
    }
121

122
    public DatasetFieldType findByName(String name) {
123
        try {
124
            return  (DatasetFieldType) em.createQuery(NAME_QUERY).setParameter("fieldName", name).getSingleResult();
×
125
        } catch (NoResultException e) {
×
126
            return null;
×
127
        }
128
       
129
    }
130

131
    /**
132
     * Gets the dataset field type, or returns {@code null}. Does not throw
133
     * exceptions.
134
     *
135
     * @param name the name do the field type
136
     * @return the field type, or {@code null}
137
     * @see #findByName(java.lang.String)
138
     */
139
    public DatasetFieldType findByNameOpt(String name) {
140
        try {
141
            return em.createNamedQuery("DatasetFieldType.findByName", DatasetFieldType.class)
×
142
                    .setParameter("name", name)
×
143
                    .getSingleResult();
×
144
        } catch (NoResultException nre) {
×
145
            return null;
×
146
        }
147
    }
148

149
    /* 
150
     * Similar method for looking up foreign metadata field mappings, for metadata
151
     * imports. for these the uniquness of names isn't guaranteed (i.e., there 
152
     * can be a field "author" in many different formats that we want to support), 
153
     * so these have to be looked up by both the field name and the name of the 
154
     * foreign format.
155
     */
156
    public ForeignMetadataFieldMapping findFieldMapping(String formatName, String pathName) {
157
        try {
158
            return em.createNamedQuery("ForeignMetadataFieldMapping.findByPath", ForeignMetadataFieldMapping.class)
×
159
                    .setParameter("formatName", formatName)
×
160
                    .setParameter("xPath", pathName)
×
161
                    .getSingleResult();
×
162
        } catch (NoResultException nre) {
×
163
            return null;
×
164
        }
165
        // TODO: cache looked up results.
166
    }
167

168
    public ControlledVocabularyValue findControlledVocabularyValue(Object pk) {
169
        return em.find(ControlledVocabularyValue.class, pk);
×
170
    }
171
   
172
    /**
173
     * @param dsft The DatasetFieldType in which to look up a
174
     * ControlledVocabularyValue.
175
     * @param strValue String value that may exist in a controlled vocabulary of
176
     * the provided DatasetFieldType.
177
     * @param lenient should we accept alternate spellings for value from mapping table
178
     *
179
     * @return The ControlledVocabularyValue found or null.
180
     */
181
    public ControlledVocabularyValue findControlledVocabularyValueByDatasetFieldTypeAndStrValue(DatasetFieldType dsft, String strValue, boolean lenient) {
182
        TypedQuery<ControlledVocabularyValue> typedQuery = em.createQuery("SELECT OBJECT(o) FROM ControlledVocabularyValue AS o WHERE o.strValue = :strvalue AND o.datasetFieldType = :dsft", ControlledVocabularyValue.class);       
×
183
        typedQuery.setParameter("strvalue", strValue);
×
184
        typedQuery.setParameter("dsft", dsft);
×
185
        try {
186
            ControlledVocabularyValue cvv = typedQuery.getSingleResult();
×
187
            return cvv;
×
188
        } catch (NoResultException | NonUniqueResultException ex) {
×
189
            // if the value isn't found, check in the list of alternate values for this datasetFieldType
UNCOV
190
            TypedQuery<ControlledVocabAlternate> alternateQuery = em.createQuery("SELECT OBJECT(o) FROM ControlledVocabAlternate as o WHERE o.strValue = :strvalue AND o.datasetFieldType = :dsft", ControlledVocabAlternate.class);
×
191
            alternateQuery.setParameter("strvalue", strValue);
×
192
            alternateQuery.setParameter("dsft", dsft);
×
193
            try {
UNCOV
194
                ControlledVocabAlternate alternateValue = alternateQuery.getSingleResult();
×
195
                return alternateValue.getControlledVocabularyValue();
×
196
            } catch (NoResultException | NonUniqueResultException ex2) {
×
197
                return null;
×
198
            }
199
        }
200
    }
201
    
202
    public ControlledVocabAlternate findControlledVocabAlternateByControlledVocabularyValueAndStrValue(ControlledVocabularyValue cvv, String strValue){
UNCOV
203
        TypedQuery<ControlledVocabAlternate> typedQuery = em.createQuery("SELECT OBJECT(o) FROM ControlledVocabAlternate AS o WHERE o.strValue = :strvalue AND o.controlledVocabularyValue = :cvv", ControlledVocabAlternate.class);
×
UNCOV
204
        typedQuery.setParameter("strvalue", strValue);
×
UNCOV
205
        typedQuery.setParameter("cvv", cvv);
×
206
        try {
UNCOV
207
            ControlledVocabAlternate alt = typedQuery.getSingleResult();
×
208
            return alt;
×
209
        } catch (NoResultException e) {
×
210
            return null;
×
UNCOV
211
        } catch (NonUniqueResultException ex){
×
212
           List results = typedQuery.getResultList();
×
213
           return (ControlledVocabAlternate) results.get(0);
×
214
        }
215
    }
216
    
217
    /**
218
     * @param dsft The DatasetFieldType in which to look up a
219
     * ControlledVocabularyValue.
220
     * @param identifier String Identifier that may exist in a controlled vocabulary of
221
     * the provided DatasetFieldType.
222
     *
223
     * @return The ControlledVocabularyValue found or null.
224
     */
225
    public ControlledVocabularyValue findControlledVocabularyValueByDatasetFieldTypeAndIdentifier (DatasetFieldType dsft, String identifier)  {
UNCOV
226
        TypedQuery<ControlledVocabularyValue> typedQuery = em.createQuery("SELECT OBJECT(o) FROM ControlledVocabularyValue AS o WHERE o.identifier = :identifier AND o.datasetFieldType = :dsft", ControlledVocabularyValue.class);       
×
UNCOV
227
        typedQuery.setParameter("identifier", identifier);
×
UNCOV
228
        typedQuery.setParameter("dsft", dsft);
×
229
        try {
UNCOV
230
            ControlledVocabularyValue cvv = typedQuery.getSingleResult();
×
231
            return cvv;
×
232
        } catch (NoResultException | NonUniqueResultException ex) {
×
233
                return null;
×
234
        }
235
    }
236

237
    // return singleton NA Controled Vocabulary Value
238
    public ControlledVocabularyValue findNAControlledVocabularyValue() {
UNCOV
239
        TypedQuery<ControlledVocabularyValue> typedQuery = em.createQuery("SELECT OBJECT(o) FROM ControlledVocabularyValue AS o WHERE o.datasetFieldType is null AND o.strValue = :strvalue", ControlledVocabularyValue.class);
×
UNCOV
240
        typedQuery.setParameter("strvalue", DatasetField.NA_VALUE);
×
UNCOV
241
        return typedQuery.getSingleResult();
×
242
    }
243

244
    public DatasetFieldType save(DatasetFieldType dsfType) {
245
        return em.merge(dsfType);
×
246
    }
247

248
    public MetadataBlock save(MetadataBlock mdb) {
UNCOV
249
        return em.merge(mdb);
×
250
    }
251

252
    public ControlledVocabularyValue save(ControlledVocabularyValue cvv) {
UNCOV
253
        return em.merge(cvv);
×
254
    }
255
    
256
    public ControlledVocabAlternate save(ControlledVocabAlternate alt) {
UNCOV
257
        return em.merge(alt);
×
258
    } 
259
    
260

261
    /**
262
     * This method returns a Map relating DatasetFieldTypes with any external
263
     * vocabulary configuration information associated with them via the CVocConf
264
     * setting. THe mapping is keyed by the DatasetFieldType id for primitive fields
265
     * and, for a compound field, by the id of either the 'parent' DatasetFieldType
266
     * id or of the child field specified as the 'term-uri-field' (the field where
267
     * the URI of the term is stored (and not one of the child fields where the term
268
     * name, vocabulary URI, vocabulary Name or other managed information may go.)
269
     * 
270
     * The map only contains values for DatasetFieldTypes that are configured to use external vocabulary services.
271
     * 
272
     * @param byTermUriField - false: the id of the parent DatasetFieldType is the key, true: the 'term-uri-field' DatasetFieldType id is used as the key
273
     * @return - a map of JsonObjects containing configuration information keyed by the DatasetFieldType id (Long)
274
     */
275
    public Map<Long, JsonObject> getCVocConf(boolean byTermUriField){
276
        
277
        //ToDo - change to an API call to be able to provide feedback if the json is invalid?
278
        String cvocSetting = settingsService.getValueForKey(SettingsServiceBean.Key.CVocConf);
1✔
279
        if (cvocSetting == null || cvocSetting.isEmpty()) {
1✔
UNCOV
280
            oldHash=null;
×
UNCOV
281
            return new HashMap<>();
×
282
        }
283
        String newHash = DigestUtils.md5Hex(cvocSetting);
1✔
284
        if (newHash.equals(oldHash)) {
1✔
285
            return byTermUriField ? cvocMapByTermUri : cvocMap;
×
286
        } 
287
        oldHash=newHash;
1✔
288
        cvocMap=new HashMap<>();
1✔
289
        cvocMapByTermUri=new HashMap<>();
1✔
290
        
291
        try (JsonReader jsonReader = Json.createReader(new StringReader(settingsService.getValueForKey(SettingsServiceBean.Key.CVocConf)))) {
1✔
292
            JsonArray cvocConfJsonArray = jsonReader.readArray();
1✔
293
            for (JsonObject jo : cvocConfJsonArray.getValuesAs(JsonObject.class)) {
1✔
294
                DatasetFieldType dft = findByNameOpt(jo.getString("field-name"));
1✔
295
                if (dft == null) {
1✔
UNCOV
296
                    logger.warning("Ignoring External Vocabulary setting for non-existent field: "
×
UNCOV
297
                      + jo.getString("field-name"));
×
298
                } else {
299
                    cvocMap.put(dft.getId(), jo);
1✔
300
                    if (jo.containsKey("term-uri-field")) {
1✔
301
                        String termUriField = jo.getString("term-uri-field");
1✔
302
                        if (!dft.isHasChildren()) {
1✔
303
                            if (termUriField.equals(dft.getName())) {
1✔
304
                                logger.fine("Found primitive field for term uri : " + dft.getName() + ": " + dft.getId());
1✔
305
                                cvocMapByTermUri.put(dft.getId(), jo);
1✔
306
                            }
307
                        } else {
UNCOV
308
                            DatasetFieldType childdft = findByNameOpt(jo.getString("term-uri-field"));
×
UNCOV
309
                            logger.fine("Found term child field: " + childdft.getName()+ ": " + childdft.getId());
×
UNCOV
310
                            cvocMapByTermUri.put(childdft.getId(), jo);
×
UNCOV
311
                            if (childdft.getParentDatasetFieldType() != dft) {
×
UNCOV
312
                                logger.warning("Term URI field (" + childdft.getDisplayName() + ") not a child of parent: "
×
313
                                  + dft.getDisplayName());
×
314
                            }
315
                        }
316
                        if (dft == null) {
1✔
317
                            logger.warning("Ignoring External Vocabulary setting for non-existent child field: "
×
318
                              + jo.getString("term-uri-field"));
×
319
                        }
320
                    }
321
                    if (jo.containsKey("managed-fields")) {
1✔
322
                        JsonObject managedFields = jo.getJsonObject("managed-fields");
1✔
323
                        for (String s : managedFields.keySet()) {
1✔
324
                            dft = findByNameOpt(managedFields.getString(s));
1✔
325
                            if (dft == null) {
1✔
326
                                logger.warning("Ignoring External Vocabulary setting for non-existent child field: "
1✔
327
                                        + managedFields.getString(s));
1✔
328
                            } else {
UNCOV
329
                                logger.fine("Found: " + dft.getName());
×
330
                            }
331
                        }
1✔
332
                    }
333
                }
334
            }
1✔
UNCOV
335
            } catch(JsonException e) {
×
UNCOV
336
                logger.warning("Ignoring External Vocabulary setting due to parsing error: " + e.getLocalizedMessage());
×
337
            }
1✔
338
        return byTermUriField ? cvocMapByTermUri : cvocMap;
1✔
339
    }
340

341
    /**
342
     * Adds information about the external vocabulary term being used in this DatasetField to the ExternalVocabularyValue table if it doesn't already exist.
343
     * @param df - the primitive/parent compound field containing a newly saved value
344
     */
345
    public void registerExternalVocabValues(DatasetField df) {
UNCOV
346
        DatasetFieldType dft = df.getDatasetFieldType();
×
UNCOV
347
        logger.fine("Registering for field: " + dft.getName());
×
UNCOV
348
        JsonObject cvocEntry = getCVocConf(true).get(dft.getId());
×
UNCOV
349
        if (dft.isPrimitive()) {
×
UNCOV
350
            List<DatasetField> siblingsDatasetFields = new ArrayList<>();
×
351
            if(dft.getParentDatasetFieldType()!=null) {
×
352
                siblingsDatasetFields = df.getParentDatasetFieldCompoundValue().getChildDatasetFields();
×
353
            }
354
            for (DatasetFieldValue dfv : df.getDatasetFieldValues()) {
×
355
                registerExternalTerm(cvocEntry, dfv.getValue(), siblingsDatasetFields);
×
356
            }
×
357
        } else {
×
UNCOV
358
            if (df.getDatasetFieldType().isCompound()) {
×
359
                DatasetFieldType termdft = findByNameOpt(cvocEntry.getString("term-uri-field"));
×
360
                for (DatasetFieldCompoundValue cv : df.getDatasetFieldCompoundValues()) {
×
361
                    for (DatasetField cdf : cv.getChildDatasetFields()) {
×
362
                        logger.fine("Found term uri field type id: " + cdf.getDatasetFieldType().getId());
×
363
                        if (cdf.getDatasetFieldType().equals(termdft)) {
×
364
                            registerExternalTerm(cvocEntry, cdf.getValue(), cv.getChildDatasetFields());
×
365
                        }
366
                    }
×
367
                }
×
368
            }
369
        }
UNCOV
370
    }
×
371

372
    /**
373
     * Retrieves indexable strings from a cached externalvocabularyvalue entry filtered through retrieval-filtering configuration.
374
     * <p>
375
     * This method assumes externalvocabularyvalue entries have been filtered and that they contain a single JsonObject.
376
     * Cases Handled : A String, an Array of Strings, an Array of Objects with "value" or "content" keys, an Object with one or more entries that have String values or Array values with a set of String values.
377
     * The string(s), or the "value/content"s for each language are added to the set.
378
     * Retrieved string values are indexed in the term-uri-field (parameter defined in CVOC configuration) by default, or in the field specified by an optional "indexIn" parameter in the retrieval-filtering defined in the CVOC configuration.
379
     * <p>
380
     * Any parsing error results in no entries (there can be unfiltered entries with
381
     * unknown structure - getting some strings from such an entry could give fairly
382
     * random info that would be bad to addd for searches, etc.)
383
     *
384
     * @param termUri unique identifier to search in database
385
     * @param cvocEntry related cvoc configuration
386
     * @param indexingField name of solr field that will be filled with getStringsFor while indexing
387
     * @return - a set of indexable strings
388
     */
389
    public Set<String> getIndexableStringsByTermUri(String termUri, JsonObject cvocEntry, String indexingField) {
390
        Set<String> strings = new HashSet<>();
1✔
391
        JsonObject jo = getExternalVocabularyValue(termUri);
1✔
392
        JsonObject filtering = cvocEntry.getJsonObject("retrieval-filtering");
1✔
393
        String termUriField = cvocEntry.getJsonString("term-uri-field").getString();
1✔
394

395
        if (jo != null) {
1✔
396
            try {
397
                for (String key : jo.keySet()) {
1✔
398
                    String indexIn = filtering.getJsonObject(key).getString("indexIn", null);
1✔
399
                    // Either we are in mapping mode so indexingField (solr field) equals indexIn (cvoc config)
400
                    // Or we are in default mode indexingField is termUriField, indexIn is not defined then only termName and personName keys are used
401
                    if (indexingField.equals(indexIn) ||
1✔
402
                            (indexIn == null && termUriField.equals(indexingField) && (key.equals("termName")) || key.equals("personName"))) {
1✔
403
                        JsonValue jv = jo.get(key);
1✔
404
                        if (jv.getValueType().equals(JsonValue.ValueType.STRING)) {
1✔
405
                            logger.fine("adding " + jo.getString(key) + " for " + termUri);
1✔
406
                            strings.add(jo.getString(key));
1✔
407
                        } else if (jv.getValueType().equals(JsonValue.ValueType.ARRAY)) {
1✔
408
                            JsonArray jarr = jv.asJsonArray();
1✔
409
                            for (int i = 0; i < jarr.size(); i++) {
1✔
410
                                if (jarr.get(i).getValueType().equals(JsonValue.ValueType.STRING)) {
1✔
UNCOV
411
                                    strings.add(jarr.getString(i));
×
412
                                } else if (jarr.get(i).getValueType().equals(ValueType.OBJECT)) { // This condition handles SKOMOS format like [{"lang": "en","value": "non-apis bee"},{"lang": "fr","value": "abeille non apis"}]
1✔
413
                                    JsonObject entry = jarr.getJsonObject(i);
1✔
414
                                    if (entry.containsKey("value")) {
1✔
415
                                        logger.fine("adding " + entry.getString("value") + " for " + termUri);
1✔
416
                                        strings.add(entry.getString("value"));
1✔
UNCOV
417
                                    } else if (entry.containsKey("content")) {
×
UNCOV
418
                                        logger.fine("adding " + entry.getString("content") + " for " + termUri);
×
UNCOV
419
                                        strings.add(entry.getString("content"));
×
420

421
                                    }
422
                                }
423
                            }
424
                        } else if (jv.getValueType().equals(JsonValue.ValueType.OBJECT)) {
1✔
425
                            JsonObject joo = jv.asJsonObject();
1✔
426
                            for (Map.Entry<String, JsonValue> entry : joo.entrySet()) {
1✔
427
                                if (entry.getValue().getValueType().equals(JsonValue.ValueType.STRING)) { // This condition handles format like { "fr": "association de quartier", "en": "neighborhood associations"}
1✔
428
                                    logger.fine("adding " + joo.getString(entry.getKey()) + " for " + termUri);
1✔
429
                                    strings.add(joo.getString(entry.getKey()));
1✔
430
                                } else if (entry.getValue().getValueType().equals(ValueType.ARRAY)) { // This condition handles format like {"en": ["neighbourhood societies"]}
1✔
431
                                    JsonArray jarr = entry.getValue().asJsonArray();
1✔
432
                                    for (int i = 0; i < jarr.size(); i++) {
1✔
433
                                        if (jarr.get(i).getValueType().equals(JsonValue.ValueType.STRING)) {
1✔
434
                                            logger.fine("adding " + jarr.getString(i) + " for " + termUri);
1✔
435
                                            strings.add(jarr.getString(i));
1✔
436
                                        }
437
                                    }
438
                                }
439
                            }
1✔
440
                        }
441
                    }
442
                }
1✔
UNCOV
443
            } catch (Exception e) {
×
UNCOV
444
                logger.warning(
×
UNCOV
445
                        "Problem interpreting external vocab value for uri: " + termUri + " : " + e.getMessage());
×
UNCOV
446
                return new HashSet<String>();
×
447
            }
1✔
448
        }
449
        logger.fine("Returning " + String.join(",", strings) + " for " + termUri);
1✔
450
        return strings;
1✔
451
    }
452

453
    /**
454
     * Perform a query to retrieve a cached value from the externalvocabularvalue table
455
     * @param termUri
456
     * @return - the entry's value as a JsonObject
457
     */
458
    public JsonObject getExternalVocabularyValue(String termUri) {
459
        try {
UNCOV
460
            ExternalVocabularyValue evv = em
×
UNCOV
461
                    .createQuery("select object(o) from ExternalVocabularyValue as o where o.uri=:uri",
×
462
                            ExternalVocabularyValue.class)
UNCOV
463
                    .setParameter("uri", termUri).getSingleResult();
×
UNCOV
464
            String valString = evv.getValue();
×
465
            try (JsonReader jr = Json.createReader(new StringReader(valString))) {
×
466
                return jr.readObject();
×
UNCOV
467
            } catch (Exception e) {
×
468
                logger.warning("Problem parsing external vocab value for uri: " + termUri + " : " + e.getMessage());
×
469
            }
470
        } catch (NoResultException nre) {
×
471
            logger.warning("No external vocab value for uri: " + termUri);
×
472
        }
×
473
        return null;
×
474
    }
475

476
    /**
477
     * Perform a call to the external service to retrieve information about the term URI
478
     *
479
     * @param cvocEntry             - the configuration for the DatasetFieldType associated with this term
480
     * @param term                  - the term uri as a string
481
     * @param relatedDatasetFields  - siblings or childs of the term
482
     */
483
    public void registerExternalTerm(JsonObject cvocEntry, String term, List<DatasetField> relatedDatasetFields) {
UNCOV
484
        String retrievalUri = cvocEntry.getString("retrieval-uri");
×
UNCOV
485
        String termUriFieldName = cvocEntry.getString("term-uri-field");
×
UNCOV
486
        String prefix = cvocEntry.getString("prefix", null);
×
UNCOV
487
        if(StringUtils.isBlank(term)) {
×
UNCOV
488
            logger.fine("Ignoring blank term");
×
489
            return;
×
490
        }
491

492
        boolean isExternal = false;
×
493
        JsonObject vocabs = cvocEntry.getJsonObject("vocabs");
×
494
        for (String key: vocabs.keySet()) {
×
UNCOV
495
            JsonObject vocab = vocabs.getJsonObject(key);
×
UNCOV
496
            if (vocab.containsKey("uriSpace")) {
×
497
                if (term.startsWith(vocab.getString("uriSpace"))) {
×
498
                    isExternal = true;
×
499
                    break;
×
500
                }
501
            }
502
        }
×
503
        if (!isExternal) {
×
504
            logger.fine("Ignoring free text entry: " + term);
×
UNCOV
505
            return;
×
506
        }
507
        logger.fine("Registering term: " + term);
×
508
        try {
509
            //Assure the term is in URI form - should be if the uriSpace entry was correct
510
            new URI(term);
×
UNCOV
511
            ExternalVocabularyValue evv = null;
×
512
            try {
UNCOV
513
                evv = em.createQuery("select object(o) from ExternalVocabularyValue as o where o.uri=:uri",
×
UNCOV
514
                        ExternalVocabularyValue.class).setParameter("uri", term).getSingleResult();
×
515
            } catch (NoResultException nre) {
×
516
                evv = new ExternalVocabularyValue(term, null);
×
UNCOV
517
            }
×
518
            if (evv.getValue() == null) {
×
519
                String adjustedTerm = (prefix==null)? term: term.replace(prefix, "");
×
520

521
                try {
522
                    retrievalUri = tryToReplaceRetrievalUriParam(retrievalUri, "0", adjustedTerm);
×
523
                    retrievalUri = tryToReplaceRetrievalUriParam(retrievalUri, termUriFieldName, adjustedTerm);
×
524
                    for (DatasetField f : relatedDatasetFields) {
×
UNCOV
525
                        retrievalUri = tryToReplaceRetrievalUriParam(retrievalUri, f.getDatasetFieldType().getName(), f.getValue());
×
UNCOV
526
                    }
×
527
                } catch (InvalidParameterException e) {
×
528
                    logger.warning("InvalidParameterException in tryReplaceRetrievalUriParam : " + e.getMessage());
×
529
                    return;
×
530
                }
×
531
                if (retrievalUri.contains("{")) {
×
532
                    logger.severe("Retrieval URI still contains unreplaced parameter :" + retrievalUri);
×
533
                    return;
×
534
                }
535

536
                logger.fine("Didn't find " + term + ", calling " + retrievalUri);
×
537
                try (CloseableHttpClient httpClient = HttpClients.custom()
×
538
                        .addInterceptorLast(new HttpResponseInterceptor() {
×
539
                            @Override
540
                            public void process(HttpResponse response, HttpContext context) throws HttpException, IOException {
541
                                int statusCode = response.getStatusLine().getStatusCode();
×
542
                                if (statusCode == 504) {
×
543
                                    //Throwing an exception triggers the retry handler
UNCOV
544
                                    throw new IOException("Retry due to 504 response");
×
545
                                }
546
                            }
×
547
                        })
548
                        //The retry handler will also do retries for network errors/other things that cause an IOException
549
                        .setRetryHandler(new DefaultHttpRequestRetryHandler(3, false))
×
UNCOV
550
                        .build()) {
×
551
                    HttpGet httpGet = new HttpGet(retrievalUri);
×
552
                    //application/json+ld is for backward compatibility
UNCOV
553
                    httpGet.addHeader("Accept", "application/ld+json, application/json+ld, application/json");
×
554
                    //Adding others custom HTTP request headers if exists
555
                    final JsonObject headers = cvocEntry.getJsonObject("headers");
×
556
                    if (headers != null) {
×
UNCOV
557
                        final Set<String> headerKeys = headers.keySet();
×
558
                        for (final String hKey: headerKeys) {
×
UNCOV
559
                            httpGet.addHeader(hKey, headers.getString(hKey));
×
560
                        }
×
561
                    }
562
                    HttpResponse response = httpClient.execute(httpGet);
×
563
                    String data = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
×
564
                    int statusCode = response.getStatusLine().getStatusCode();
×
565
                    if (statusCode == 200) {
×
UNCOV
566
                        logger.fine("Returned data: " + data);
×
567
                        try (JsonReader jsonReader = Json.createReader(new StringReader(data))) {
×
568
                            String dataObj = filterResponse(cvocEntry, jsonReader.readObject(), term).toString();
×
569
                            evv.setValue(dataObj);
×
570
                            evv.setLastUpdateDate(Timestamp.from(Instant.now()));
×
571
                            logger.fine("JsonObject: " + dataObj);
×
572
                            em.merge(evv);
×
573
                            em.flush();
×
574
                            logger.fine("Wrote value for term: " + term);
×
575
                        } catch (JsonException je) {
×
576
                            logger.severe("Error retrieving: " + retrievalUri + " : " + je.getMessage());
×
577
                        } catch (PersistenceException e) {
×
578
                            logger.fine("Problem persisting: " + retrievalUri + " : " + e.getMessage());
×
579
                        }
×
580
                    } else {
581
                        logger.severe("Received response code : " + statusCode + " when retrieving " + retrievalUri
×
582
                                + " : " + data);
583
                    }
584
                } catch (IOException ioe) {
×
UNCOV
585
                    logger.severe("IOException when retrieving url: " + retrievalUri + " : " + ioe.getMessage());
×
586
                }
×
587
            }
UNCOV
588
        } catch (URISyntaxException e) {
×
589
            logger.fine("Term is not a URI: " + term);
×
590
        }
×
591
    }
×
592

593
    private String tryToReplaceRetrievalUriParam(String retrievalUri, String paramName, String value) throws InvalidParameterException {
594

595
        if(StringUtils.isBlank(paramName)) {
×
596
            throw new InvalidParameterException("Empty or null paramName is not allowed while replacing retrieval uri parameter");
×
597
        }
598

UNCOV
599
        if(retrievalUri.contains(paramName)) {
×
600
            logger.fine("Parameter {" + paramName + "} found in retrievalUri");
×
601

UNCOV
602
            if(StringUtils.isBlank(value)) {
×
UNCOV
603
                throw new InvalidParameterException("Empty or null value is not allowed while replacing retrieval uri parameter");
×
604
            }
605

UNCOV
606
            if(retrievalUri.contains("encodeUrl:" + paramName)) {
×
607
                retrievalUri = retrievalUri.replace("{encodeUrl:"+paramName+"}", URLEncoder.encode(value, StandardCharsets.UTF_8));
×
608
            } else {
UNCOV
609
                retrievalUri = retrievalUri.replace("{"+paramName+"}", value);
×
610
            }
611
        } else {
612
            logger.fine("Parameter {" + paramName + "} not found in retrievalUri");
×
613
        }
614

UNCOV
615
        return retrievalUri;
×
616
    }
617

618
    /**
619
     * Parse the raw value returned by an external service for a give term uri and
620
     * filter it according to the 'retrieval-filtering' configuration for this
621
     * DatasetFieldType, creating a Json value with the specified structure
622
     *
623
     * @param cvocEntry - the config for this DatasetFieldType
624
     * @param readObject - the raw response from the service
625
     * @param termUri - the term uri
626
     * @return - a JsonObject with the structure defined by the filtering configuration
627
     */
628
    private JsonObject filterResponse(JsonObject cvocEntry, JsonObject readObject, String termUri) {
629

UNCOV
630
        JsonObjectBuilder job = Json.createObjectBuilder();
×
UNCOV
631
        JsonObject filtering = cvocEntry.getJsonObject("retrieval-filtering");
×
UNCOV
632
        logger.fine("RF: " + filtering.toString());
×
UNCOV
633
        JsonObject managedFields = cvocEntry.getJsonObject("managed-fields");
×
UNCOV
634
        logger.fine("MF: " + managedFields.toString());
×
635
        for (String filterKey : filtering.keySet()) {
×
636
            if (!filterKey.equals("@context")) {
×
637
                try {
638
                    JsonObject filter = filtering.getJsonObject(filterKey);
×
639
                    logger.fine("F: " + filter.toString());
×
640
                    JsonArray params = filter.getJsonArray("params");
×
641
                    if (params == null) {
×
UNCOV
642
                        params = Json.createArrayBuilder().build();
×
643
                    }
644
                    logger.fine("Params: " + params.toString());
×
645
                    List<Object> vals = new ArrayList<Object>();
×
646
                    for (int i = 0; i < params.size(); i++) {
×
647
                        String param = params.getString(i);
×
UNCOV
648
                        if (param.startsWith("/")) {
×
649
                            // Remove leading /
650
                            param = param.substring(1);
×
651
                            String[] pathParts = param.split("/");
×
652
                            logger.fine("PP: " + String.join(", ", pathParts));
×
653
                            JsonValue curPath = readObject;
×
UNCOV
654
                            vals.add(i, processPathSegment(0, pathParts, curPath, termUri));
×
655
                            logger.fine("Added param value: " + i + ": " + vals.get(i));
×
656
                        } else {
×
657
                            logger.fine("Param is: " + param);
×
658
                            // param is not a path - either a reference to the term URI
659
                            if (param.equals("@id")) {
×
660
                                logger.fine("Adding id param: " + termUri);
×
661
                                vals.add(i, termUri);
×
662
                            } else {
663
                                // or a hardcoded value
664
                                logger.fine("Adding hardcoded param: " + param);
×
665
                                vals.add(i, param);
×
666
                            }
667
                        }
668
                    }
669
                    // Shortcut: nominally using a pattern of {0} and a param that is @id or
670
                    // hardcoded value allows the same options as letting the pattern itself be @id
671
                    // or a hardcoded value
UNCOV
672
                    String pattern = filter.getString("pattern");
×
UNCOV
673
                    logger.fine("Pattern: " + pattern);
×
UNCOV
674
                    if (pattern.equals("@id")) {
×
UNCOV
675
                        logger.fine("Added #id pattern: " + filterKey + ": " + termUri);
×
UNCOV
676
                        job.add(filterKey, termUri);
×
677
                    } else if (pattern.contains("{")) {
×
678
                        if (pattern.equals("{0}")) {
×
679
                            if (vals.get(0) instanceof JsonArray) {
×
680
                                job.add(filterKey, (JsonArray) vals.get(0));
×
681
                            } else if (vals.get(0) instanceof JsonObject) {
×
682
                                job.add(filterKey, (JsonObject) vals.get(0));
×
683
                            } else {
684
                                job.add(filterKey, (String) vals.get(0));
×
685
                            }
686
                        } else {
687
                            String result = MessageFormat.format(pattern, vals.toArray());
×
UNCOV
688
                            logger.fine("Result: " + result);
×
689
                            job.add(filterKey, result);
×
UNCOV
690
                            logger.fine("Added : " + filterKey + ": " + result);
×
UNCOV
691
                        }
×
692
                    } else {
693
                        logger.fine("Added hardcoded pattern: " + filterKey + ": " + pattern);
×
694
                        job.add(filterKey, pattern);
×
695
                    }
696
                } catch (Exception e) {
×
UNCOV
697
                    logger.warning("External Vocabulary: " + termUri + " - Failed to find value for " + filterKey + ": "
×
698
                            + e.getMessage());
×
699
                    e.printStackTrace();
×
UNCOV
700
                }
×
701
            }
702
        }
×
703
        JsonObject filteredResponse = job.build();
×
704
        if(filteredResponse.isEmpty()) {
×
705
            logger.severe("Unable to filter response for term: " + termUri + ",  received: " + readObject.toString());
×
706
            //Better to store nothing in this case so unknown values don't propagate to exported metadata (we'll just send the termUri itself in those cases).
707
            return null;
×
708
        } else {
709
            return filteredResponse;
×
710
        }
711
    }
712

713
    Object processPathSegment(int index, String[] pathParts, JsonValue curPath, String termUri) {
714
        if (index < pathParts.length - 1) {
×
UNCOV
715
            if (pathParts[index].contains("=")) {
×
UNCOV
716
                JsonArray arr = ((JsonArray) curPath);
×
UNCOV
717
                String[] keyVal = pathParts[index].split("=");
×
UNCOV
718
                logger.fine("Looking for object where " + keyVal[0] + " is " + keyVal[1]);
×
719
                String expected = keyVal[1];
×
720

721
                if (!expected.equals("*")) {
×
722
                    if (expected.equals("@id")) {
×
723
                        expected = termUri;
×
724
                    }
UNCOV
725
                    for (int k = 0; k < arr.size(); k++) {
×
726
                        JsonObject jo = arr.getJsonObject(k);
×
727
                        String val = jo.getString(keyVal[0]);
×
728
                        if (val.equals(expected)) {
×
UNCOV
729
                            logger.fine("Found: " + jo.toString());
×
730
                            curPath = jo;
×
731
                            return processPathSegment(index + 1, pathParts, curPath, termUri);
×
732
                        }
733
                    }
734
                } else {
735
                    JsonArrayBuilder parts = Json.createArrayBuilder();
×
736
                    for (JsonValue subPath : arr) {
×
UNCOV
737
                        if (subPath instanceof JsonObject) {
×
UNCOV
738
                            JsonValue nextValue = ((JsonObject) subPath).get(keyVal[0]);
×
UNCOV
739
                            Object obj = processPathSegment(index + 1, pathParts, nextValue, termUri);
×
740
                            if (obj instanceof String) {
×
741
                                parts.add((String) obj);
×
742
                            } else {
743
                                parts.add((JsonValue) obj);
×
744
                            }
745
                        }
746
                    }
×
UNCOV
747
                    return parts.build();
×
748
                }
749

UNCOV
750
            } else {
×
751
                curPath = ((JsonObject) curPath).get(pathParts[index]);
×
752
                logger.fine("Found next Path object " + curPath.toString());
×
UNCOV
753
                return processPathSegment(index + 1, pathParts, curPath, termUri);
×
754
            }
755
        } else {
756
            logger.fine("Last segment: " + curPath.toString());
×
757
            logger.fine("Looking for : " + pathParts[index]);
×
758
            JsonValue jv = ((JsonObject) curPath).get(pathParts[index]);
×
UNCOV
759
            ValueType type =jv.getValueType(); 
×
UNCOV
760
            if (type.equals(JsonValue.ValueType.STRING)) {
×
761
                return ((JsonString) jv).getString();
×
762
            } else if (jv.getValueType().equals(JsonValue.ValueType.ARRAY)) {
×
763
                return jv;
×
764
            } else if (jv.getValueType().equals(JsonValue.ValueType.OBJECT)) {
×
765
                return jv;
×
766
            }
767
        }
768

769
        return null;
×
770

771
    }
772
   
773
    /**
774
     * Supports validation of externally controlled values. If the value is a URI it
775
     * must be in the namespace (start with) one of the uriSpace values of an
776
     * allowed vocabulary. If free text entries are allowed for this field (per the
777
     * configuration), non-uri entries are also assumed valid.
778
     * 
779
     * @param dft
780
     * @param value
781
     * @return - true: valid
782
     */
783
    public boolean isValidCVocValue(DatasetFieldType dft, String value) {
UNCOV
784
        JsonObject jo = getCVocConf(true).get(dft.getId());
×
UNCOV
785
        JsonObject vocabs = jo.getJsonObject("vocabs");
×
UNCOV
786
        boolean valid = false;
×
UNCOV
787
        boolean couldBeFreeText = true;
×
UNCOV
788
        boolean freeTextAllowed = jo.getBoolean("allow-free-text", false);
×
789
        for (String vocabName : vocabs.keySet()) {
×
790
            JsonObject vocab = vocabs.getJsonObject(vocabName);
×
791
            String baseUri = vocab.getString("uriSpace");
×
792
            if (value.startsWith(baseUri)) {
×
793
                valid = true;
×
794
                break;
×
795
            } else {
796
                String protocol = baseUri.substring(baseUri.indexOf("://") + 3);
×
797
                if (value.startsWith(protocol)) {
×
798
                    couldBeFreeText = false;
×
799
                    // No break because we need to check for conflicts with all vocabs
800
                }
801
            }
802
        }
×
803
        if (!valid) {
×
UNCOV
804
            if (freeTextAllowed && couldBeFreeText) {
×
UNCOV
805
                valid = true;
×
806
            }
807
        }
808
        return valid;
×
809
    }
810
    
811
    public List<String> getVocabScripts( Map<Long, JsonObject> cvocConf) {
812
        //ToDo - only return scripts that are needed (those fields are set on display pages, those blocks/fields are allowed in the Dataverse collection for create/edit)?
813
        Set<String> scripts = new HashSet<String>();
×
UNCOV
814
        for (JsonObject jo : cvocConf.values()) {
×
815
            // Allow either a single script (a string) or an array of scripts (used, for
816
            // example, to allow use of the common cvocutils.js script along with a main
817
            // script for the field.)
818
            JsonValue scriptValue = jo.get("js-url");
×
819
            ValueType scriptType = scriptValue.getValueType();
×
UNCOV
820
            if (scriptType.equals(ValueType.STRING)) {
×
UNCOV
821
                scripts.add(((JsonString) scriptValue).getString());
×
UNCOV
822
            } else if (scriptType.equals(ValueType.ARRAY)) {
×
823
                JsonArray scriptArray = ((JsonArray) scriptValue);
×
824
                for (int i = 0; i < scriptArray.size(); i++) {
×
825
                    scripts.add(scriptArray.getString(i));
×
826
                }
827
            }
828
        }
×
829
        String customScript = settingsService.getValueForKey(SettingsServiceBean.Key.ControlledVocabularyCustomJavaScript);
×
830
        if (customScript != null && !customScript.isEmpty()) {
×
UNCOV
831
            scripts.add(customScript);
×
832
        }
833
        return Arrays.asList(scripts.toArray(new String[0]));
×
834
    }
835

836
    public String getFieldLanguage(String languages, String localeCode) {
837
        // If the fields list of supported languages contains the current locale (e.g.
838
        // the lang of the UI, or the current metadata input/display lang (tbd)), use
839
        // that. Otherwise, return the first in the list
UNCOV
840
        String[] langStrings = languages.split("\\s*,\\s*");
×
UNCOV
841
        if (langStrings.length > 0) {
×
UNCOV
842
            if (Arrays.asList(langStrings).contains(localeCode)) {
×
UNCOV
843
                return localeCode;
×
844
            } else {
845
                return langStrings[0];
×
846
            }
847
        }
848
        return null;
×
849
    }
850

851
    public List<DatasetFieldType> findAllDisplayedOnCreateInMetadataBlock(MetadataBlock metadataBlock) {
UNCOV
852
        CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
×
853
        CriteriaQuery<DatasetFieldType> criteriaQuery = criteriaBuilder.createQuery(DatasetFieldType.class);
×
854

UNCOV
855
        Root<MetadataBlock> metadataBlockRoot = criteriaQuery.from(MetadataBlock.class);
×
UNCOV
856
        Root<DatasetFieldType> datasetFieldTypeRoot = criteriaQuery.from(DatasetFieldType.class);
×
857

858
        Predicate requiredInDataversePredicate = buildRequiredInDataversePredicate(criteriaBuilder, datasetFieldTypeRoot);
×
859

860
        criteriaQuery.where(
×
861
                criteriaBuilder.and(
×
UNCOV
862
                        criteriaBuilder.equal(metadataBlockRoot.get("id"), metadataBlock.getId()),
×
863
                        datasetFieldTypeRoot.in(metadataBlockRoot.get("datasetFieldTypes")),
×
UNCOV
864
                        criteriaBuilder.or(
×
865
                                criteriaBuilder.isTrue(datasetFieldTypeRoot.get("displayOnCreate")),
×
866
                                requiredInDataversePredicate
867
                        )
868
                )
869
        );
870

UNCOV
871
        criteriaQuery.select(datasetFieldTypeRoot).distinct(true);
×
872

UNCOV
873
        TypedQuery<DatasetFieldType> typedQuery = em.createQuery(criteriaQuery);
×
UNCOV
874
        return typedQuery.getResultList();
×
875
    }
876

877
    public List<DatasetFieldType> findAllInMetadataBlockAndDataverse(MetadataBlock metadataBlock, Dataverse dataverse, boolean onlyDisplayedOnCreate) {
878
        CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
×
879
        CriteriaQuery<DatasetFieldType> criteriaQuery = criteriaBuilder.createQuery(DatasetFieldType.class);
×
880

UNCOV
881
        Root<MetadataBlock> metadataBlockRoot = criteriaQuery.from(MetadataBlock.class);
×
UNCOV
882
        Root<DatasetFieldType> datasetFieldTypeRoot = criteriaQuery.from(DatasetFieldType.class);
×
883
        Root<Dataverse> dataverseRoot = criteriaQuery.from(Dataverse.class);
×
884

885
        // Join Dataverse with DataverseFieldTypeInputLevel on the "dataverseFieldTypeInputLevels" attribute, using a LEFT JOIN.
886
        Join<Dataverse, DataverseFieldTypeInputLevel> datasetFieldTypeInputLevelJoin = dataverseRoot.join("dataverseFieldTypeInputLevels", JoinType.LEFT);
×
887

888
        // Define a predicate to include DatasetFieldTypes that are marked as included in the input level.
UNCOV
889
        Predicate includedAsInputLevelPredicate = criteriaBuilder.and(
×
UNCOV
890
                criteriaBuilder.equal(datasetFieldTypeRoot, datasetFieldTypeInputLevelJoin.get("datasetFieldType")),
×
891
                criteriaBuilder.isTrue(datasetFieldTypeInputLevelJoin.get("include"))
×
892
        );
893

894
        // Define a predicate to include DatasetFieldTypes that are marked as required in the input level.
895
        Predicate requiredAsInputLevelPredicate = criteriaBuilder.and(
×
896
                criteriaBuilder.equal(datasetFieldTypeRoot, datasetFieldTypeInputLevelJoin.get("datasetFieldType")),
×
UNCOV
897
                criteriaBuilder.isTrue(datasetFieldTypeInputLevelJoin.get("required"))
×
898
        );
899

900
        // Create a subquery to check for the absence of a specific DataverseFieldTypeInputLevel.
901
        Subquery<Long> subquery = criteriaQuery.subquery(Long.class);
×
902
        Root<DataverseFieldTypeInputLevel> subqueryRoot = subquery.from(DataverseFieldTypeInputLevel.class);
×
UNCOV
903
        subquery.select(criteriaBuilder.literal(1L))
×
UNCOV
904
                .where(
×
UNCOV
905
                        criteriaBuilder.equal(subqueryRoot.get("dataverse"), dataverseRoot),
×
906
                        criteriaBuilder.equal(subqueryRoot.get("datasetFieldType"), datasetFieldTypeRoot)
×
907
                );
908

909
        // Define a predicate to exclude DatasetFieldTypes that have no associated input level (i.e., the subquery does not return a result).
910
        Predicate hasNoInputLevelPredicate = criteriaBuilder.not(criteriaBuilder.exists(subquery));
×
911

912
        // Define a predicate to include the required fields in Dataverse.
UNCOV
913
        Predicate requiredInDataversePredicate = buildRequiredInDataversePredicate(criteriaBuilder, datasetFieldTypeRoot);
×
914

915
        // Define a predicate for displaying DatasetFieldTypes on create.
916
        // If onlyDisplayedOnCreate is true, include fields that:
917
        // - Are either marked as displayed on create OR marked as required, OR
918
        // - Are required according to the input level.
919
        // Otherwise, use an always-true predicate (conjunction).
UNCOV
920
        Predicate displayedOnCreatePredicate = onlyDisplayedOnCreate
×
UNCOV
921
                ? criteriaBuilder.or(
×
UNCOV
922
                criteriaBuilder.or(
×
UNCOV
923
                        criteriaBuilder.isTrue(datasetFieldTypeRoot.get("displayOnCreate")),
×
924
                        requiredInDataversePredicate
925
                ),
926
                requiredAsInputLevelPredicate
927
        )
928
                : criteriaBuilder.conjunction();
×
929

930
        // Build the final WHERE clause by combining all the predicates.
UNCOV
931
        criteriaQuery.where(
×
UNCOV
932
                criteriaBuilder.equal(dataverseRoot.get("id"), dataverse.getId()), // Match the Dataverse ID.
×
933
                criteriaBuilder.equal(metadataBlockRoot.get("id"), metadataBlock.getId()), // Match the MetadataBlock ID.
×
UNCOV
934
                metadataBlockRoot.in(dataverseRoot.get("metadataBlocks")), // Ensure the MetadataBlock is part of the Dataverse.
×
UNCOV
935
                datasetFieldTypeRoot.in(metadataBlockRoot.get("datasetFieldTypes")), // Ensure the DatasetFieldType is part of the MetadataBlock.
×
936
                criteriaBuilder.or(includedAsInputLevelPredicate, hasNoInputLevelPredicate), // Include DatasetFieldTypes based on the input level predicates.
×
937
                displayedOnCreatePredicate // Apply the display-on-create filter if necessary.
938
        );
939

940
        criteriaQuery.select(datasetFieldTypeRoot).distinct(true);
×
941

UNCOV
942
        return em.createQuery(criteriaQuery).getResultList();
×
943
    }
944

945
    private Predicate buildRequiredInDataversePredicate(CriteriaBuilder criteriaBuilder, Root<DatasetFieldType> datasetFieldTypeRoot) {
946
        // Predicate to check if the current DatasetFieldType is required.
947
        Predicate isRequired = criteriaBuilder.isTrue(datasetFieldTypeRoot.get("required"));
×
948

949
        // Subquery to check if the parentDatasetFieldType is required or null.
950
        // We need this check to avoid including conditionally required fields.
UNCOV
951
        Subquery<Boolean> subquery = criteriaBuilder.createQuery(Boolean.class).subquery(Boolean.class);
×
952
        Root<DatasetFieldType> parentRoot = subquery.from(DatasetFieldType.class);
×
953

UNCOV
954
        subquery.select(criteriaBuilder.literal(true))
×
UNCOV
955
                .where(
×
956
                        criteriaBuilder.equal(parentRoot, datasetFieldTypeRoot.get("parentDatasetFieldType")),
×
957
                        criteriaBuilder.or(
×
UNCOV
958
                                criteriaBuilder.isNull(parentRoot.get("required")),
×
959
                                criteriaBuilder.isTrue(parentRoot.get("required"))
×
960
                        )
961
                );
962

963
        // Predicate to check that either the parentDatasetFieldType meets the condition or doesn't exist (is null).
964
        Predicate parentCondition = criteriaBuilder.or(
×
UNCOV
965
                criteriaBuilder.exists(subquery),
×
UNCOV
966
                criteriaBuilder.isNull(datasetFieldTypeRoot.get("parentDatasetFieldType"))
×
967
        );
968

969
        return criteriaBuilder.and(isRequired, parentCondition);
×
970
    }
971
}
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

© 2025 Coveralls, Inc