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

IQSS / dataverse / #22002

01 Apr 2024 07:56PM CUT coverage: 20.716% (+0.5%) from 20.173%
#22002

push

github

web-flow
Merge pull request #10453 from IQSS/develop

Merge 6.2 into master

704 of 2679 new or added lines in 152 files covered. (26.28%)

81 existing lines in 49 files now uncovered.

17160 of 82836 relevant lines covered (20.72%)

0.21 hits per line

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

0.0
/src/main/java/edu/harvard/iq/dataverse/util/json/JSONLDUtil.java
1
package edu.harvard.iq.dataverse.util.json;
2

3
import java.io.StringReader;
4
import java.io.StringWriter;
5
import java.sql.Timestamp;
6
import java.time.LocalDate;
7
import java.time.LocalDateTime;
8
import java.time.format.DateTimeFormatter;
9
import java.util.ArrayList;
10
import java.util.Arrays;
11
import java.util.HashMap;
12
import java.util.LinkedList;
13
import java.util.List;
14
import java.util.Map;
15
import java.util.Optional;
16
import java.util.Map.Entry;
17
import java.util.TreeMap;
18
import java.util.logging.Logger;
19

20

21
import jakarta.json.Json;
22
import jakarta.json.JsonArray;
23
import jakarta.json.JsonObject;
24
import jakarta.json.JsonObjectBuilder;
25
import jakarta.json.JsonString;
26
import jakarta.json.JsonValue;
27
import jakarta.json.JsonWriter;
28
import jakarta.json.JsonWriterFactory;
29
import jakarta.json.JsonValue.ValueType;
30
import jakarta.json.stream.JsonGenerator;
31
import jakarta.ws.rs.BadRequestException;
32

33
import edu.harvard.iq.dataverse.ControlledVocabularyValue;
34
import edu.harvard.iq.dataverse.Dataset;
35
import edu.harvard.iq.dataverse.DatasetField;
36
import edu.harvard.iq.dataverse.DatasetFieldCompoundValue;
37
import edu.harvard.iq.dataverse.DatasetFieldServiceBean;
38
import edu.harvard.iq.dataverse.DatasetFieldType;
39
import edu.harvard.iq.dataverse.DatasetFieldValue;
40
import edu.harvard.iq.dataverse.DatasetVersion;
41
import edu.harvard.iq.dataverse.GlobalId;
42
import edu.harvard.iq.dataverse.MetadataBlock;
43
import edu.harvard.iq.dataverse.MetadataBlockServiceBean;
44
import edu.harvard.iq.dataverse.TermsOfUseAndAccess;
45
import org.apache.commons.lang3.StringUtils;
46

47
import com.apicatalog.jsonld.JsonLd;
48
import com.apicatalog.jsonld.JsonLdError;
49
import com.apicatalog.jsonld.document.JsonDocument;
50

51
import edu.harvard.iq.dataverse.DatasetVersion.VersionState;
52
import edu.harvard.iq.dataverse.license.License;
53
import edu.harvard.iq.dataverse.license.LicenseServiceBean;
54
import edu.harvard.iq.dataverse.pidproviders.PidProvider;
55
import jakarta.json.JsonReader;
56

57
public class JSONLDUtil {
×
58

59
    private static final Logger logger = Logger.getLogger(JSONLDUtil.class.getCanonicalName());
×
60

61
    /*
62
     * private static Map<String, String> populateContext(JsonValue json) {
63
     * Map<String, String> context = new TreeMap<String, String>(); if (json
64
     * instanceof JsonArray) { logger.warning("Array @context not yet supported"); }
65
     * else { for (String key : ((JsonObject) json).keySet()) {
66
     * context.putIfAbsent(key, ((JsonObject) json).getString(key)); } } return
67
     * context; }
68
     */
69

70
    public static JsonObject getContext(Map<String, String> contextMap) {
71
        JsonObjectBuilder contextBuilder = Json.createObjectBuilder();
×
72
        for (Entry<String, String> e : contextMap.entrySet()) {
×
73
            contextBuilder.add(e.getKey(), e.getValue());
×
74
        }
×
75
        return contextBuilder.build();
×
76
    }
77

78
    public static Dataset updateDatasetMDFromJsonLD(Dataset ds, String jsonLDBody,
79
            MetadataBlockServiceBean metadataBlockSvc, DatasetFieldServiceBean datasetFieldSvc, boolean append,
80
            boolean migrating, LicenseServiceBean licenseSvc) {
81

82
        DatasetVersion dsv = new DatasetVersion();
×
83

84
        JsonObject jsonld = decontextualizeJsonLD(jsonLDBody);
×
85
        if (migrating) {
×
NEW
86
            Optional<GlobalId> maybePid = PidProvider.parse(jsonld.getString("@id"));
×
87
            if (maybePid.isPresent()) {
×
88
                ds.setGlobalId(maybePid.get());
×
89
            } else {
90
                // unparsable PID passed. Terminate.
91
                throw new BadRequestException("Cannot parse the @id '" + jsonld.getString("@id")
×
92
                        + "'. Make sure it is in valid form - see Dataverse Native API documentation.");
93
            }
94
        }
95
        
96
        //Store the metadatalanguage if sent - the caller needs to check whether it is allowed (as with any GlobalID)
97
        ds.setMetadataLanguage(jsonld.getString(JsonLDTerm.schemaOrg("inLanguage").getUrl(),null));
×
98

99
        dsv = updateDatasetVersionMDFromJsonLD(dsv, jsonld, metadataBlockSvc, datasetFieldSvc, append, migrating, licenseSvc);
×
100
        dsv.setDataset(ds);
×
101

102
        List<DatasetVersion> versions = new ArrayList<>(1);
×
103
        versions.add(dsv);
×
104

105
        ds.setVersions(versions);
×
106
        if (migrating) {
×
107
            if (jsonld.containsKey(JsonLDTerm.schemaOrg("dateModified").getUrl())) {
×
108
                String dateString = jsonld.getString(JsonLDTerm.schemaOrg("dateModified").getUrl());
×
109
                LocalDateTime dateTime = getDateTimeFrom(dateString);
×
110
                ds.setModificationTime(Timestamp.valueOf(dateTime));
×
111
            }
112
        }
113
        return ds;
×
114
    }
115

116
    public static DatasetVersion updateDatasetVersionMDFromJsonLD(DatasetVersion dsv, String jsonLDBody,
117
            MetadataBlockServiceBean metadataBlockSvc, DatasetFieldServiceBean datasetFieldSvc, boolean append, boolean migrating, LicenseServiceBean licenseSvc) {
118
        JsonObject jsonld = decontextualizeJsonLD(jsonLDBody);
×
119
        return updateDatasetVersionMDFromJsonLD(dsv, jsonld, metadataBlockSvc, datasetFieldSvc, append, migrating, licenseSvc);
×
120
    }
121

122
    /**
123
     * 
124
     * @param dsv
125
     * @param jsonld
126
     * @param metadataBlockSvc
127
     * @param datasetFieldSvc
128
     * @param append           - if append, will add new top level field values for
129
     *                         multi-valued fields, if true and field type isn't
130
     *                         multiple, will fail. if false will replace all
131
     *                         value(s) for fields found in the json-ld.
132
     * @return
133
     */
134
    public static DatasetVersion updateDatasetVersionMDFromJsonLD(DatasetVersion dsv, JsonObject jsonld,
135
            MetadataBlockServiceBean metadataBlockSvc, DatasetFieldServiceBean datasetFieldSvc, boolean append, boolean migrating, LicenseServiceBean licenseSvc) {
136

137
        //Assume draft to start
138
        dsv.setVersionState(VersionState.DRAFT);
×
139
        
140
        populateFieldTypeMap(metadataBlockSvc);
×
141

142
        // get existing ones?
143
        List<DatasetField> dsfl = dsv.getDatasetFields();
×
144
        Map<DatasetFieldType, DatasetField> fieldByTypeMap = new HashMap<DatasetFieldType, DatasetField>();
×
145
        for (DatasetField dsf : dsfl) {
×
146
            if (fieldByTypeMap.containsKey(dsf.getDatasetFieldType())) {
×
147
                // May have multiple values per field, but not multiple fields of one type?
148
                logger.warning("Multiple fields of type " + dsf.getDatasetFieldType().getName());
×
149
            }
150
            fieldByTypeMap.put(dsf.getDatasetFieldType(), dsf);
×
151
        }
×
152

153
        TermsOfUseAndAccess terms = (dsv.getTermsOfUseAndAccess() != null) ? dsv.getTermsOfUseAndAccess().copyTermsOfUseAndAccess() : new TermsOfUseAndAccess();
×
154

155

156
        for (String key : jsonld.keySet()) {
×
157
            if (!key.equals("@context")) {
×
158
                if (dsftMap.containsKey(key)) {
×
159

160
                    DatasetFieldType dsft = dsftMap.get(key);
×
161
                    DatasetField dsf = null;
×
162
                    if (fieldByTypeMap.containsKey(dsft)) {
×
163
                        dsf = fieldByTypeMap.get(dsft);
×
164
                        // If there's an existing field, we use it with append and remove it for !append
165
                        // unless it's multiple
166
                        if (!append && !dsft.isAllowMultiples()) {
×
167
                            dsfl.remove(dsf);
×
168
                            dsf=null;
×
169
                        }
170
                    }
171
                    if (dsf == null) {
×
172
                        dsf = new DatasetField();
×
173
                        dsfl.add(dsf);
×
174
                        dsf.setDatasetFieldType(dsft);
×
175
                    }
176

177
                    JsonArray valArray = getValues(jsonld.get(key), dsft.isAllowMultiples(), dsft.getName());
×
178

179
                    addField(dsf, valArray, dsft, datasetFieldSvc, append);
×
180

181
                } else {
×
182
                    //When migrating, the publication date and version number can be set
183
                    if (key.equals(JsonLDTerm.schemaOrg("datePublished").getUrl())&& migrating && !append) {
×
184
                        dsv.setVersionState(VersionState.RELEASED);
×
185
                    } else if (key.equals(JsonLDTerm.schemaOrg("version").getUrl())&& migrating && !append) {
×
186
                        String friendlyVersion = jsonld.getString(JsonLDTerm.schemaOrg("version").getUrl());
×
187
                        int index = friendlyVersion.indexOf(".");
×
188
                        if (index > 0) {
×
189
                            dsv.setVersionNumber(Long.parseLong(friendlyVersion.substring(0, index)));
×
190
                            dsv.setMinorVersionNumber(Long.parseLong(friendlyVersion.substring(index + 1)));
×
191
                        }
192
                    }
×
193
                    else if (datasetTerms.contains(key)) {
×
194
                        // Other Dataset-level TermsOfUseAndAccess
195
                        if (!append || !isSet(terms, key)) {
×
196
                            if (key.equals(JsonLDTerm.schemaOrg("license").getUrl())) {
×
197
                                if (jsonld.containsKey(JsonLDTerm.termsOfUse.getUrl())) {
×
198
                                    throw new BadRequestException("Cannot specify " + JsonLDTerm.schemaOrg("license").getUrl() + " and " + JsonLDTerm.termsOfUse.getUrl());
×
199
                                }
200
                                if (StringUtils.isEmpty(jsonld.getString(key))) {
×
201
                                    setSemTerm(terms, key, licenseSvc.getDefault());
×
202
                                }
203
                                else {
204
                                        License license = licenseSvc.getByNameOrUri(jsonld.getString(key));
×
205
                                        if (license == null) throw new BadRequestException("Invalid license");
×
206
                                        setSemTerm(terms, key, license);
×
207
                                }
×
208
                            }
209
                            else if (key.equals("https://dataverse.org/schema/core#fileRequestAccess")) {
×
210
                                setSemTerm(terms, key, jsonld.getBoolean(key));
×
211
                            }
212
                            else {
213
                                setSemTerm(terms, key, jsonld.getString(key));
×
214
                            }
215
                        }
216
                        else {
217
                            throw new BadRequestException("Can't append to a single-value field that already has a value: " + key);
×
218
                        }
219
                    } else if (key.equals(JsonLDTerm.fileTermsOfAccess.getUrl())) {
×
220
                        // Other DataFile-level TermsOfUseAndAccess
221
                        JsonObject fAccessObject = jsonld.getJsonObject(JsonLDTerm.fileTermsOfAccess.getUrl());
×
222
                        for (String fileKey : fAccessObject.keySet()) {
×
223
                            if (datafileTerms.contains(fileKey)) {
×
224
                                if (!append || !isSet(terms, fileKey)) {
×
225
                                    if (fileKey.equals(JsonLDTerm.fileRequestAccess.getUrl())) {
×
226
                                        setSemTerm(terms, fileKey, fAccessObject.getBoolean(fileKey));
×
227
                                    } else {
228
                                        setSemTerm(terms, fileKey, fAccessObject.getString(fileKey));
×
229
                                    }
230
                                } else {
231
                                    throw new BadRequestException(
×
232
                                            "Can't append to a single-value field that already has a value: "
233
                                                    + fileKey);
234
                                }
235
                            }
236
                        }
×
237
                    }
238
                    // ToDo: support Dataverse location metadata? e.g. move to new dataverse?
239
                    // re: JsonLDTerm.schemaOrg("includedInDataCatalog")
240
                }
241
            }
242
        }
×
243
        dsv.setTermsOfUseAndAccess(terms);
×
244
        terms.setDatasetVersion(dsv);
×
245
        dsv.setDatasetFields(dsfl);
×
246

247
        return dsv;
×
248
    }
249
    /**
250
     * 
251
     * @param dsv
252
     * @param jsonLDBody
253
     * @param metadataBlockService
254
     * @param datasetFieldSvc
255
     * @param b
256
     * @param c
257
     * @return
258
     */
259
    public static DatasetVersion deleteDatasetVersionMDFromJsonLD(DatasetVersion dsv, String jsonLDBody,
260
            MetadataBlockServiceBean metadataBlockSvc, LicenseServiceBean licenseSvc) {
261
        logger.fine("deleteDatasetVersionMD");
×
262
        JsonObject jsonld = decontextualizeJsonLD(jsonLDBody);
×
263
        //All terms are now URIs
264
        //Setup dsftMap - URI to datasetFieldType map
265
        populateFieldTypeMap(metadataBlockSvc);
×
266

267

268
        //Another map - from datasetFieldType to an existing field in the dataset
269
        List<DatasetField> dsfl = dsv.getDatasetFields();
×
270
        Map<DatasetFieldType, DatasetField> fieldByTypeMap = new HashMap<DatasetFieldType, DatasetField>();
×
271
        for (DatasetField dsf : dsfl) {
×
272
            if (fieldByTypeMap.containsKey(dsf.getDatasetFieldType())) {
×
273
                // May have multiple values per field, but not multiple fields of one type?
274
                logger.warning("Multiple fields of type " + dsf.getDatasetFieldType().getName());
×
275
            }
276
            fieldByTypeMap.put(dsf.getDatasetFieldType(), dsf);
×
277
        }
×
278
        
279
        TermsOfUseAndAccess terms = dsv.getTermsOfUseAndAccess().copyTermsOfUseAndAccess();
×
280

281
        //Iterate through input json
282
        for (String key : jsonld.keySet()) {
×
283
            //Skip context (shouldn't be present with decontextualize used above)
284
            if (!key.equals("@context")) {
×
285
                if (dsftMap.containsKey(key)) {
×
286
                    //THere's a field type with theis URI
287
                    DatasetFieldType dsft = dsftMap.get(key);
×
288
                    DatasetField dsf = null;
×
289
                    if (fieldByTypeMap.containsKey(dsft)) {
×
290
                        //There's a field of this type
291
                        dsf = fieldByTypeMap.get(dsft);
×
292

293
                        // Todo - normalize object vs. array
294
                        JsonArray valArray = getValues(jsonld.get(key), dsft.isAllowMultiples(), dsft.getName());
×
295
                        logger.fine("Deleting: " + key + " : " + valArray.toString());
×
296
                        DatasetField dsf2 = getReplacementField(dsf, valArray);
×
297
                        if(dsf2 == null) {
×
298
                            //Exact match - remove the field
299
                            dsfl.remove(dsf);
×
300
                        } else {
301
                            //Partial match - some values of a multivalue field match, so keep the remaining values
302
                            dsfl.remove(dsf);
×
303
                            dsfl.add(dsf2);
×
304
                        }
305
                    }
306
                } else {
×
307
                    // Internal/non-metadatablock terms
308
                    boolean found=false;
×
309
                    if (key.equals(JsonLDTerm.schemaOrg("license").getUrl())) {
×
310
                        if(licenseSvc.getByNameOrUri(jsonld.getString(key)) != null) {
×
311
                            setSemTerm(terms, key, licenseSvc.getDefault());
×
312
                        } else {
313
                            throw new BadRequestException(
×
314
                                    "Term: " + key + " with value: " + jsonld.getString(key) + " not found.");
×
315
                        }
316
                        found=true;
×
317
                    } else if (datasetTerms.contains(key)) {
×
318
                        if(!deleteIfSemTermMatches(terms, key, jsonld.get(key))) {
×
319
                            throw new BadRequestException(
×
320
                                    "Term: " + key + " with value: " + jsonld.getString(key) + " not found.");
×
321
                        }
322
                        found=true;
×
323
                    } else if (key.equals(JsonLDTerm.fileTermsOfAccess.getUrl())) {
×
324
                        JsonObject fAccessObject = jsonld.getJsonObject(JsonLDTerm.fileTermsOfAccess.getUrl());
×
325
                        for (String fileKey : fAccessObject.keySet()) {
×
326
                            if (datafileTerms.contains(fileKey)) {
×
327
                                if(!deleteIfSemTermMatches(terms, key, jsonld.get(key))) {
×
328
                                    throw new BadRequestException(
×
329
                                            "Term: " + key + " with value: " + jsonld.getString(key) + " not found.");
×
330
                                }
331
                                found=true;
×
332
                            }
333
                        }
×
334
                    } else if(!found) {
×
335
                        throw new BadRequestException(
×
336
                                "Term: " + key + " not found.");
337
                                    }
338
                    
339
                    dsv.setTermsOfUseAndAccess(terms);
×
340
                }
341
            }
342
        }
×
343
        dsv.setDatasetFields(dsfl);
×
344
        return dsv;
×
345
    }
346
    
347
    /**
348
     * 
349
     * @param dsf
350
     * @param valArray
351
     * @return null if exact match, otherwise return a field without the value to be deleted
352
     */
353
    private static DatasetField getReplacementField(DatasetField dsf, JsonArray valArray) {
354
        // TODO Parse valArray and remove any matching entries in the dsf
355
        // Until then, delete removes all values of a multivalued field
356
        // Doing this on a required field will fail.
357
        return null;
×
358
    }
359

360
    private static void addField(DatasetField dsf, JsonArray valArray, DatasetFieldType dsft,
361
            DatasetFieldServiceBean datasetFieldSvc, boolean append) {
362

363
        if (append && !dsft.isAllowMultiples()) {
×
364
            if ((dsft.isCompound() && !dsf.getDatasetFieldCompoundValues().isEmpty())
×
365
                    || (dsft.isAllowControlledVocabulary() && !dsf.getControlledVocabularyValues().isEmpty())
×
366
                    || !dsf.getDatasetFieldValues().isEmpty()) {
×
367
                throw new BadRequestException(
×
368
                        "Can't append to a single-value field that already has a value: " + dsft.getName());
×
369
            }
370
        }
371
        logger.fine("Name: " + dsft.getName());
×
372
        logger.fine("val: " + valArray.toString());
×
373
        logger.fine("Compound: " + dsft.isCompound());
×
374
        logger.fine("CV: " + dsft.isAllowControlledVocabulary());
×
375

376
        Map<Long, JsonObject> cvocMap = datasetFieldSvc.getCVocConf(true);
×
377
        
378
        if (dsft.isCompound()) {
×
379
            /*
380
             * List<DatasetFieldCompoundValue> vals = parseCompoundValue(type,
381
             * jsonld.get(key),testType); for (DatasetFieldCompoundValue dsfcv : vals) {
382
             * dsfcv.setParentDatasetField(ret); } dsf.setDatasetFieldCompoundValues(vals);
383
             */
384
            List<DatasetFieldCompoundValue> cvList = dsf.getDatasetFieldCompoundValues();
×
385
            if (!cvList.isEmpty()) {
×
386
                if (!append) {
×
387
                    cvList.clear();
×
388
                } else if (!dsft.isAllowMultiples() && cvList.size() == 1) {
×
389
                    // Trying to append but only a single value is allowed (and there already is
390
                    // one)
391
                    // (and we don't currently support appending new fields within a compound value)
392
                    throw new BadRequestException(
×
393
                            "Append with compound field with single value not yet supported: " + dsft.getDisplayName());
×
394
                }
395
            }
396

397
            List<DatasetFieldCompoundValue> vals = new LinkedList<>();
×
398
            for (JsonValue val : valArray) {
×
399
                if (!(val instanceof JsonObject)) {
×
400
                    throw new BadRequestException(
×
401
                            "Compound field values must be JSON objects, field: " + dsft.getName());
×
402
                }
403
                DatasetFieldCompoundValue cv = null;
×
404

405
                cv = new DatasetFieldCompoundValue();
×
406
                cv.setDisplayOrder(cvList.size());
×
407
                cvList.add(cv);
×
408
                cv.setParentDatasetField(dsf);
×
409

410
                JsonObject obj = (JsonObject) val;
×
411
                for (String childKey : obj.keySet()) {
×
412
                    if (dsftMap.containsKey(childKey)) {
×
413
                        DatasetFieldType childft = dsftMap.get(childKey);
×
414
                        if (!dsft.getChildDatasetFieldTypes().contains(childft)) {
×
415
                            throw new BadRequestException(
×
416
                                    "Compound field " + dsft.getName() + "can't include term " + childKey);
×
417
                        }
418
                        DatasetField childDsf = new DatasetField();
×
419
                        cv.getChildDatasetFields().add(childDsf);
×
420
                        childDsf.setDatasetFieldType(childft);
×
421
                        childDsf.setParentDatasetFieldCompoundValue(cv);
×
422

423
                        JsonArray childValArray = getValues(obj.get(childKey), childft.isAllowMultiples(),
×
424
                                childft.getName());
×
425
                        addField(childDsf, childValArray, childft, datasetFieldSvc, append);
×
426
                    }
427
                }
×
428
            }
×
429

430
        } else if (dsft.isControlledVocabulary()) {
×
431

432
            List<ControlledVocabularyValue> vals = dsf.getControlledVocabularyValues();
×
433
            for (JsonString strVal : valArray.getValuesAs(JsonString.class)) {
×
434
                String strValue = strVal.getString();
×
435
                ControlledVocabularyValue cvv = datasetFieldSvc
×
436
                        .findControlledVocabularyValueByDatasetFieldTypeAndStrValue(dsft, strValue, true);
×
437
                if (cvv == null) {
×
438
                    throw new BadRequestException(
×
439
                            "Unknown value for Controlled Vocab Field: " + dsft.getName() + " : " + strValue);
×
440
                }
441
                // Only add value to the list if it is not a duplicate
442
                if (strValue.equals("Other")) {
×
443
                    logger.fine("vals = " + vals + ", contains: " + vals.contains(cvv));
×
444
                }
445
                if (!vals.contains(cvv)) {
×
446
                    if (vals.size() > 0) {
×
447
                        cvv.setDisplayOrder(vals.size());
×
448
                    }
449
                    vals.add(cvv);
×
450
                    cvv.setDatasetFieldType(dsft);
×
451
                }
452
            }
×
453
            dsf.setControlledVocabularyValues(vals);
×
454

455
        } else {
×
456

457
            boolean extVocab=false;
×
458
            if(cvocMap.containsKey(dsft.getId())) {
×
459
                extVocab=true;
×
460
            }
461
            List<DatasetFieldValue> vals = dsf.getDatasetFieldValues();
×
462

463
            for (JsonString strVal : valArray.getValuesAs(JsonString.class)) {
×
464
                String strValue = strVal.getString();
×
465
                if(extVocab) {
×
466
                    if(!datasetFieldSvc.isValidCVocValue(dsft, strValue)) {
×
467
                        throw new BadRequestException("Invalid values submitted for " + dsft.getName() + " which is limited to specific vocabularies.");
×
468
                    }
469
                    datasetFieldSvc.registerExternalTerm(cvocMap.get(dsft.getId()), strValue);
×
470
                }
471
                DatasetFieldValue datasetFieldValue = new DatasetFieldValue();
×
472

473
                datasetFieldValue.setDisplayOrder(vals.size());
×
474
                datasetFieldValue.setValue(strValue.trim());
×
475
                vals.add(datasetFieldValue);
×
476
                datasetFieldValue.setDatasetField(dsf);
×
477

478
            }
×
479
            dsf.setDatasetFieldValues(vals);
×
480
        }
481
    }
×
482

483
    private static JsonArray getValues(JsonValue val, boolean allowMultiples, String name) {
484
        JsonArray valArray = null;
×
485
        if (val instanceof JsonArray) {
×
486
            if ((((JsonArray) val).size() > 1) && !allowMultiples) {
×
487
                throw new BadRequestException("Array for single value notsupported: " + name);
×
488
            } else {
489
                valArray = (JsonArray) val;
×
490
            }
491
        } else {
492
            valArray = Json.createArrayBuilder().add(val).build();
×
493
        }
494
        return valArray;
×
495
    }
496

497
    static Map<String, String> localContext = new TreeMap<String, String>();
×
498
    static Map<String, DatasetFieldType> dsftMap = new TreeMap<String, DatasetFieldType>();
×
499

500
    //A map if DatasetFieldTypes by decontextualized URL
501
    private static void populateFieldTypeMap(MetadataBlockServiceBean metadataBlockSvc) {
502
        if (dsftMap.isEmpty()) {
×
503
            List<MetadataBlock> mdbList = metadataBlockSvc.listMetadataBlocks();
×
504
            for (MetadataBlock mdb : mdbList) {
×
505
                for (DatasetFieldType dsft : mdb.getDatasetFieldTypes()) {
×
506
                    dsftMap.put(dsft.getJsonLDTerm().getUrl(), dsft);
×
507
                }
×
508
            }
×
509
            logger.fine("DSFT Map: " + String.join(", ", dsftMap.keySet()));
×
510
        }
511
    }
×
512

513
    public static void populateContext(MetadataBlockServiceBean metadataBlockSvc) {
514
        if (localContext.isEmpty()) {
×
515

516
            List<MetadataBlock> mdbList = metadataBlockSvc.listMetadataBlocks();
×
517
            for (MetadataBlock mdb : mdbList) {
×
518
                //Assures the mdb's namespace is in the list checked by JsonLDNamespace.isInNamespace() below
519
                mdb.getJsonLDNamespace();
×
520
                for (DatasetFieldType dsft : mdb.getDatasetFieldTypes()) {
×
521
                    if ((dsft.getUri() != null) && !JsonLDNamespace.isInNamespace(dsft.getUri())) {
×
522
                        // Add term if uri exists and it's not in one of the namespaces already defined
523
                        localContext.putIfAbsent(dsft.getName(), dsft.getUri());
×
524
                    }
525
                }
×
526
            }
×
527
            JsonLDNamespace.addNamespacesToContext(localContext);
×
528
            logger.fine("LocalContext keys: " + String.join(", ", localContext.keySet()));
×
529
        }
530
    }
×
531

532
    public static JsonObject decontextualizeJsonLD(String jsonLDString) {
533
        logger.fine(jsonLDString);
×
534
        try (StringReader rdr = new StringReader(jsonLDString)) {
×
535

536
            // Use JsonLd to expand/compact to localContext
537
            try (JsonReader jsonReader = Json.createReader(rdr)) {
×
538
                JsonObject jsonld = jsonReader.readObject();
×
539
                JsonDocument doc = JsonDocument.of(jsonld);
×
540
                JsonArray array = JsonLd.expand(doc).get();
×
541
                jsonld = JsonLd.compact(JsonDocument.of(array), JsonDocument.of(Json.createObjectBuilder().build())).get();
×
542
                // jsonld = array.getJsonObject(0);
543
                logger.fine("Decontextualized object: " + jsonld);
×
544
                return jsonld;
×
545
            } catch (JsonLdError e) {
×
546
                System.out.println(e.getMessage());
×
547
                return null;
×
548
            }
549
        }
×
550
    }
551

552
    private static JsonObject recontextualizeJsonLD(JsonObject jsonldObj, MetadataBlockServiceBean metadataBlockSvc) {
553

554
        populateContext(metadataBlockSvc);
×
555

556
        // Use JsonLd to expand/compact to localContext
557
        JsonDocument doc = JsonDocument.of(jsonldObj);
×
558
        JsonArray array = null;
×
559
        try {
560
            array = JsonLd.expand(doc).get();
×
561

562
            jsonldObj = JsonLd.compact(JsonDocument.of(array), JsonDocument.of(JSONLDUtil.getContext(localContext)))
×
563
                    .get();
×
564
            logger.fine("Compacted: " + jsonldObj.toString());
×
565
            return jsonldObj;
×
566
        } catch (JsonLdError e) {
×
567
            System.out.println(e.getMessage());
×
568
            return null;
×
569
        }
570
    }
571

572
    public static String prettyPrint(JsonValue val) {
573
        StringWriter sw = new StringWriter();
×
574
        Map<String, Object> properties = new HashMap<>(1);
×
575
        properties.put(JsonGenerator.PRETTY_PRINTING, true);
×
576
        JsonWriterFactory writerFactory = Json.createWriterFactory(properties);
×
577
        JsonWriter jsonWriter = writerFactory.createWriter(sw);
×
578
        jsonWriter.write(val);
×
579
        jsonWriter.close();
×
580
        return sw.toString();
×
581
    }
582

583
//Modified from https://stackoverflow.com/questions/3389348/parse-any-date-in-java
584

585
    private static final Map<String, String> DATE_FORMAT_REGEXPS = new HashMap<String, String>() {
×
586
        {
587
            put("^\\d{8}$", "yyyyMMdd");
×
588
            put("^\\d{1,2}-\\d{1,2}-\\d{4}$", "dd-MM-yyyy");
×
589
            put("^\\d{4}-\\d{1,2}-\\d{1,2}$", "yyyy-MM-dd");
×
590
            put("^\\d{1,2}/\\d{1,2}/\\d{4}$", "MM/dd/yyyy");
×
591
            put("^\\d{4}/\\d{1,2}/\\d{1,2}$", "yyyy/MM/dd");
×
592
            put("^\\d{1,2}\\s[a-z]{3}\\s\\d{4}$", "dd MMM yyyy");
×
593
            put("^\\d{1,2}\\s[a-z]{4,}\\s\\d{4}$", "dd MMMM yyyy");
×
594
        }
×
595
    };
596

597
    private static final Map<String, String> DATETIME_FORMAT_REGEXPS = new HashMap<String, String>() {
×
598
        {
599
            put("^\\d{12}$", "yyyyMMddHHmm");
×
600
            put("^\\d{8}\\s\\d{4}$", "yyyyMMdd HHmm");
×
601
            put("^\\d{1,2}-\\d{1,2}-\\d{4}\\s\\d{1,2}:\\d{2}$", "dd-MM-yyyy HH:mm");
×
602
            put("^\\d{4}-\\d{1,2}-\\d{1,2}\\s\\d{1,2}:\\d{2}$", "yyyy-MM-dd HH:mm");
×
603
            put("^\\d{1,2}/\\d{1,2}/\\d{4}\\s\\d{1,2}:\\d{2}$", "MM/dd/yyyy HH:mm");
×
604
            put("^\\d{4}/\\d{1,2}/\\d{1,2}\\s\\d{1,2}:\\d{2}$", "yyyy/MM/dd HH:mm");
×
605
            put("^\\d{1,2}\\s[a-z]{3}\\s\\d{4}\\s\\d{1,2}:\\d{2}$", "dd MMM yyyy HH:mm");
×
606
            put("^\\d{1,2}\\s[a-z]{4,}\\s\\d{4}\\s\\d{1,2}:\\d{2}$", "dd MMMM yyyy HH:mm");
×
607
            put("^\\d{14}$", "yyyyMMddHHmmss");
×
608
            put("^\\d{8}\\s\\d{6}$", "yyyyMMdd HHmmss");
×
609
            put("^\\d{1,2}-\\d{1,2}-\\d{4}\\s\\d{1,2}:\\d{2}:\\d{2}$", "dd-MM-yyyy HH:mm:ss");
×
610
            put("^\\d{4}-\\d{1,2}-\\d{1,2}\\s\\d{1,2}:\\d{2}:\\d{2}$", "yyyy-MM-dd HH:mm:ss");
×
611
            put("^\\d{1,2}/\\d{1,2}/\\d{4}\\s\\d{1,2}:\\d{2}:\\d{2}$", "MM/dd/yyyy HH:mm:ss");
×
612
            put("^\\d{4}/\\d{1,2}/\\d{1,2}\\s\\d{1,2}:\\d{2}:\\d{2}$", "yyyy/MM/dd HH:mm:ss");
×
613
            put("^\\d{1,2}\\s[a-z]{3}\\s\\d{4}\\s\\d{1,2}:\\d{2}:\\d{2}$", "dd MMM yyyy HH:mm:ss");
×
614
            put("^\\d{1,2}\\s[a-z]{4,}\\s\\d{4}\\s\\d{1,2}:\\d{2}:\\d{2}$", "dd MMMM yyyy HH:mm:ss");
×
615
            put("^\\d{4}-\\d{1,2}-\\d{1,2}\\s\\d{1,2}:\\d{2}:\\d{2}\\.\\d{3}$", "yyyy-MM-dd HH:mm:ss.SSS");
×
616
            put("^[a-z,A-Z]{3}\\s[a-z,A-Z]{3}\\s\\d{1,2}\\s\\d{1,2}:\\d{2}:\\d{2}\\s[a-z,A-Z]{3}\\s\\d{4}$",
×
617
                    "EEE MMM dd HH:mm:ss zzz yyyy"); // Wed Sep 23 19:33:46 UTC 2020
618

619
        }
×
620
    };
621

622
    /**
623
     * Determine DateTimeFormatter pattern matching with the given date string.
624
     * Returns null if format is unknown. You can simply extend DateUtil with more
625
     * formats if needed.
626
     * 
627
     * @param dateString The date string to determine the SimpleDateFormat pattern
628
     *                   for.
629
     * @return The matching SimpleDateFormat pattern, or null if format is unknown.
630
     * @see SimpleDateFormat
631
     */
632
    public static DateTimeFormatter determineDateTimeFormat(String dateString) {
633
        for (String regexp : DATETIME_FORMAT_REGEXPS.keySet()) {
×
634
            if (dateString.toLowerCase().matches(regexp)) {
×
635
                return DateTimeFormatter.ofPattern(DATETIME_FORMAT_REGEXPS.get(regexp));
×
636
            }
637
        }
×
638
        logger.warning("Unknown datetime format: " + dateString);
×
639
        return null; // Unknown format.
×
640
    }
641

642
    public static DateTimeFormatter determineDateFormat(String dateString) {
643
        for (String regexp : DATE_FORMAT_REGEXPS.keySet()) {
×
644
            if (dateString.toLowerCase().matches(regexp)) {
×
645
                return DateTimeFormatter.ofPattern(DATE_FORMAT_REGEXPS.get(regexp));
×
646
            }
647
        }
×
648
        logger.warning("Unknown date format: " + dateString);
×
649
        return null; // Unknown format.
×
650
    }
651

652
    public static LocalDateTime getDateTimeFrom(String dateString) {
653
        DateTimeFormatter dtf = determineDateTimeFormat(dateString);
×
654
        if (dtf != null) {
×
655
            return LocalDateTime.parse(dateString, dtf);
×
656
        } else {
657
            dtf = determineDateFormat(dateString);
×
658
            if (dtf != null) {
×
659
                return LocalDate.parse(dateString, dtf).atStartOfDay();
×
660
            }
661
        }
662

663
        return null;
×
664
    }
665

666
    // Convenience methods for TermsOfUseAndAccess
667

668
    public static final List<String> datasetTerms = new ArrayList<String>(Arrays.asList(
×
669
            "http://schema.org/license",
670
            "https://dataverse.org/schema/core#termsOfUse",
671
            "https://dataverse.org/schema/core#confidentialityDeclaration",
672
            "https://dataverse.org/schema/core#specialPermissions", "https://dataverse.org/schema/core#restrictions",
673
            "https://dataverse.org/schema/core#citationRequirements",
674
            "https://dataverse.org/schema/core#depositorRequirements", "https://dataverse.org/schema/core#conditions",
675
            "https://dataverse.org/schema/core#disclaimer"));
676
    public static final List<String> datafileTerms = new ArrayList<String>(Arrays.asList(
×
677
            "https://dataverse.org/schema/core#termsOfAccess", "https://dataverse.org/schema/core#fileRequestAccess",
678
            "https://dataverse.org/schema/core#dataAccessPlace", "https://dataverse.org/schema/core#originalArchive",
679
            "https://dataverse.org/schema/core#availabilityStatus",
680
            "https://dataverse.org/schema/core#contactForAccess", "https://dataverse.org/schema/core#sizeOfCollection",
681
            "https://dataverse.org/schema/core#studyCompletion"));
682

683
    public static boolean isSet(TermsOfUseAndAccess terms, String semterm) {
684
        switch (semterm) {
×
685
        case "http://schema.org/license":
686
            return terms.getLicense() != null;
×
687
        case "https://dataverse.org/schema/core#termsOfUse":
688
            return !StringUtils.isBlank(terms.getTermsOfUse());
×
689
        case "https://dataverse.org/schema/core#confidentialityDeclaration":
690
            return !StringUtils.isBlank(terms.getConfidentialityDeclaration());
×
691
        case "https://dataverse.org/schema/core#specialPermissions":
692
            return !StringUtils.isBlank(terms.getSpecialPermissions());
×
693
        case "https://dataverse.org/schema/core#restrictions":
694
            return !StringUtils.isBlank(terms.getRestrictions());
×
695
        case "https://dataverse.org/schema/core#citationRequirements":
696
            return !StringUtils.isBlank(terms.getCitationRequirements());
×
697
        case "https://dataverse.org/schema/core#depositorRequirements":
698
            return !StringUtils.isBlank(terms.getDepositorRequirements());
×
699
        case "https://dataverse.org/schema/core#conditions":
700
            return !StringUtils.isBlank(terms.getConditions());
×
701
        case "https://dataverse.org/schema/core#disclaimer":
702
            return !StringUtils.isBlank(terms.getDisclaimer());
×
703
        case "https://dataverse.org/schema/core#termsOfAccess":
704
            return !StringUtils.isBlank(terms.getTermsOfAccess());
×
705
        case "https://dataverse.org/schema/core#fileRequestAccess":
706
            return !terms.isFileAccessRequest();
×
707
        case "https://dataverse.org/schema/core#dataAccessPlace":
708
            return !StringUtils.isBlank(terms.getDataAccessPlace());
×
709
        case "https://dataverse.org/schema/core#originalArchive":
710
            return !StringUtils.isBlank(terms.getOriginalArchive());
×
711
        case "https://dataverse.org/schema/core#availabilityStatus":
712
            return !StringUtils.isBlank(terms.getAvailabilityStatus());
×
713
        case "https://dataverse.org/schema/core#contactForAccess":
714
            return !StringUtils.isBlank(terms.getContactForAccess());
×
715
        case "https://dataverse.org/schema/core#sizeOfCollection":
716
            return !StringUtils.isBlank(terms.getSizeOfCollection());
×
717
        case "https://dataverse.org/schema/core#studyCompletion":
718
            return !StringUtils.isBlank(terms.getStudyCompletion());
×
719
        default:
720
            logger.warning("isSet called for " + semterm);
×
721
            return false;
×
722
        }
723
    }
724

725
    public static void setSemTerm(TermsOfUseAndAccess terms, String semterm, Object value) {
726
        switch (semterm) {
×
727
        case "http://schema.org/license":
728
            terms.setLicense((License) value);
×
729
            break;
×
730
        case "https://dataverse.org/schema/core#termsOfUse":
731
            terms.setTermsOfUse((String) value);
×
732
            break;
×
733
        case "https://dataverse.org/schema/core#confidentialityDeclaration":
734
            terms.setConfidentialityDeclaration((String) value);
×
735
            break;
×
736
        case "https://dataverse.org/schema/core#specialPermissions":
737
            terms.setSpecialPermissions((String) value);
×
738
            break;
×
739
        case "https://dataverse.org/schema/core#restrictions":
740
            terms.setRestrictions((String) value);
×
741
            break;
×
742
        case "https://dataverse.org/schema/core#citationRequirements":
743
            terms.setCitationRequirements((String) value);
×
744
            break;
×
745
        case "https://dataverse.org/schema/core#depositorRequirements":
746
            terms.setDepositorRequirements((String) value);
×
747
            break;
×
748
        case "https://dataverse.org/schema/core#conditions":
749
            terms.setConditions((String) value);
×
750
            break;
×
751
        case "https://dataverse.org/schema/core#disclaimer":
752
            terms.setDisclaimer((String) value);
×
753
            break;
×
754
        case "https://dataverse.org/schema/core#termsOfAccess":
755
            terms.setTermsOfAccess((String) value);
×
756
            break;
×
757
        case "https://dataverse.org/schema/core#fileRequestAccess":
758
            terms.setFileAccessRequest((boolean) value);
×
759
            break;
×
760
        case "https://dataverse.org/schema/core#dataAccessPlace":
761
            terms.setDataAccessPlace((String) value);
×
762
            break;
×
763
        case "https://dataverse.org/schema/core#originalArchive":
764
            terms.setOriginalArchive((String) value);
×
765
            break;
×
766
        case "https://dataverse.org/schema/core#availabilityStatus":
767
            terms.setAvailabilityStatus((String) value);
×
768
            break;
×
769
        case "https://dataverse.org/schema/core#contactForAccess":
770
            terms.setContactForAccess((String) value);
×
771
            break;
×
772
        case "https://dataverse.org/schema/core#sizeOfCollection":
773
            terms.setSizeOfCollection((String) value);
×
774
            break;
×
775
        case "https://dataverse.org/schema/core#studyCompletion":
776
            terms.setStudyCompletion((String) value);
×
777
            break;
×
778
        default:
779
            logger.warning("setSemTerm called for " + semterm);
×
780
            break;
781
        }
782
    }
×
783

784
    private static boolean deleteIfSemTermMatches(TermsOfUseAndAccess terms, String semterm, JsonValue jsonValue) {
785
        boolean foundTerm=false;
×
786
        String val = null;
×
787
        if(jsonValue.getValueType().equals(ValueType.STRING)) {
×
788
            val = ((JsonString)jsonValue).getString();
×
789
        }
790
        switch (semterm) {
×
791
        
792
        case "https://dataverse.org/schema/core#termsOfUse":
793
            if(terms.getTermsOfUse().equals(val)) {
×
794
                terms.setTermsOfUse(null);
×
795
                foundTerm=true;
×
796
            }
797
            break;
798
        case "https://dataverse.org/schema/core#confidentialityDeclaration":
799
            if(terms.getConfidentialityDeclaration().equals(val)) {
×
800
                terms.setConfidentialityDeclaration(null);
×
801
                foundTerm=true;
×
802
            }
803
            break;
804
        case "https://dataverse.org/schema/core#specialPermissions":
805
            if(terms.getSpecialPermissions().equals(val)) {
×
806
                terms.setSpecialPermissions(null);
×
807
                foundTerm=true;
×
808
            }
809
            break;
810
        case "https://dataverse.org/schema/core#restrictions":
811
            if(terms.getRestrictions().equals(val)) {
×
812
                terms.setRestrictions(null);
×
813
                foundTerm=true;
×
814
            }
815
            break;
816
        case "https://dataverse.org/schema/core#citationRequirements":
817
            if(terms.getCitationRequirements().equals(val)) {
×
818
                terms.setCitationRequirements(null);
×
819
                foundTerm=true;
×
820
            }
821
            break;
822
        case "https://dataverse.org/schema/core#depositorRequirements":
823
            if(terms.getDepositorRequirements().equals(val)) {
×
824
                terms.setDepositorRequirements(null);
×
825
                foundTerm=true;
×
826
            }
827
            break;
828
        case "https://dataverse.org/schema/core#conditions":
829
            if(terms.getConditions().equals(val)) {
×
830
                terms.setConditions(null);
×
831
                foundTerm=true;
×
832
            }
833
            break;
834
        case "https://dataverse.org/schema/core#disclaimer":
835
            if(terms.getDisclaimer().equals(val)) {
×
836
                terms.setDisclaimer(null);
×
837
                foundTerm=true;
×
838
            }
839
            break;
840
        case "https://dataverse.org/schema/core#termsOfAccess":
841
            if(terms.getTermsOfAccess().equals(val)) {
×
842
                terms.setTermsOfAccess(null);
×
843
                foundTerm=true;
×
844
            }
845
            break;
846
        case "https://dataverse.org/schema/core#fileRequestAccess":
847
            if(terms.isFileAccessRequest() && (jsonValue.equals(JsonValue.TRUE))) {
×
848
                terms.setFileAccessRequest(false);
×
849
                foundTerm=true;
×
850
            }
851
            break;
852
        case "https://dataverse.org/schema/core#dataAccessPlace":
853
            if(terms.getDataAccessPlace().equals(val)) {
×
854
                terms.setDataAccessPlace(null);
×
855
                foundTerm=true;
×
856
            }
857
            break;
858
        case "https://dataverse.org/schema/core#originalArchive":
859
            if(terms.getOriginalArchive().equals(val)) {
×
860
                terms.setOriginalArchive(null);
×
861
                foundTerm=true;
×
862
            }
863
            break;
864
        case "https://dataverse.org/schema/core#availabilityStatus":
865
            if(terms.getAvailabilityStatus().equals(val)) {
×
866
                terms.setAvailabilityStatus(null);
×
867
                foundTerm=true;
×
868
            }
869
            break;
870
        case "https://dataverse.org/schema/core#contactForAccess":
871
            if(terms.getContactForAccess().equals(val)) {
×
872
                terms.setContactForAccess(null);
×
873
                foundTerm=true;
×
874
            }
875
            break;
876
        case "https://dataverse.org/schema/core#sizeOfCollection":
877
            if(terms.getSizeOfCollection().equals(val)) {
×
878
                terms.setSizeOfCollection(null);
×
879
                foundTerm=true;
×
880
            }
881
            break;
882
        case "https://dataverse.org/schema/core#studyCompletion":
883
            if(terms.getStudyCompletion().equals(val)) {
×
884
                terms.setStudyCompletion(null);
×
885
                foundTerm=true;
×
886
            }
887
            break;
888
        default:
889
            logger.warning("deleteIfSemTermMatches called for " + semterm);
×
890
            break;
891
        }
892
        return foundTerm; 
×
893
    }
894

895
}
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