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

IQSS / dataverse / #23111

10 Sep 2024 03:14PM CUT coverage: 20.681% (-0.05%) from 20.734%
#23111

Pull #10781

github

landreev
Merge branch 'develop' into 10623-globus-improvements
Resolved conflicts:
	src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
(#10623)
Pull Request #10781: Improved handling of Globus uploads

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

9 existing lines in 3 files now uncovered.

17550 of 84861 relevant lines covered (20.68%)

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/datasetutility/AddReplaceFileHelper.java
1
/*
2
 * To change this license header, choose License Headers in Project Properties.
3
 * To change this template file, choose Tools | Templates
4
 * and open the template in the editor.
5
 */
6
package edu.harvard.iq.dataverse.datasetutility;
7

8
import edu.harvard.iq.dataverse.DataFile;
9
import edu.harvard.iq.dataverse.DataFile.ChecksumType;
10
import edu.harvard.iq.dataverse.DataFileServiceBean;
11
import edu.harvard.iq.dataverse.Dataset;
12
import edu.harvard.iq.dataverse.DatasetLock;
13
import edu.harvard.iq.dataverse.DatasetServiceBean;
14
import edu.harvard.iq.dataverse.DatasetVersion;
15
import edu.harvard.iq.dataverse.EjbDataverseEngine;
16
import edu.harvard.iq.dataverse.FileMetadata;
17
import edu.harvard.iq.dataverse.PermissionServiceBean;
18
import edu.harvard.iq.dataverse.api.ApiConstants;
19
import edu.harvard.iq.dataverse.api.Util;
20
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
21
import edu.harvard.iq.dataverse.authorization.users.User;
22
import edu.harvard.iq.dataverse.dataaccess.DataAccess;
23
import edu.harvard.iq.dataverse.engine.command.Command;
24
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
25
import edu.harvard.iq.dataverse.engine.command.exception.CommandException;
26
import edu.harvard.iq.dataverse.engine.command.impl.RestrictFileCommand;
27
import edu.harvard.iq.dataverse.engine.command.impl.UpdateDatasetVersionCommand;
28
import edu.harvard.iq.dataverse.ingest.IngestServiceBean;
29
import edu.harvard.iq.dataverse.util.BundleUtil;
30
import edu.harvard.iq.dataverse.util.SystemConfig;
31
import edu.harvard.iq.dataverse.util.file.CreateDataFileResult;
32
import edu.harvard.iq.dataverse.util.json.JsonPrinter;
33
import edu.harvard.iq.dataverse.util.json.JsonUtil;
34

35
import java.io.IOException;
36
import java.io.InputStream;
37
import java.util.ArrayList;
38
import java.util.Arrays;
39
import java.util.Collections;
40
import java.util.HashMap;
41
import java.util.Iterator;
42
import java.util.List;
43
import java.util.Map;
44
import java.util.Objects;
45
import java.util.Set;
46
import java.util.logging.Level;
47
import java.util.logging.Logger;
48

49
import jakarta.ejb.Asynchronous;
50
import jakarta.ejb.EJBException;
51
import jakarta.json.Json;
52
import jakarta.json.JsonArrayBuilder;
53
import jakarta.json.JsonNumber;
54
import jakarta.json.JsonObject;
55
import jakarta.json.JsonArray;
56
import jakarta.json.JsonObjectBuilder;
57
import jakarta.validation.ConstraintViolation;
58
import jakarta.ws.rs.core.MediaType;
59
import jakarta.ws.rs.core.Response;
60

61
import edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder;
62
import org.apache.commons.io.IOUtils;
63
import edu.harvard.iq.dataverse.engine.command.impl.CreateNewDataFilesCommand;
64
import edu.harvard.iq.dataverse.storageuse.UploadSessionQuotaLimit;
65
import static jakarta.ws.rs.core.Response.Status.BAD_REQUEST;
66

67
/**
68
 *  Methods to add or replace a single file.
69
 * 
70
 *  Usage example:
71
 *
72
 * // (1) Instantiate the class
73
 * 
74
 *  AddReplaceFileHelper addFileHelper = new AddReplaceFileHelper(dvRequest2,
75
 *                                               this.ingestService,
76
 *                                               this.datasetService,
77
 *                                               this.fileService,
78
 *                                               this.permissionSvc,
79
 *                                               this.commandEngine);
80
 * 
81
 * // (2) Run file "ADD"
82
 *
83
 *  addFileHelper.runAddFileByDatasetId(datasetId,
84
 *                               newFileName,
85
 *                               newFileContentType,
86
 *                               newFileInputStream);
87
 *  // (2a) Check for errors
88
 *  if (addFileHelper.hasError()){
89
 *      // get some errors
90
 *      System.out.println(addFileHelper.getErrorMessagesAsString("\n"));
91
 *  }
92
 * 
93
 *
94
 * // OR (3) Run file "REPLACE"
95
 *
96
 *  addFileHelper.runReplaceFile(datasetId,
97
 *                               newFileName,
98
 *                               newFileContentType,
99
 *                               newFileInputStream,
100
 *                               fileToReplaceId);
101
 *  // (2a) Check for errors
102
 *  if (addFileHelper.hasError()){
103
 *      // get some errors
104
 *      System.out.println(addFileHelper.getErrorMessagesAsString("\n"));
105
 *  }
106
 *
107
 * 
108
 * 
109
 * @author rmp553
110
 */
111
public class AddReplaceFileHelper{
112
    
113
    private static final Logger logger = Logger.getLogger(AddReplaceFileHelper.class.getCanonicalName());
×
114
    
115
    public static String FILE_ADD_OPERATION = "FILE_ADD_OPERATION";
×
116
    public static String FILE_REPLACE_OPERATION = "FILE_REPLACE_OPERATION";
×
117
    public static String FILE_REPLACE_FORCE_OPERATION = "FILE_REPLACE_FORCE_OPERATION";
×
118

119
    private String currentOperation;
120
    boolean multifile = false;
×
121
    // -----------------------------------
122
    // All the needed EJBs, passed to the constructor
123
    // -----------------------------------
124
    private IngestServiceBean ingestService;
125
    private DatasetServiceBean datasetService;
126
    private DataFileServiceBean fileService;        
127
    private PermissionServiceBean permissionService;
128
    private EjbDataverseEngine commandEngine;
129
    private SystemConfig systemConfig;
130
    // -----------------------------------
131
    // Instance variables directly added
132
    // -----------------------------------
133
    private Dataset dataset;                    // constructor (for add, not replace)
134
    private DataverseRequest dvRequest;         // constructor
135
    private InputStream newFileInputStream;     // step 30
136
    private String newFileName;                 // step 30
137
    private String newFileContentType;          // step 30
138
    private String newStorageIdentifier;        // step 30
139
    private String newCheckSum;                 // step 30
140
    private ChecksumType newCheckSumType;       //step 30
141
    
142
    // -- Optional  
143
    private DataFile fileToReplace;             // step 25
144
    
145
    private DatasetVersion workingVersion;
146
    private DatasetVersion clone;
147
    List<DataFile> initialFileList; 
148
    List<DataFile> finalFileList;
149
    
150
    // -----------------------------------
151
    // Ingested files
152
    // -----------------------------------
153
    private List<DataFile> newlyAddedFiles; 
154
    private List<FileMetadata> newlyAddedFileMetadatas;
155
    // -----------------------------------
156
    // For error handling
157
    // -----------------------------------
158
    
159
    private boolean errorFound;
160
    private List<String> errorMessages;
161
    private Response.Status httpErrorCode; // optional
162
    
163
    // For Force Replace, this becomes a warning rather than an error
164
    //
165
    private boolean contentTypeWarningFound;
166
    private String contentTypeWarningString;
167
    
168
    private boolean duplicateFileErrorFound;
169

170
    private String duplicateFileErrorString;
171

172
    private boolean duplicateFileWarningFound;
173
    private String duplicateFileWarningString;
174
    
175
    private String duplicateFileComponentMessage;
176

177
    public String getDuplicateFileComponentMessage() {
178
        return duplicateFileComponentMessage;
×
179
    }
180

181
    public void setDuplicateFileComponentMessage(String duplicateFileComponentMessage) {
182
        this.duplicateFileComponentMessage = duplicateFileComponentMessage;
×
183
    }
×
184
    
185
    public boolean isDuplicateFileErrorFound() {
186
        return duplicateFileErrorFound;
×
187
    }
188

189
    public void setDuplicateFileErrorFound(boolean duplicateFileErrorFound) {
190
        this.duplicateFileErrorFound = duplicateFileErrorFound;
×
191
    }
×
192

193
    public String getDuplicateFileErrorString() {
194
        return duplicateFileErrorString;
×
195
    }
196

197
    public void setDuplicateFileErrorString(String duplicateFileErrorString) {
198
        this.duplicateFileErrorString = duplicateFileErrorString;
×
199
    }
×
200
    
201
    public boolean isDuplicateFileWarningFound() {
202
        return duplicateFileWarningFound;
×
203
    }
204

205
    public void setDuplicateFileWarningFound(boolean duplicateFileWarningFound) {
206
        this.duplicateFileWarningFound = duplicateFileWarningFound;
×
207
    }
×
208

209
    public String getDuplicateFileWarningString() {
210
        return duplicateFileWarningString;
×
211
    }
212

213
    public void setDuplicateFileWarningString(String duplicateFileWarningString) {
214
        this.duplicateFileWarningString = duplicateFileWarningString;
×
215
    }
×
216
    
217
    public void resetFileHelper(){
218
        
219
        initErrorHandling();
×
220
        
221
        // operation
222
        currentOperation = null;
×
223
        
224
        // dataset level
225
        dataset = null;
×
226
        
227
        // file to replace
228
        fileToReplace = null;
×
229
        
230
        newFileInputStream = null;    
×
231
        newFileName = null;    
×
232
        newFileContentType = null;    
×
233
    
234
        // file lists
235
        initialFileList = null;
×
236
        finalFileList = null;
×
237
        
238
        // final files
239
        newlyAddedFiles = null;
×
240
        newlyAddedFileMetadatas = null;
×
241
        
242
    }
×
243
    
244
    /** 
245
     * MAIN CONSTRUCTOR -- minimal requirements
246
     * 
247
     * @param dataset
248
     * @param ingestService
249
     * @param datasetService
250
     * @param dvRequest 
251
     */
252
    public AddReplaceFileHelper(DataverseRequest dvRequest, 
253
                            IngestServiceBean ingestService,
254
                            DatasetServiceBean datasetService,
255
                            DataFileServiceBean fileService,
256
                            PermissionServiceBean permissionService,
257
                            EjbDataverseEngine commandEngine,
258
                            SystemConfig systemConfig){
×
259

260
        // ---------------------------------
261
        // make sure DataverseRequest isn't null and has a user
262
        // ---------------------------------
263
        if (dvRequest == null){
×
264
            throw new NullPointerException("dvRequest cannot be null");
×
265
        }
266
        if (dvRequest.getUser() == null){
×
267
            throw new NullPointerException("dvRequest cannot have a null user");
×
268
        }
269

270
        // ---------------------------------
271
        // make sure services aren't null
272
        // ---------------------------------
273
        if (ingestService == null){
×
274
            throw new NullPointerException("ingestService cannot be null");
×
275
        }
276
        if (datasetService == null){
×
277
            throw new NullPointerException("datasetService cannot be null");
×
278
        }
279
        if (fileService == null){
×
280
            throw new NullPointerException("fileService cannot be null");
×
281
        }
282
        if (permissionService == null){
×
283
            throw new NullPointerException("ingestService cannot be null");
×
284
        }
285
        if (commandEngine == null){
×
286
            throw new NullPointerException("commandEngine cannot be null");
×
287
        }
288
        if (systemConfig == null) {
×
289
            throw new NullPointerException("systemConfig cannot be null");
×
290
        }
291

292
        // ---------------------------------
293
        
294
        this.ingestService = ingestService;
×
295
        this.datasetService = datasetService;
×
296
        this.fileService = fileService;
×
297
        this.permissionService = permissionService;
×
298
        this.commandEngine = commandEngine;
×
299
        this.systemConfig = systemConfig;
×
300
        initErrorHandling();
×
301
        
302
        // Initiate instance vars
303
        this.dataset = null;
×
304
        this.dvRequest = dvRequest;
×
305
        dvRequest.getUser();
×
306
        
307
    }
×
308

309
    /**
310
     *
311
     * @param chosenDataset
312
     * @param newFileName
313
     * @param newFileContentType
314
     * @param newFileInputStream
315
     * @param optionalFileParams
316
     * @return
317
     */
318
    public boolean runAddFileByDataset(Dataset chosenDataset,
319
                                       String newFileName,
320
                                       String newFileContentType,
321
                                       String newStorageIdentifier,
322
                                       InputStream newFileInputStream,
323
                                       OptionalFileParams optionalFileParams){
324
        return this.runAddFileByDataset(chosenDataset,newFileName,newFileContentType,newStorageIdentifier,newFileInputStream,optionalFileParams,false);
×
325

326
    }
327

328
    private boolean runAddFileByDataset(Dataset chosenDataset,
329
                                       String newFileName,
330
                                       String newFileContentType,
331
                                       String newStorageIdentifier,
332
                                       InputStream newFileInputStream,
333
                                       OptionalFileParams optionalFileParams,
334
                                       boolean multipleFiles) {
335

336
        msgt(">> runAddFileByDatasetId");
×
337

338
        initErrorHandling();
×
339

340
        multifile=multipleFiles;
×
341
        this.currentOperation = FILE_ADD_OPERATION;
×
342

343
        if (!this.step_001_loadDataset(chosenDataset)){
×
344
            return false;
×
345
        }
346

347
        //return this.runAddFile(this.dataset, newFileName, newFileContentType, newFileInputStream, optionalFileParams);
348
        return this.runAddReplaceFile(dataset, newFileName, newFileContentType, newStorageIdentifier, newFileInputStream, optionalFileParams);
×
349

350
    }
351
    
352
    
353
    /**
354
     * After the constructor, this method is called to add a file
355
     * 
356
     * @param dataset
357
     * @param newFileName
358
     * @param newFileContentType
359
     * @param newFileInputStream
360
     * @return 
361
     */
362
    /*
363
    public boolean runAddFile(Dataset dataset,
364
                            String newFileName, 
365
                            String newFileContentType, 
366
                            InputStream newFileInputStream, 
367
                            OptionalFileParams optionalFileParams){
368
        msgt(">> runAddFile");
369
        
370
        initErrorHandling();
371
        
372
        if (this.hasError()){
373
            return false;
374
        }
375
        this.currentOperation = FILE_ADD_OPERATION;
376
        
377
        return this.runAddReplaceFile(dataset, newFileName, newFileContentType, newFileInputStream, optionalFileParams);
378
    }*/
379
    
380

381
    public boolean runForceReplaceFile(long fileToReplaceId, String newFilename, String newFileContentType,
382
        String newStorageIdentifier, InputStream newFileInputStream, Dataset ds, OptionalFileParams optionalFileParams) {
383
        return runForceReplaceFile(fileToReplaceId, newFilename, newFileContentType,
×
384
                newStorageIdentifier, newFileInputStream, ds, optionalFileParams, false);
385
    }
386
    /**
387
     * After the constructor, this method is called to replace a file
388
     * 
389
     * @param dataset
390
     * @param newFileName
391
     * @param newFileContentType
392
     * @param newStorageIdentifier2 
393
     * @param newFileInputStream
394
     * @return 
395
     */
396
    private boolean runForceReplaceFile(Long oldFileId,
397
                        String newFileName, 
398
                        String newFileContentType, 
399
                        String newStorageIdentifier,
400
                        InputStream newFileInputStream,
401
                        Dataset ds,
402
                        OptionalFileParams optionalFileParams,
403
                        boolean multipleFiles){
404
        
405
        msgt(">> runForceReplaceFile");
×
406
        initErrorHandling();
×
407

408
        multifile=multipleFiles;
×
409
        this.currentOperation = FILE_REPLACE_FORCE_OPERATION;
×
410

411
               
412
        if (oldFileId==null){
×
413
            this.addErrorSevere(getBundleErr("existing_file_to_replace_id_is_null"));
×
414
            return false;
×
415
        }
416
       
417
        // Loads local variable "fileToReplace"
418
        //
419
        if (!this.step_005_loadFileToReplaceById(oldFileId)){
×
420
            return false;
×
421
        }
422
        if(!ds.getId().equals(fileToReplace.getOwner().getId())) {
×
423
            this.addErrorSevere(getBundleErr("existing_file_to_replace_not_in_dataset"));
×
424
            return false;
×
425
        }
426
        // ds may include changes not yet in the copy created when loading the file from the db, as in replaceFiles()
427
        return this.runAddReplaceFile(ds, newFileName, newFileContentType, newStorageIdentifier, newFileInputStream, optionalFileParams);
×
428
    }
429
    
430

431
    public boolean runReplaceFile(long fileToReplaceId, String newFilename, String newFileContentType,
432
            String newStorageIdentifier, InputStream newFileInputStream, Dataset ds, OptionalFileParams optionalFileParams) {
433
        return runReplaceFile(fileToReplaceId, newFilename, newFileContentType,
×
434
                newStorageIdentifier, newFileInputStream, ds, optionalFileParams, false);
435
        
436
    }
437
    
438
    private boolean runReplaceFile(Long oldFileId,
439
                            String newFileName, 
440
                            String newFileContentType, 
441
                            String newStorageIdentifier, 
442
                            InputStream newFileInputStream,
443
                            Dataset ds,
444
                            OptionalFileParams optionalFileParams,
445
                            boolean multipleFiles){
446
    
447
        msgt(">> runReplaceFile");
×
448

449
        initErrorHandling();
×
450
        multifile=multipleFiles;
×
451
        this.currentOperation = FILE_REPLACE_OPERATION;
×
452
        
453
        if (oldFileId==null){
×
454
            this.addErrorSevere(getBundleErr("existing_file_to_replace_id_is_null"));
×
455
            return false;
×
456
        }
457
        
458
         
459
        // Loads local variable "fileToReplace"
460
        //
461
        if (!this.step_005_loadFileToReplaceById(oldFileId)){
×
462
            return false;
×
463
        }
464

465
        if(!ds.getId().equals(fileToReplace.getOwner().getId())) {
×
466
            this.addErrorSevere(getBundleErr("existing_file_to_replace_not_in_dataset"));
×
467
            return false;
×
468
        }
469
        // ds may include changes not yet in the copy created when loading the file from the db, as in replaceFiles()
470
        return this.runAddReplaceFile(ds, newFileName, newFileContentType, newStorageIdentifier, newFileInputStream, optionalFileParams);
×
471
    }
472
    
473
    
474
    
475
    /**
476
     * Here we're going to run through the steps to ADD or REPLACE a file
477
     * 
478
     * The difference between ADD and REPLACE (add/delete) is:
479
     * 
480
     *  oldFileId - For ADD, set to null
481
     *  oldFileId - For REPLACE, set to id of file to replace 
482
     * 
483
     * This has now been broken into Phase 1 and Phase 2
484
     * 
485
     * The APIs will use this method and call Phase 1 & Phase 2 consecutively
486
     * 
487
     * The UI will call Phase 1 on initial upload and 
488
     *   then run Phase 2 if the user chooses to save the changes.
489
     * @param newStorageIdentifier 
490
     * 
491
     * 
492
     * @return 
493
     */
494
    
495
    private boolean runAddReplaceFile(Dataset owner,  
496
            String newFileName, String newFileContentType, 
497
            String newStorageIdentifier, InputStream newFileInputStream,
498
            OptionalFileParams optionalFileParams){
499
        
500
        // Run "Phase 1" - Initial ingest of file + error check
501
        // But don't save the dataset version yet
502
        //
503
        boolean phase1Success = runAddReplacePhase1(owner,  
×
504
                                        newFileName,  
505
                                        newFileContentType,  
506
                                        newStorageIdentifier,
507
                                        newFileInputStream,
508
                                        optionalFileParams
509
                                        );
510
        if (!phase1Success){
×
511
            return false;
×
512
        }
513
        boolean tabIngest = true;
×
514
        if (optionalFileParams != null) {
×
515
            tabIngest = optionalFileParams.getTabIngest();
×
516
        }
517
        return runAddReplacePhase2(tabIngest);
×
518
        
519
    }
520

521
    /**
522
     * Note: UI replace is always a "force replace" which means
523
     *  the replacement file can have a different content type
524
     * 
525
     * @param oldFileId
526
     * @param newFileName
527
     * @param newFileContentType
528
     * @param newFileInputStream
529
     * @param optionalFileParams
530
     * @return 
531
     */
532
    public boolean runReplaceFromUI_Phase1(Long oldFileId,  
533
            String newFileName, 
534
            String newFileContentType,
535
            InputStream newFileInputStream,
536
            String fullStorageId,
537
            OptionalFileParams optionalFileParams){
538
        
539
        
540
        initErrorHandling();
×
541
        this.currentOperation = FILE_REPLACE_FORCE_OPERATION;
×
542
        
543
        if (oldFileId==null){
×
544
            this.addErrorSevere(getBundleErr("existing_file_to_replace_id_is_null"));
×
545
            return false;
×
546
        }
547
        
548
         
549
        // Loads local variable "fileToReplace"
550
        //
551
        if (!this.step_005_loadFileToReplaceById(oldFileId)){
×
552
            return false;
×
553
        }
554
        //Update params to match existing file (except checksum, which should match the new file)
555
        if(fileToReplace != null) {
×
556
            String checksum = optionalFileParams.getCheckSum();
×
557
            ChecksumType checkSumType = optionalFileParams.getCheckSumType();
×
558
            try {
559
                optionalFileParams = new OptionalFileParams(fileToReplace);
×
560
                optionalFileParams.setCheckSum(checksum, checkSumType);
×
561
            } catch (DataFileTagException e) {
×
562
                // Shouldn't happen since fileToReplace should have valid tags
563
                e.printStackTrace();
×
564
            }
×
565
        }
566

567
        return this.runAddReplacePhase1(fileToReplace.getOwner(), 
×
568
                newFileName, 
569
                newFileContentType,
570
                fullStorageId,
571
                newFileInputStream, 
572
                optionalFileParams);
573

574
       
575
    }
576
    
577
    
578
    /**
579
     * For the UI: File add/replace has been broken into 2 steps
580
     * 
581
     * Phase 1 (here): Add/replace the file and make sure there are no errors
582
     *          But don't update the Dataset (yet)
583
     * @param newStorageIdentifier 
584
     * 
585
     * @return 
586
     */
587
    private boolean runAddReplacePhase1(Dataset owner,  
588
            String newFileName, 
589
            String newFileContentType,
590
            String newStorageIdentifier, InputStream newFileInputStream,
591
            OptionalFileParams optionalFileParams){
592
        
593
        if (this.hasError()){
×
594
            return false;   // possible to have errors already...
×
595
        }
596

597
        msgt("step_001_loadDataset");
×
598
        if (!this.step_001_loadDataset(owner)){
×
599
            return false;
×
600
        }
601
        
602
        msgt("step_010_VerifyUserAndPermissions");
×
603
        if (!this.step_010_VerifyUserAndPermissions()){
×
604
            return false;
×
605
            
606
        }
607

608
        msgt("step_020_loadNewFile");
×
609
        if (!this.step_020_loadNewFile(newFileName, newFileContentType, newStorageIdentifier, newFileInputStream)){
×
610
            return false;
×
611
            
612
        }
613
        if(optionalFileParams != null) {
×
614
                if(optionalFileParams.hasCheckSum()) {
×
615
                        newCheckSum = optionalFileParams.getCheckSum();
×
616
                        newCheckSumType = optionalFileParams.getCheckSumType();
×
617
                }
618
        }
619

620
        msgt("step_030_createNewFilesViaIngest");
×
621
        if (!this.step_030_createNewFilesViaIngest()){
×
622
            return false;
×
623
            
624
        }
625

626
        msgt("step_050_checkForConstraintViolations");
×
627
        if (!this.step_050_checkForConstraintViolations()){
×
628
            return false;            
×
629
        }
630

631
        msgt("step_055_loadOptionalFileParams");
×
632
        if (!this.step_055_loadOptionalFileParams(optionalFileParams)){
×
633
            return false;            
×
634
        }
635
        
636
        // if the fileToReplace hasn't been released,
637
        if (fileToReplace != null && !fileToReplace.isReleased()) {
×
638
            DataFile df = finalFileList.get(0); // step_055 uses a loop and assumes only one file
×
639
            // set the replacement file's previous and root datafileIds to match (unless
640
            // they are the defaults)
641
            if (fileToReplace.getPreviousDataFileId() != null) {
×
642
                df.setPreviousDataFileId(fileToReplace.getPreviousDataFileId());
×
643
                df.setRootDataFileId(fileToReplace.getRootDataFileId());
×
644
            }
645
            // Reuse any file PID during a replace operation (if File PIDs are in use)
646
            if (systemConfig.isFilePIDsEnabledForCollection(owner.getOwner())) {
×
647
                df.setGlobalId(fileToReplace.getGlobalId());
×
648
                df.setGlobalIdCreateTime(fileToReplace.getGlobalIdCreateTime());
×
649
                // Should be true or fileToReplace wouldn't have an identifier (since it's not
650
                // yet released in this if statement)
651
                df.setIdentifierRegistered(fileToReplace.isIdentifierRegistered());
×
652
                fileToReplace.setGlobalId(null);
×
653
            }
654
        }
655
        
656
        if(fileToReplace != null && fileToReplace.getEmbargo() != null) {
×
657
            DataFile df = finalFileList.get(0); // step_055 uses a loop and assumes only one file
×
658
            df.setEmbargo(fileToReplace.getEmbargo());
×
659
        }
660
      
661

662
        return true;
×
663
    }
664
    
665
    
666
    public boolean runReplaceFromUI_Phase2(){
667
        return runAddReplacePhase2(true);
×
668
    }
669
    
670

671
    /**
672
     * Called from the UI backing bean
673
     * 
674
     * @param categoriesList
675
     * @return 
676
     */
677
    public boolean updateCategoriesFromUI(List<String> categoriesList){
678
        if (hasError()){
×
679
            logger.severe("Should not be calling this method");
×
680
            return false;
×
681
        }
682
        
683
        if ((finalFileList==null)||(finalFileList.size()==0)){
×
684
            throw new NullPointerException("finalFileList needs at least 1 file!!");
×
685
        }
686
        
687
        // don't need to make updates
688
        //
689
        if (categoriesList ==null){
×
690
            return true;           
×
691
        }
692
        
693
        // remove nulls, dupes, etc.
694
        //
695
        categoriesList = Util.removeDuplicatesNullsEmptyStrings(categoriesList);
×
696
        if (categoriesList.isEmpty()){
×
697
            return true;
×
698
        }
699
        
700
        for (DataFile df : finalFileList){
×
701
            
702
            df.getFileMetadata().setCategoriesByName(categoriesList);
×
703
        }
×
704
        
705
        return true;
×
706
    }
707
    
708
    /**
709
     * Called from the UI backing bean
710

711
     * @param label
712
     * @param description
713
     * @param restricted
714
     * @return 
715
     */
716
    public boolean updateLabelDescriptionRestrictedFromUI(String label, String description, Boolean restricted){
717
                
718
        if (hasError()){
×
719
            logger.severe("Should not be calling this method");
×
720
            return false;
×
721
        }
722
        
723
        if ((finalFileList==null)||(finalFileList.size()==0)){
×
724
            throw new NullPointerException("finalFileList needs at least 1 file!!");
×
725
        }
726
        
727
        
728
        for (DataFile df : finalFileList){
×
729
            
730
            // update description
731
            if (description != null){
×
732
                df.getFileMetadata().setDescription(description.trim());
×
733
            }        
734

735
            // update label
736
            if (label != null){
×
737
                df.getFileMetadata().setLabel(label.trim());
×
738
            }               
739
            
740
            // update restriction
741
            if (restricted == null){
×
742
                restricted = false;
×
743
            }
744
            
745
            df.getFileMetadata().setRestricted(restricted);
×
746
        }
×
747
        
748
        return true;
×
749
    }
750
    
751
    /**
752
     * For the UI: File add/replace has been broken into 2 steps
753
     * 
754
     * Phase 2 (here): Phase 1 has run ok, Update the Dataset -- issue the commands!
755
     * 
756
     * @return 
757
     */
758
    private boolean runAddReplacePhase2(boolean tabIngest){
759
        
760
        if (this.hasError()){
×
761
            return false;   // possible to have errors already...
×
762
        }
763

764
        if ((finalFileList ==  null)||(finalFileList.isEmpty())){
×
765
            addError(getBundleErr("phase2_called_early_no_new_files"));
×
766
            return false;
×
767
        }
768
        
769
         msgt("step_060_addFilesViaIngestService");
×
770
        if (!this.step_060_addFilesViaIngestService(tabIngest)){
×
771
            return false;
×
772
            
773
        }
774
        if (this.isFileReplaceOperation()) {
×
775
            msgt("step_080_run_update_dataset_command_for_replace");
×
776
            if (!this.step_080_run_update_dataset_command_for_replace()) {
×
777
                return false;
×
778
            }
779
        } else if (!multifile) {
×
780
            msgt("step_070_run_update_dataset_command");
×
781
            if (!this.step_070_run_update_dataset_command()) {
×
782
                return false;
×
783
            }
784
        }
785

786
        msgt("step_090_notifyUser");
×
787
        if (!this.step_090_notifyUser()){
×
788
            return false;            
×
789
        }
790

791
        msgt("step_100_startIngestJobs");
×
792
        if (!this.step_100_startIngestJobs()){
×
793
            return false;            
×
794
        }
795

796
        return true;
×
797
    }
798
    
799
    
800
    /**
801
     *  Get for currentOperation
802
     *  @return String
803
     */
804
    public String getCurrentOperation(){
805
        return this.currentOperation;
×
806
    }
807

808
    
809
    /**
810
     * Is this a file FORCE replace operation?
811
     * 
812
     * Only overrides warnings of content type change
813
     * 
814
     * @return 
815
     */
816
    public boolean isForceFileOperation(){
817
        
818
        return this.currentOperation.equals(FILE_REPLACE_FORCE_OPERATION);
×
819
    }
820
    
821
    /**
822
     * Is this a file replace operation?
823
     * @return 
824
     */
825
    public boolean isFileReplaceOperation(){
826
    
827
        if (this.currentOperation.equals(FILE_REPLACE_OPERATION)){
×
828
            return true;
×
829
        }else if (this.currentOperation.equals(FILE_REPLACE_FORCE_OPERATION)){
×
830
            return true;
×
831
        }
832
        return false;
×
833
    }
834
    
835
    /**
836
     * Is this a file add operation?
837
     * 
838
     * @return 
839
     */
840
    public boolean isFileAddOperation(){
841
    
842
        return this.currentOperation.equals(FILE_ADD_OPERATION);
×
843
    }
844

845
    /**
846
     * Initialize error handling vars
847
     */
848
    private void initErrorHandling(){
849

850
        this.errorFound = false;
×
851
        this.errorMessages = new ArrayList<>();
×
852
        this.httpErrorCode = null;
×
853
        
854
        
855
        contentTypeWarningFound = false;
×
856
        contentTypeWarningString = null;
×
857
    }
×
858
         
859
    
860

861
    /**
862
     * Add error message
863
     * 
864
     * @param errMsg 
865
     */
866
    private void addError(String errMsg){
867
        
868
        if (errMsg == null){
×
869
            throw new NullPointerException("errMsg cannot be null");
×
870
        }
871
        this.errorFound = true;
×
872
 
873
        logger.fine(errMsg);
×
874
        this.errorMessages.add(errMsg);
×
875
    }
×
876
    
877
    /**
878
     * Add Error mesage and, if it's known, the HTTP response code
879
     * 
880
     * @param badHttpResponse, e.g. Response.Status.FORBIDDEN
881
     * @param errMsg 
882
     */
883
    private void addError(Response.Status badHttpResponse, String errMsg){
884
        
885
        if (badHttpResponse == null){
×
886
            throw new NullPointerException("badHttpResponse cannot be null");
×
887
        }
888
        if (errMsg == null){
×
889
            throw new NullPointerException("errMsg cannot be null");
×
890
        }
891
      
892
        this.httpErrorCode = badHttpResponse;
×
893
        
894
        this.addError(errMsg);
×
895
                
896
        
897
    }
×
898
    
899
    private void addErrorWarning(String errMsg){
900
        if (errMsg == null){
×
901
            throw new NullPointerException("errMsg cannot be null");
×
902
        }
903
 
904
        logger.severe(errMsg);
×
905
        this.setDuplicateFileWarning(errMsg);
×
906
        this.errorMessages.add(errMsg);
×
907
        
908
    }
×
909
    
910
    
911
    private void addErrorSevere(String errMsg){
912
        
913
        if (errMsg == null){
×
914
            throw new NullPointerException("errMsg cannot be null");
×
915
        }
916
        this.errorFound = true;
×
917
 
918
        logger.severe(errMsg);
×
919
        this.errorMessages.add(errMsg);
×
920
    }
×
921

922
    
923
    /**
924
     * Was an error found?
925
     * 
926
     * @return 
927
     */
928
    public boolean hasError(){
929
        return this.errorFound;
×
930
        
931
    }
932
    
933
    /**
934
     * get error messages
935
     * 
936
     * @return 
937
     */
938
    public List<String> getErrorMessages(){
939
        return this.errorMessages;
×
940
    }   
941

942
    /**
943
     * get error messages as string 
944
     * 
945
     * @param joinString
946
     * @return 
947
     */
948
    public String getErrorMessagesAsString(String joinString){
949
        if (joinString==null){
×
950
            joinString = "\n";
×
951
        }
952
        return String.join(joinString, this.errorMessages);
×
953
    }   
954

955
   
956
    /**
957
     * For API use, return the HTTP error code
958
     * 
959
     * Default is BAD_REQUEST
960
     * 
961
     * @return 
962
     */
963
    public Response.Status getHttpErrorCode(){
964
       
965
        if (!hasError()){
×
966
            logger.severe("Do not call this method unless there is an error!  check '.hasError()'");
×
967
        }
968
        
969
        if (httpErrorCode == null){
×
970
            return Response.Status.BAD_REQUEST;
×
971
        }else{
972
            return httpErrorCode;
×
973
        }
974
    }
975
    
976
    
977
    /**
978
     * Convenience method for getting bundle properties
979
     * 
980
     * @param msgName
981
     * @return 
982
     * @deprecated This method is deprecated because you have to know to search
983
     * only part of a bundle key ("add_file_error") rather than the full bundle
984
     * key ("file.addreplace.error.add.add_file_error") leading you to believe
985
     * that the bundle key is not used.
986
     */
987
    @Deprecated
988
    private String getBundleMsg(String msgName, boolean isErr){
989
        if (msgName == null){
×
990
            throw new NullPointerException("msgName cannot be null");
×
991
        }
992
        if (isErr){        
×
993
            return BundleUtil.getStringFromBundle("file.addreplace.error." + msgName);
×
994
        }else{
995
            return BundleUtil.getStringFromBundle("file.addreplace.success." + msgName);
×
996
        }
997
       
998
    }
999
    
1000
    /**
1001
     * Convenience method for getting bundle error message
1002
     * 
1003
     * @param msgName
1004
     * @return 
1005
     */
1006
    private String getBundleErr(String msgName){
1007
        return this.getBundleMsg(msgName, true);
×
1008
    }
1009
    
1010
    
1011
     
1012
    /**
1013
     * 
1014
     */
1015
    private boolean step_001_loadDataset(Dataset selectedDataset){
1016

1017
        if (this.hasError()){
×
1018
            return false;
×
1019
        }
1020

1021
        if (selectedDataset == null){
×
1022
            this.addErrorSevere(getBundleErr("dataset_is_null"));
×
1023
            return false;
×
1024
        }
1025

1026
        dataset = selectedDataset;
×
1027
        
1028
        return true;
×
1029
    }
1030
    
1031
    
1032
    
1033
    /**
1034
     *  Step 10 Verify User and Permissions
1035
     * 
1036
     * 
1037
     * @return 
1038
     */
1039
    private boolean step_010_VerifyUserAndPermissions(){
1040
        
1041
        if (this.hasError()){
×
1042
            return false;
×
1043
        }
1044
                
1045
        return step_015_auto_check_permissions(dataset);
×
1046

1047
    }
1048
    
1049
    private boolean step_015_auto_check_permissions(Dataset datasetToCheck){
1050
        
1051
        if (this.hasError()){
×
1052
            return false;
×
1053
        }
1054
        
1055
        if (datasetToCheck == null){
×
1056
            addError(getBundleErr("dataset_is_null"));
×
1057
            return false;
×
1058
        }
1059
        
1060
        // Make a temp. command
1061
        //
1062
        
1063
        Command updateDatasetVersionCommand = new UpdateDatasetVersionCommand(datasetToCheck, dvRequest);
×
1064
        
1065
        // Can this user run the command?
1066
        //
1067
        if (!permissionService.isUserAllowedOn(dvRequest.getUser(), updateDatasetVersionCommand, datasetToCheck)) {
×
1068
            addError(Response.Status.FORBIDDEN,getBundleErr("no_edit_dataset_permission"));
×
1069
           return false;
×
1070
        }
1071
        
1072
        return true;
×
1073
        
1074
    }
1075
    
1076
    
1077
    private boolean step_020_loadNewFile(String fileName, String fileContentType, String storageIdentifier, InputStream fileInputStream){
1078
        
1079
        if (this.hasError()){
×
1080
            return false;
×
1081
        }
1082
        
1083
        if (fileName == null){
×
1084
            this.addErrorSevere(getBundleErr("filename_undetermined"));
×
1085
            return false;
×
1086
            
1087
        }
1088

1089
        if (fileContentType == null){
×
1090
            this.addErrorSevere(getBundleErr("file_content_type_undetermined"));
×
1091
            return false;
×
1092
            
1093
        }
1094
        
1095
                if (fileInputStream == null) {
×
1096
                        if (storageIdentifier == null) {
×
1097
                                this.addErrorSevere(getBundleErr("file_upload_failed"));
×
1098
                                return false;
×
1099
                        } 
1100
                } 
1101
                
1102
        newFileName = fileName;
×
1103
        newFileContentType = fileContentType;
×
1104
        
1105
        //One of these will be null
1106
            newStorageIdentifier = storageIdentifier;
×
1107
        newFileInputStream = fileInputStream;
×
1108
        
1109
        return true;
×
1110
    }
1111

1112
    
1113
    /**
1114
     * Optional: old file to replace
1115
     * 
1116
     * @param oldFile
1117
     * @return 
1118
     */
1119
    private boolean step_005_loadFileToReplaceById(Long dataFileId){
1120
        
1121
        if (this.hasError()){
×
1122
            return false;
×
1123
        }
1124
        
1125
        //  Check for Null
1126
        //
1127
        if (dataFileId == null){
×
1128
            this.addErrorSevere(getBundleErr("existing_file_to_replace_id_is_null"));
×
1129
            return false;
×
1130
        }
1131
        
1132
        // Does the file exist?
1133
        //
1134
        DataFile existingFile = fileService.find(dataFileId);
×
1135
        
1136
        if (existingFile == null){           
×
1137
            this.addError(BundleUtil.getStringFromBundle("file.addreplace.error.existing_file_to_replace_not_found_by_id", Collections.singletonList(dataFileId.toString())));
×
1138
            return false;
×
1139
        } 
1140
        
1141

1142
        // Do we have permission to replace this file? e.g. Edit the file's dataset
1143
        //
1144
        if (!step_015_auto_check_permissions(existingFile.getOwner())){
×
1145
            return false;
×
1146
        };
1147
        // Is the file in the latest dataset version?
1148
        //
1149
        if (!step_007_auto_isReplacementInLatestVersion(existingFile)){
×
1150
            return false;
×
1151
        }
1152
        
1153
        fileToReplace = existingFile;
×
1154
        
1155
        return true;        
×
1156

1157
    }
1158
    
1159
    /**
1160
     * Make sure the file to replace is in the workingVersion
1161
     *  -- e.g. that it wasn't deleted from a previous Version
1162
     * 
1163
     * @return 
1164
     */
1165
    private boolean step_007_auto_isReplacementInLatestVersion(DataFile existingFile){
1166
        
1167
        if (existingFile == null){
×
1168
            throw new NullPointerException("existingFile cannot be null!");
×
1169
        }
1170

1171
        if (this.hasError()){
×
1172
            return false;
×
1173
        }
1174
        
1175
        
1176
        DatasetVersion latestVersion = existingFile.getOwner().getLatestVersion();
×
1177
        
1178
        boolean fileInLatestVersion = false;
×
1179
        for (FileMetadata fm : latestVersion.getFileMetadatas()){
×
1180
            if (fm.getDataFile().getId() != null){
×
1181
                if (Objects.equals(existingFile.getId(),fm.getDataFile().getId())){
×
1182
                    fileInLatestVersion = true;
×
1183
                }
1184
            }
1185
        }
×
1186
        if (!fileInLatestVersion){
×
1187
            addError(getBundleErr("existing_file_not_in_latest_published_version"));
×
1188
            return false;                        
×
1189
        }
1190
        return true;
×
1191
    }
1192
    
1193
    
1194
    private boolean step_030_createNewFilesViaIngest(){
1195
        
1196
        if (this.hasError()){
×
1197
            return false;
×
1198
        }
1199

1200
        // Load the working version of the Dataset
1201
        workingVersion = dataset.getOrCreateEditVersion();
×
1202
        if(!multifile) {
×
1203
            //Don't repeatedly update the clone (losing changes) in multifile case
1204
            clone = workingVersion.cloneDatasetVersion();
×
1205
        }
1206
        try {
1207
            /*CreateDataFileResult result = FileUtil.createDataFiles(workingVersion,
1208
                    this.newFileInputStream,
1209
                    this.newFileName,
1210
                    this.newFileContentType,
1211
                    this.newStorageIdentifier,
1212
                    this.newCheckSum,
1213
                    this.newCheckSumType,
1214
                    this.systemConfig);*/
1215
            
1216
            UploadSessionQuotaLimit quota = null; 
×
1217
            if (systemConfig.isStorageQuotasEnforced()) {
×
1218
                quota = fileService.getUploadSessionQuotaLimit(dataset);
×
1219
            }
1220
            Command<CreateDataFileResult> cmd = new CreateNewDataFilesCommand(dvRequest, workingVersion, newFileInputStream, newFileName, newFileContentType, newStorageIdentifier, quota, newCheckSum, newCheckSumType);
×
1221
            CreateDataFileResult createDataFilesResult = commandEngine.submit(cmd);
×
1222
            initialFileList = createDataFilesResult.getDataFiles();
×
1223

1224
        } catch (CommandException ex) {
×
1225
            if (ex.getMessage() != null && !ex.getMessage().isEmpty()) {
×
1226
                this.addErrorSevere(getBundleErr("ingest_create_file_err") + " " + ex.getMessage());
×
1227
            } else {
1228
                this.addErrorSevere(getBundleErr("ingest_create_file_err"));
×
1229
            }
1230
            logger.severe(ex.toString());
×
1231
            this.runMajorCleanup(); 
×
1232
            return false;
×
1233
        } finally {
1234
            IOUtils.closeQuietly(this.newFileInputStream);
×
1235
         }
1236
         /**
1237
         * This only happens:
1238
         *  (1) the dataset was empty
1239
         *  (2) the new file (or new file unzipped) did not ingest via "createDataFiles"
1240
         */
1241
        if (initialFileList.isEmpty()){
×
1242
            this.addErrorSevere(getBundleErr("initial_file_list_empty"));
×
1243
            this.runMajorCleanup();
×
1244
            return false;
×
1245
        }
1246
        
1247
        /**
1248
         * REPLACE: File replacement is limited to a single file!!
1249
         * 
1250
         * ADD: When adding files, some types of individual files
1251
         * are broken into several files--which is OK
1252
         */
1253
        if (isFileReplaceOperation()){
×
1254
            if (initialFileList.size() > 1){
×
1255
                this.addError(getBundleErr("initial_file_list_more_than_one"));
×
1256
                this.runMajorCleanup();
×
1257
                return false;
×
1258

1259
            }
1260
        }
1261
        
1262
        if (this.step_040_auto_checkForDuplicates()){
×
1263
            return true;
×
1264
        }
1265
                       
1266

1267
        /*
1268
            commenting out. see the comment in the source of the method below.
1269
        if (this.step_045_auto_checkForFileReplaceDuplicate()) {
1270
            return true;
1271
        }*/
1272
        
1273
        return false;
×
1274
    }
1275
    
1276
    
1277
    /**
1278
     * Create a "final file list" 
1279
     * 
1280
     * This is always run after step 30 -- the ingest
1281
     * 
1282
     * @return 
1283
     */
1284
    private boolean step_040_auto_checkForDuplicates(){
1285
        this.duplicateFileErrorString = "";
×
1286
        this.duplicateFileErrorFound = false;
×
1287
        
1288
        msgt("step_040_auto_checkForDuplicates");
×
1289
        if (this.hasError()){
×
1290
            return false;
×
1291
        }
1292
        
1293
        // Double checked -- this check also happens in step 30
1294
        //
1295
        if (initialFileList.isEmpty()){
×
1296
            this.addErrorSevere(getBundleErr("initial_file_list_empty"));
×
1297
            return false;
×
1298
        }
1299

1300
        // Initialize new file list
1301
        this.finalFileList = new ArrayList<>();
×
1302

1303
        if (isFileReplaceOperation() && this.fileToReplace == null){
×
1304
            // This error shouldn't happen if steps called correctly
1305
            this.addErrorSevere(getBundleErr("existing_file_to_replace_is_null") + " (This error shouldn't happen if steps called in sequence....checkForFileReplaceDuplicate)");
×
1306
            return false;
×
1307
        }
1308
        
1309
        // -----------------------------------------------------------
1310
        // Iterate through the recently ingest files
1311
        // -----------------------------------------------------------
1312
        for (DataFile df : initialFileList){
×
1313
             msg("Checking file: " + df.getFileMetadata().getLabel());
×
1314

1315
            // -----------------------------------------------------------
1316
            // (1) Check for ingest warnings
1317
            // -----------------------------------------------------------
1318
            if (df.isIngestProblem()) {
×
1319
                if (df.getIngestReportMessage() != null) {
×
1320
                    // may collect multiple error messages
1321
                    this.addError(df.getIngestReportMessage());
×
1322
                }
1323
                df.setIngestDone();
×
1324
            }
1325
          
1326
            
1327
            // -----------------------------------------------------------
1328
            // (2) Check for duplicates
1329
            // Only a warning now
1330
            // -----------------------------------------------------------     
1331
            if (isFileReplaceOperation() && Objects.equals(df.getChecksumValue(), fileToReplace.getChecksumValue())){
×
1332
                this.addError(getBundleErr("replace.new_file_same_as_replacement"));                
×
1333
                this.duplicateFileErrorFound = true;
×
1334
                this.duplicateFileErrorString = getBundleErr("replace.new_file_same_as_replacement");
×
1335
                break;
×
1336
            } 
1337
            
1338
            if (DuplicateFileChecker.isDuplicateOriginalWay(workingVersion, df.getFileMetadata())){
×
1339
                String dupeName = df.getFileMetadata().getLabel();
×
1340
                this.duplicateFileWarningFound = true;
×
1341
                this.duplicateFileWarningString = BundleUtil.getStringFromBundle("file.addreplace.warning.duplicate_file", 
×
1342
                                Arrays.asList(dupeName));
×
1343
                this.addErrorWarning(this.duplicateFileWarningString); 
×
1344

1345
            }             
1346
            finalFileList.add(df);
×
1347
        }
×
1348
        
1349
        if (this.hasError()){
×
1350
            // We're recovering from the duplicate check.
1351
            msg("We're recovering from a duplicate check 1");
×
1352
            runMajorCleanup();
×
1353
            msg("We're recovering from a duplicate check 2");
×
1354
            finalFileList.clear();           
×
1355
            return false;
×
1356
        }
1357
        
1358
       /**
1359
         * REPLACE: File replacement is limited to a single file!!
1360
         * 
1361
         * ADD: When adding files, some types of individual files
1362
         * are broken into several files--which is OK
1363
         */
1364
            
1365
       /**
1366
        *  Also: check that the file is being replaced with the same content type
1367
        *  file. Treat this as a fatal error, unless this is a "force replace" 
1368
        *  operation; then it should be treated as merely a warning.
1369
        */
1370
        if (isFileReplaceOperation()){
×
1371
        
1372
            if (finalFileList.size() > 1){     
×
1373
                String errMsg = "(This shouldn't happen -- error should have been detected in 030_createNewFilesViaIngest)";
×
1374
                this.addErrorSevere(getBundleErr("initial_file_list_more_than_one") + " " + errMsg);            
×
1375
                return false;
×
1376
            }
1377
            
1378
            // Has the content type of the file changed?
1379
            //
1380
            String fileType = fileToReplace.getOriginalFileFormat() != null ? fileToReplace.getOriginalFileFormat() : fileToReplace.getContentType();
×
1381
            if (!finalFileList.get(0).getContentType().equalsIgnoreCase(fileType)) {
×
1382
                String friendlyType = fileToReplace.getOriginalFormatLabel() != null ? fileToReplace.getOriginalFormatLabel() : fileToReplace.getFriendlyType();
×
1383

1384
                List<String> errParams = Arrays.asList(friendlyType,
×
1385
                                                finalFileList.get(0).getFriendlyType());
×
1386
                
1387
                String contentTypeErr = BundleUtil.getStringFromBundle("file.addreplace.error.replace.new_file_has_different_content_type", 
×
1388
                                errParams);
1389
                                        
1390
                if (isForceFileOperation()){
×
1391
                    // for force replace, just give a warning
1392
                    this.setContentTypeWarning(contentTypeErr);
×
1393
                }else{
1394
                    // not a force replace? it's an error
1395
                    this.addError(contentTypeErr);
×
1396
                    runMajorCleanup();
×
1397
                    return false;
×
1398
                }
1399
            }
1400
        }
1401
        
1402
        if (finalFileList.isEmpty()){
×
1403
            this.addErrorSevere("There are no files to add.  (This error shouldn't happen if steps called in sequence....step_040_auto_checkForDuplicates)");                
×
1404
            return false;
×
1405
        }
1406
        
1407
        
1408
        return true;
×
1409
    } // end step_040_auto_checkForDuplicates
1410
    
1411
    
1412
    /**
1413
     * This is always checked.   
1414
     * 
1415
     * For ADD: If there is not replacement file, then the check is considered a success
1416
     * For REPLACE: The checksum is examined against the "finalFileList" list
1417
     * 
1418
     * NOTE: this method was always called AFTER the main duplicate check; 
1419
     * So we would never detect this condition - of the file being replaced with 
1420
     * the same file... because it would always be caught as simply an attempt
1421
     * to replace a file with a file alraedy in the dataset! 
1422
     * So I commented it out, instead modifying the method above, step_040_auto_checkForDuplicates()
1423
     * to do both - check (first) if a file is being replaced with the exact same file;
1424
     * and check if a file, or files being uploaded are duplicates of files already 
1425
     * in the dataset. AND the replacement content type too. -- L.A. Jan 16 2017
1426
     * 
1427
     */
1428
    /*private boolean step_045_auto_checkForFileReplaceDuplicate(){
1429
        
1430
        if (this.hasError()){
1431
            return false;
1432
        }
1433

1434
        // Not a FILE REPLACE operation -- skip this step!!
1435
        //
1436
        if (!isFileReplaceOperation()){
1437
            return true;
1438
        }
1439

1440
        
1441
        if (finalFileList.isEmpty()){
1442
            // This error shouldn't happen if steps called in sequence....
1443
            this.addErrorSevere("There are no files to add.  (This error shouldn't happen if steps called in sequence....checkForFileReplaceDuplicate)");                
1444
            return false;
1445
        }
1446
        
1447
        
1448
        if (this.fileToReplace == null){
1449
            // This error shouldn't happen if steps called correctly
1450
            this.addErrorSevere(getBundleErr("existing_file_to_replace_is_null") + " (This error shouldn't happen if steps called in sequence....checkForFileReplaceDuplicate)");
1451
            return false;
1452
        }
1453
    
1454
        for (DataFile df : finalFileList){
1455
            
1456
            if (Objects.equals(df.getChecksumValue(), fileToReplace.getChecksumValue())){
1457
                this.addError(getBundleErr("replace.new_file_same_as_replacement"));                                
1458
                break;
1459
            }
1460

1461
            // Has the content type of the file changed?
1462
            //
1463
            if (!df.getContentType().equalsIgnoreCase(fileToReplace.getContentType())){
1464
            
1465
                List<String> errParams = Arrays.asList(fileToReplace.getFriendlyType(),
1466
                                                df.getFriendlyType());
1467
                
1468
                String contentTypeErr = BundleUtil.getStringFromBundle("file.addreplace.error.replace.new_file_has_different_content_type", 
1469
                                errParams);
1470
                                        
1471
                if (isForceFileOperation()){
1472
                    // for force replace, just give a warning
1473
                    this.setContentTypeWarning(contentTypeErr);
1474
                }else{
1475
                    // not a force replace? it's an error
1476
                    this.addError(contentTypeErr);
1477
                }
1478
            }
1479

1480
        }
1481
        
1482
        if (hasError()){
1483
            runMajorCleanup();
1484
            return false;
1485
        }
1486
        
1487
        return true;
1488
        
1489
    } // end step_045_auto_checkForFileReplaceDuplicate
1490
    */
1491
    
1492
    
1493
    
1494
    private boolean step_050_checkForConstraintViolations(){
1495
                
1496
        if (this.hasError()){
×
1497
            return false;
×
1498
        }
1499
        
1500
        if (finalFileList.isEmpty()){
×
1501
            // This error shouldn't happen if steps called in sequence....
1502
            this.addErrorSevere(getBundleErr("final_file_list_empty"));
×
1503
            return false;
×
1504
        }
1505

1506
        // -----------------------------------------------------------
1507
        // Iterate through checking for constraint violations
1508
        //  Gather all error messages
1509
        // -----------------------------------------------------------   
1510
        Set<ConstraintViolation> constraintViolations = workingVersion.validate();    
×
1511

1512
        // -----------------------------------------------------------   
1513
        // No violations found
1514
        // -----------------------------------------------------------   
1515
        if (constraintViolations.isEmpty()){
×
1516
            return true;
×
1517
        }
1518
        
1519
        new ArrayList<>();
×
1520
        for (ConstraintViolation violation : constraintViolations) {
×
1521
            /*
1522
            for 8859 return conflict response status if the validation fails
1523
            due to terms of use/access out of compliance
1524
            */
1525
            if (workingVersion.getTermsOfUseAndAccess().getValidationMessage() != null) {
×
1526
                addError(Response.Status.CONFLICT,workingVersion.getTermsOfUseAndAccess().getValidationMessage());
×
1527
            } else {
1528
                this.addError(violation.getMessage());
×
1529
            }
1530
        }
×
1531
        
1532
        return this.hasError();
×
1533
    }
1534
    
1535
    
1536
    /**
1537
     * Load optional file params such as description, tags, fileDataTags, etc..
1538
     * 
1539
     * @param optionalFileParams
1540
     * @return 
1541
     */
1542
    private boolean step_055_loadOptionalFileParams(OptionalFileParams optionalFileParams){
1543
        
1544
        if (hasError()){
×
1545
            return false;
×
1546
        }
1547

1548
        // --------------------------------------------
1549
        // OK, the object may be null
1550
        // --------------------------------------------
1551
        if (optionalFileParams == null){
×
1552
            return true;
×
1553
        }
1554
        
1555
            
1556
        // --------------------------------------------
1557
        // Iterate through files (should only be 1 for now)
1558
        // Add tags, description, etc
1559
        // --------------------------------------------
1560
        for (DataFile df : finalFileList){
×
1561
            try {
1562
                optionalFileParams.addOptionalParams(df);
×
1563
                
1564
                // call restriction command here
1565
                boolean restrict = optionalFileParams.getRestriction();
×
1566
                if (restrict != df.getFileMetadata().isRestricted()) {
×
1567
                    commandEngine.submit(new RestrictFileCommand(df, dvRequest, restrict));
×
1568
                }
1569
                
1570
            } catch (DataFileTagException ex) {
×
1571
                logger.log(Level.SEVERE, null, ex);
×
1572
                addError(ex.getMessage());
×
1573
                return false;
×
1574
            } catch (CommandException ex) {
×
1575
                addError(ex.getMessage());
×
1576
            }
×
1577
        }
×
1578
        
1579
        
1580
        return true;
×
1581
    }
1582
    
1583
    private boolean step_060_addFilesViaIngestService(boolean tabIngest){
1584
                       
1585
        if (this.hasError()){
×
1586
            return false;
×
1587
        }
1588
                
1589
        if (finalFileList.isEmpty()){
×
1590
            // This error shouldn't happen if steps called in sequence....
1591
            this.addErrorSevere(getBundleErr("final_file_list_empty"));                
×
1592
            return false;
×
1593
        }
1594
        
1595
        int nFiles = finalFileList.size();
×
1596
        finalFileList = ingestService.saveAndAddFilesToDataset(workingVersion, finalFileList, fileToReplace, tabIngest);
×
1597

1598
        if (nFiles != finalFileList.size()) {
×
1599
            if (nFiles == 1) {
×
1600
                addError("Failed to save the content of the uploaded file.");
×
1601
            } else {
1602
                addError("Failed to save the content of at least one of the uploaded files.");
×
1603
            }
1604
            return false;
×
1605
        }
1606
        
1607
        return true;
×
1608
    }
1609
    
1610
    List<FileMetadata> filesToDelete = new ArrayList<FileMetadata>();
×
1611
    Map<Long, String> deleteFileStorageLocations = new HashMap<>();
×
1612
    
1613
    /**
1614
     * Create and run the update dataset command
1615
     * 
1616
     * @return 
1617
     */
1618
    private boolean step_070_run_update_dataset_command() {
1619
        //Note -only single file operations and multifile replace call this, multifile add does not
1620
        if (this.hasError()) {
×
1621
            return false;
×
1622
        }
1623

1624
        Command<Dataset> update_cmd = null;
×
1625
        String deleteStorageLocation = null;
×
1626
        long deleteFileId = -1;
×
1627
        if (isFileReplaceOperation()) {
×
1628
            if (!multifile) {
×
1629
                filesToDelete.clear();
×
1630
                deleteFileStorageLocations.clear();
×
1631
            }
1632
            filesToDelete.add(fileToReplace.getFileMetadata());
×
1633

1634
            if (!fileToReplace.isReleased()) {
×
1635
                // If file is only in draft version, also need to delete the physical file
1636
                deleteStorageLocation = fileService.getPhysicalFileToDelete(fileToReplace);
×
1637
                deleteFileId = fileToReplace.getId();
×
1638
                deleteFileStorageLocations.put(deleteFileId, deleteStorageLocation);
×
1639
            }
1640
            if (!multifile) {
×
1641
                // Adding the file to the delete list for the command will delete this
1642
                // filemetadata and, if the file hasn't been released, the datafile itself.
1643
                update_cmd = new UpdateDatasetVersionCommand(dataset, dvRequest, filesToDelete, clone);
×
1644
            }
1645
        } else {
1646
            update_cmd = new UpdateDatasetVersionCommand(dataset, dvRequest, clone);
×
1647
        }
1648
        if (!multifile) {
×
1649
            //Avoid NPE in multifile replace case
1650
            ((UpdateDatasetVersionCommand) update_cmd).setValidateLenient(true);
×
1651
        }
1652
        if (!multifile) {
×
1653
            try {
1654
                // Submit the update dataset command
1655
                // and update the local dataset object
1656
                //
1657
                dataset = commandEngine.submit(update_cmd);
×
1658
            } catch (CommandException ex) {
×
1659
                /**
1660
                 * @todo Add a test to exercise this error.
1661
                 */
1662
                this.addErrorSevere(getBundleErr("add.add_file_error"));
×
1663
                logger.severe(ex.getMessage());
×
1664
                return false;
×
1665
            } catch (EJBException ex) {
×
1666
                /**
1667
                 * @todo Add a test to exercise this error.
1668
                 */
1669
                this.addErrorSevere("add.add_file_error (see logs)");
×
1670
                logger.severe(ex.getMessage());
×
1671
                return false;
×
1672
            }
×
1673
        }
1674

1675
        if (isFileReplaceOperation() && deleteFileId!=-1 && !multifile) {
×
1676
            // Finalize the delete of the physical file
1677
            // (File service will double-check that the datafile no
1678
            // longer exists in the database, before proceeding to
1679
            // delete the physical file)
1680
            try {
1681
                fileService.finalizeFileDelete(deleteFileId, deleteStorageLocation);
×
1682
            } catch (IOException ioex) {
×
1683
                logger.warning("Failed to delete the physical file associated with the deleted datafile id="
×
1684
                        + deleteFileId + ", storage location: " + deleteStorageLocation);
1685
            }
×
1686
        }
1687
        return true;
×
1688
    }
1689
    
1690

1691
    private boolean runMajorCleanup(){
1692
        
1693
        // (1) remove unsaved files from the working version
1694
        removeUnSavedFilesFromWorkingVersion();
×
1695
        
1696
        // ----------------------------------------------------
1697
        // (2) if the working version is brand new, delete it
1698
        //      It doesn't have an "id" so you can't use the DeleteDatasetVersionCommand
1699
        // ----------------------------------------------------
1700
        // Remove this working version from the dataset
1701
        Iterator<DatasetVersion> versionIterator = dataset.getVersions().iterator();
×
1702
        msgt("Clear Files");
×
1703
        while (versionIterator.hasNext()) {
×
1704
            DatasetVersion dsv = versionIterator.next();
×
1705
            if (dsv.getId() == null){
×
1706
                versionIterator.remove();
×
1707
            }
1708
        }
×
1709
        
1710
        return true;
×
1711
        
1712
    }
1713
    
1714
    /**
1715
     * We are outta here!  Remove everything unsaved from the edit version!
1716
     * 
1717
     * @return 
1718
     */
1719
    private boolean removeUnSavedFilesFromWorkingVersion(){
1720
        msgt("Clean up: removeUnSavedFilesFromWorkingVersion");
×
1721
        
1722
        // -----------------------------------------------------------
1723
        // (1) Remove all new FileMetadata objects
1724
        // -----------------------------------------------------------                        
1725
        //Iterator<FileMetadata> fmIt = dataset.getEditVersion().getFileMetadatas().iterator();//  
1726
        Iterator<FileMetadata> fmIt = workingVersion.getFileMetadatas().iterator(); //dataset.getEditVersion().getFileMetadatas().iterator();//  
×
1727
        while (fmIt.hasNext()) {
×
1728
            FileMetadata fm = fmIt.next();
×
1729
            if (fm.getDataFile().getId() == null){
×
1730
                fmIt.remove();
×
1731
            }
1732
        }
×
1733
        
1734
        // -----------------------------------------------------------
1735
        // (2) Remove all new DataFile objects
1736
        // -----------------------------------------------------------                        
1737
        Iterator<DataFile> dfIt = dataset.getFiles().iterator();
×
1738
        msgt("Clear Files");
×
1739
        while (dfIt.hasNext()) {
×
1740
            DataFile df = dfIt.next();
×
1741
            if (df.getId() == null){
×
1742
                dfIt.remove();
×
1743
            }
1744
        }
×
1745
        return true;
×
1746
        
1747
    }
1748
    
1749
    
1750
    private boolean step_080_run_update_dataset_command_for_replace(){
1751

1752
        if (!isFileReplaceOperation()){
×
1753
            // Shouldn't happen!
1754
            this.addErrorSevere(getBundleErr("only_replace_operation") + " (step_080_run_update_dataset_command_for_replace)");
×
1755
            return false;
×
1756
        }
1757

1758
        if (this.hasError()){
×
1759
            return false;
×
1760
        }
1761

1762
        // -----------------------------------------------------------
1763
        // Set the "root file ids" and "previous file ids"
1764
        // THIS IS A KEY STEP - SPLIT IT OUT
1765
        //  (1) Old file: Set the Root File Id on the original file  
1766
        //  (2) New file: Set the previousFileId to the id of the original file
1767
        //  (3) New file: Set the rootFileId to the rootFileId of the original file
1768
        // -----------------------------------------------------------
1769
 
1770
        
1771
        if (fileToReplace.isReleased()) {
×
1772
            /*
1773
             * Check the root file id on fileToReplace, updating it if necessary
1774
             */
1775
            if (fileToReplace.getRootDataFileId().equals(DataFile.ROOT_DATAFILE_ID_DEFAULT)) {
×
1776

1777
                fileToReplace.setRootDataFileId(fileToReplace.getId());
×
1778
                fileToReplace = fileService.save(fileToReplace);
×
1779
            }
1780

1781
            /*
1782
             * Go through the final file list, setting the rootFileId and previousFileId
1783
             */
1784
            for (DataFile df : finalFileList) {
×
1785
                df.setPreviousDataFileId(fileToReplace.getId());
×
1786

1787
                df.setRootDataFileId(fileToReplace.getRootDataFileId());
×
1788

1789
            }
×
1790
        }
1791
        // Call the update dataset command which will delete the replaced filemetadata and file if needed (if file is not released)
1792
        //
1793
        return step_070_run_update_dataset_command();
×
1794
        
1795
       
1796
    }
1797
            
1798
    /**
1799
     * We want the version of the newly added file that has an id set
1800
     * 
1801
     * TODO: This is inefficient/expensive.  Need to redo it in a sane way
1802
     *      - e.g. Query to find 
1803
     *          (1) latest dataset version in draft
1804
     *          (2) pick off files that are NOT released
1805
     *          (3) iterate through only those files
1806
     *      - or an alternate/better version
1807
     * 
1808
     * @param df 
1809
     */
1810
    private void setNewlyAddedFiles(List<DataFile> datafiles){
1811
        
1812
        if (hasError()){
×
1813
            return;
×
1814
        }
1815
            
1816
        // Init. newly added file list
1817
        newlyAddedFiles = new ArrayList<>();
×
1818
        newlyAddedFileMetadatas = new ArrayList<>();
×
1819
        
1820
        // Loop of uglinesss...but expect 1 to 4 files in final file list
1821
        List<FileMetadata> latestFileMetadatas = dataset.getOrCreateEditVersion().getFileMetadatas();
×
1822
        
1823
        
1824
        for (DataFile newlyAddedFile : finalFileList){
×
1825
            
1826
             for (FileMetadata fm : latestFileMetadatas){
×
1827
                 if (newlyAddedFile.getChecksumValue().equals(fm.getDataFile().getChecksumValue())){
×
1828
                    if (newlyAddedFile.getStorageIdentifier().equals(fm.getDataFile().getStorageIdentifier())){
×
1829
                        newlyAddedFiles.add(fm.getDataFile());
×
1830
                        newlyAddedFileMetadatas.add(fm);
×
1831
                    }
1832
                }
1833
             }
×
1834
        }
×
1835
        /*
1836
       
1837
        newlyAddedFile = df;
1838
        
1839
        for (FileMetadata fm : dataset.getEditVersion().getFileMetadatas()){
1840
            
1841
            // Find a file where the checksum value and identifiers are the same..
1842
            //
1843
            if (newlyAddedFile.getChecksumValue().equals(fm.getDataFile().getChecksumValue())){
1844
                if (newlyAddedFile.getStorageIdentifier().equals(fm.getDataFile().getStorageIdentifier())){
1845
                    newlyAddedFile = fm.getDataFile();
1846
                    break;
1847
                }
1848
            }
1849
        }
1850
        */
1851
        
1852
    }
×
1853

1854
    /**
1855
     * For a successful replace operation, return a the first newly added file
1856
     * @return 
1857
     */
1858
    public DataFile getFirstNewlyAddedFile(){
1859
        
1860
        if ((newlyAddedFiles == null)||(newlyAddedFiles.size() == 0)){
×
1861
            return null;
×
1862
        }
1863
        return newlyAddedFiles.get(0);
×
1864
    }
1865
        
1866
    public List<DataFile> getNewlyAddedFiles(){
1867
        
1868
        return newlyAddedFiles;
×
1869
    }
1870
    
1871
    public List<FileMetadata> getNewlyAddedFileMetadatas(){
1872
        
1873
        return newlyAddedFileMetadatas;
×
1874
    }
1875
    
1876
    
1877
    public String getSuccessResult() throws NoFilesException{
1878
        if (hasError()){
×
1879
            throw new NoFilesException("Don't call this method if an error exists!! First check 'hasError()'");
×
1880
        }
1881

1882
        if (newlyAddedFiles == null){
×
1883
            throw new NullPointerException("newlyAddedFiles is null!");
×
1884
        }
1885
        
1886
        return getSuccessResultAsJsonObjectBuilder().toString();
×
1887
        
1888
    }
1889
    
1890
    public JsonObjectBuilder getSuccessResultAsJsonObjectBuilder() throws NoFilesException{
1891
        
1892
        if (hasError()){
×
1893
            throw new NoFilesException("Don't call this method if an error exists!! First check 'hasError()'");
×
1894
        }
1895
        
1896
        if (newlyAddedFiles == null){
×
1897
            throw new NullPointerException("newlyAddedFiles is null!");
×
1898
        }
1899
        
1900
        if (newlyAddedFiles.isEmpty()){
×
1901
            throw new NoFilesException("newlyAddedFiles is empty!");
×
1902
        }
1903
        
1904
        return JsonPrinter.jsonDataFileList(newlyAddedFiles);
×
1905
    }
1906
    
1907
    
1908
    /**
1909
     * Currently this is a placeholder if we decide to send
1910
     * user notifications.
1911
     * 
1912
     */
1913
    private boolean step_090_notifyUser(){
1914
        if (this.hasError()){
×
1915
            return false;
×
1916
        }
1917
       
1918
        // Create a notification!
1919
       
1920
        // skip for now, may be part of dataset update listening
1921
        //
1922
        return true;
×
1923
    }
1924
    
1925

1926
    private boolean step_100_startIngestJobs(){
1927
        if (this.hasError()){
×
1928
            return false;
×
1929
        }
1930
                
1931
        // Should only be one file in the list
1932
        setNewlyAddedFiles(finalFileList);
×
1933
        
1934
        // clear old file list
1935
        //
1936
        finalFileList.clear();
×
1937

1938
        if (!multifile) {
×
1939
            msg("pre ingest start");
×
1940
            // start the ingest!
1941
            ingestService.startIngestJobsForDataset(dataset, dvRequest.getAuthenticatedUser());
×
1942
            msg("post ingest start");
×
1943
        }
1944
        return true;
×
1945
    }
1946
    
1947
    private void msg(String m){
1948
        logger.fine(m);
×
1949
        //System.out.println(m);
1950
    }
×
1951
    private void dashes(){
1952
        msg("----------------");
×
1953
    }
×
1954
    private void msgt(String m){
1955
        dashes(); msg(m); dashes();
×
1956
    }
×
1957
    
1958
    
1959
    /**
1960
     * Return file list before saving
1961
     * 
1962
     * Used for UI display
1963
     * 
1964
     * @return 
1965
     */
1966
    public List<DataFile> getFileListBeforeSave(){
1967
        
1968
        return this.finalFileList;
×
1969
    }
1970
    
1971
    public Boolean isFinalFileListEmpty (){
1972
        return this.finalFileList.isEmpty();
×
1973
    }
1974
    
1975
    
1976
    /**
1977
     * Return file list before saving
1978
     * 
1979
     * Used for UI display
1980
     * 
1981
     * @return 
1982
     */
1983
    public List<FileMetadata> getNewFileMetadatasBeforeSave(){
1984
        
1985
        if (this.finalFileList.size() == 0){
×
1986
            return null;
×
1987
        }
1988
        
1989
        List<FileMetadata> fileMetadatas = new ArrayList<>();
×
1990
        for (DataFile df : finalFileList){
×
1991
            fileMetadatas.add(df.getFileMetadata());
×
1992
        }
×
1993
        
1994
        return fileMetadatas;
×
1995
        
1996
    }
1997
    
1998
    public void setContentTypeWarning(String warningString){
1999
        
2000
        if ((warningString == null)||(warningString.isEmpty())){
×
2001
            throw new NullPointerException("warningString cannot be null");
×
2002
        }
2003
        
2004
        contentTypeWarningFound = true;
×
2005
        contentTypeWarningString = warningString;
×
2006
    }
×
2007
    
2008
    public boolean hasContentTypeWarning(){
2009
        return this.contentTypeWarningFound;
×
2010
    }
2011
    
2012
    public String getContentTypeWarningString(){
2013
        if (!hasContentTypeWarning()){
×
2014
            // not really a NullPointerException but want to blow up here without adding try/catch everywhere
2015
            //
2016
            throw new NullPointerException("Don't call this method without checking 'hasContentTypeWarning()'");
×
2017
        }
2018
        return contentTypeWarningString;
×
2019
    }
2020
    
2021
    private String duplicateFileWarning;
2022

2023
    public String getDuplicateFileWarning() {
2024
        return duplicateFileWarning;
×
2025
    }
2026

2027
    public void setDuplicateFileWarning(String duplicateFileWarning) {
2028
        this.duplicateFileWarning = duplicateFileWarning;
×
2029
    }
×
2030

2031
    /** Add multiple pre-positioned files listed in the jsonData. Works with direct upload, Globus, and other out-of-band methods.
2032
     * 
2033
     * @param jsonData - an array of jsonData entries (one per file) using the single add file jsonData format
2034
     * @param dataset
2035
     * @param authUser
2036
     * @return
2037
     */
2038
    public Response addFiles(String jsonData, Dataset dataset, User authUser) {
2039
        msgt("(addFilesToDataset) jsonData: " + jsonData.toString());
×
2040

2041
        JsonArrayBuilder jarr = Json.createArrayBuilder();
×
2042

2043
        JsonArray filesJson = null;
×
2044

2045
        int totalNumberofFiles = 0;
×
2046
        int successNumberofFiles = 0;
×
2047
        // -----------------------------------------------------------
2048
        // Read jsonData and Parse files information from jsondata  :
2049
        // -----------------------------------------------------------
2050
        try {
2051
            filesJson = JsonUtil.getJsonArray(jsonData);
×
2052

2053

2054
            if (filesJson != null) {
×
2055
                totalNumberofFiles = filesJson.getValuesAs(JsonObject.class).size();
×
2056
                workingVersion = dataset.getOrCreateEditVersion();
×
2057
                clone = workingVersion.cloneDatasetVersion();
×
2058
                for (JsonObject fileJson : filesJson.getValuesAs(JsonObject.class)) {
×
2059

2060
                    OptionalFileParams optionalFileParams = null;
×
2061
                    try {
2062
                        optionalFileParams = new OptionalFileParams(fileJson.toString());
×
2063

2064
                        String newFilename = null;
×
2065
                        String newFileContentType = null;
×
2066
                        String newStorageIdentifier = null;
×
2067
                        if (optionalFileParams.hasStorageIdentifier()) {
×
2068
                            newStorageIdentifier = optionalFileParams.getStorageIdentifier();
×
2069
                            newStorageIdentifier = DataAccess.expandStorageIdentifierIfNeeded(newStorageIdentifier);
×
2070
                            if(!DataAccess.uploadToDatasetAllowed(dataset,  newStorageIdentifier)) {
×
2071
                                addErrorSevere("Dataset store configuration does not allow provided storageIdentifier.");
×
2072
                            }
2073
                            if (optionalFileParams.hasFileName()) {
×
2074
                                newFilename = optionalFileParams.getFileName();
×
2075
                                if (optionalFileParams.hasMimetype()) {
×
2076
                                    newFileContentType = optionalFileParams.getMimeType();
×
2077
                                }
2078
                            }
2079

2080
                            msgt("ADD!  = " + newFilename);
×
2081

2082
                            runAddFileByDataset(dataset, newFilename, newFileContentType, newStorageIdentifier, null,
×
2083
                                    optionalFileParams, true);
2084
                            if (hasError()) {
×
2085
                                JsonObjectBuilder fileoutput = Json.createObjectBuilder()
×
2086
                                        .add("storageIdentifier", newStorageIdentifier)
×
2087
                                        .add("errorMessage", getHttpErrorCode().toString() +":"+ getErrorMessagesAsString("\n"))
×
2088
                                        .add("fileDetails", fileJson);
×
2089
                                jarr.add(fileoutput);
×
2090
                            } else {
×
2091
                                JsonObject successresult = getSuccessResultAsJsonObjectBuilder().build();
×
2092
                                String duplicateWarning = getDuplicateFileWarning();
×
2093

2094
                                if (duplicateWarning != null && !duplicateWarning.isEmpty()) {
×
2095
                                    JsonObjectBuilder fileoutput = Json.createObjectBuilder()
×
2096
                                            .add("storageIdentifier", newStorageIdentifier)
×
2097
                                            .add("warningMessage", getDuplicateFileWarning())
×
2098
                                            .add("fileDetails", successresult.getJsonArray("files").getJsonObject(0));
×
2099
                                    jarr.add(fileoutput);
×
2100
                                } else {
×
2101
                                    JsonObjectBuilder fileoutput = Json.createObjectBuilder()
×
2102
                                            .add("storageIdentifier", newStorageIdentifier)
×
2103
                                            .add("successMessage", "Added successfully to the dataset")
×
2104
                                            .add("fileDetails", successresult.getJsonArray("files").getJsonObject(0));
×
2105
                                    jarr.add(fileoutput);
×
2106
                                }
2107
                            successNumberofFiles = successNumberofFiles + 1;
×
2108
                            }
×
2109
                        } else {
2110
                            JsonObjectBuilder fileoutput = Json.createObjectBuilder()
×
2111
                                    .add("errorMessage", "You must provide a storageidentifier, filename, and mimetype.")
×
2112
                                    .add("fileDetails", fileJson);
×
2113

2114
                            jarr.add(fileoutput);
×
2115
                        }
2116

2117
                    } catch (DataFileTagException ex) {
×
2118
                        logger.log(Level.SEVERE, null, ex);
×
2119
                        JsonObjectBuilder fileoutput = Json.createObjectBuilder()
×
2120
                                .add("errorCode", Response.Status.BAD_REQUEST.getStatusCode())
×
2121
                                .add("message", ex.getMessage())
×
2122
                                .add("fileDetails", fileJson);
×
2123
                        jarr.add(fileoutput);
×
2124

2125
                    }
2126
                    catch (NoFilesException ex) {
×
2127
                        logger.log(Level.SEVERE, null, ex);
×
2128
                        JsonObjectBuilder fileoutput = Json.createObjectBuilder()
×
2129
                                .add("errorCode", Response.Status.BAD_REQUEST.getStatusCode())
×
2130
                                .add("message", BundleUtil.getStringFromBundle("NoFileException!  Serious Error! See administrator!"))
×
2131
                                .add("fileDetails", fileJson);
×
2132
                        jarr.add(fileoutput);
×
2133
                    }
×
2134

2135
                }// End of adding files
×
2136

2137
                DatasetLock eipLock = dataset.getLockFor(DatasetLock.Reason.EditInProgress);
×
2138
                if (eipLock == null) {
×
2139
                    logger.log(Level.WARNING, "Dataset not locked for EditInProgress ");
×
2140
                } else {
2141
                    datasetService.removeDatasetLocks(dataset, DatasetLock.Reason.EditInProgress);
×
NEW
2142
                    logger.log(Level.FINE, "Removed EditInProgress lock");
×
2143
                }
2144
                
2145
                try {
2146
                    Command<Dataset> cmd = new UpdateDatasetVersionCommand(dataset, dvRequest, clone);
×
2147
                    ((UpdateDatasetVersionCommand) cmd).setValidateLenient(true);
×
2148
                    commandEngine.submit(cmd);
×
2149
                } catch (CommandException ex) {
×
2150
                    return error(Response.Status.INTERNAL_SERVER_ERROR, "CommandException updating DatasetVersion from addFiles job: " + ex.getMessage());
×
2151
                }
×
2152

2153
                dataset = datasetService.find(dataset.getId());
×
2154

2155
                //ingest job
2156
                ingestService.startIngestJobsForDataset(dataset, (AuthenticatedUser) authUser);
×
2157

2158
            }
2159
        }
2160
        catch ( jakarta.json.stream.JsonParsingException ex) {
×
2161
            ex.printStackTrace();
×
2162
            return error(BAD_REQUEST, "Json Parsing Exception :" + ex.getMessage());
×
2163
        }
2164
        catch (Exception e) {
×
2165
            e.printStackTrace();
×
2166
            return error(BAD_REQUEST, e.getMessage());
×
2167
        }
×
2168

2169
        JsonObjectBuilder result = Json.createObjectBuilder()
×
NEW
2170
                .add(ApiConstants.API_ADD_FILES_COUNT_PROCESSED, totalNumberofFiles)
×
NEW
2171
                .add(ApiConstants.API_ADD_FILES_COUNT_SUCCESSFUL, successNumberofFiles);
×
2172

2173

2174
        return Response.ok().entity(Json.createObjectBuilder()
×
2175
                .add("status", ApiConstants.STATUS_OK)
×
2176
                .add("data", Json.createObjectBuilder().add("Files", jarr).add("Result", result)).build() ).build();
×
2177
    }
2178
    
2179
    /**
2180
     * Replace multiple files with prepositioned replacements as listed in the
2181
     * jsonData. Works with direct upload, Globus, and other out-of-band methods.
2182
     * 
2183
     * @param jsonData - must include fileToReplaceId key with file ID and may include forceReplace key with true/false(default) 
2184
     * @param dataset
2185
     * @param authUser
2186
     * @return
2187
     */
2188
    
2189
    public Response replaceFiles(String jsonData, Dataset ds, User authUser) {
2190
        msgt("(replaceFilesInDataset) jsonData: " + jsonData.toString());
×
2191

2192
        this.dataset = ds;
×
2193
        JsonArrayBuilder jarr = Json.createArrayBuilder();
×
2194

2195
        JsonArray filesJson = null;
×
2196

2197
        int totalNumberofFiles = 0;
×
2198
        int successNumberofFiles = 0;
×
2199
        // -----------------------------------------------------------
2200
        // Read jsonData and Parse files information from jsondata  :
2201
        // -----------------------------------------------------------
2202
        try {
2203
            filesJson = JsonUtil.getJsonArray(jsonData);
×
2204

2205

2206
            if (filesJson != null) {
×
2207
                totalNumberofFiles = filesJson.getValuesAs(JsonObject.class).size();
×
2208
                workingVersion = dataset.getOrCreateEditVersion();
×
2209
                clone = workingVersion.cloneDatasetVersion();
×
2210
                for (JsonObject fileJson : filesJson.getValuesAs(JsonObject.class)) {
×
2211
                    boolean forceReplace = false;
×
2212
                    // (2a) Check for optional "forceReplace"
2213
                    if ((fileJson.containsKey("forceReplace"))) {
×
2214
                        forceReplace = fileJson.getBoolean("forceReplace", false);
×
2215
                    }
2216
                    long fileToReplaceId = -1;
×
2217
                    JsonNumber ftri = fileJson.getJsonNumber("fileToReplaceId");
×
2218
                    if(ftri !=null) {
×
2219
                        fileToReplaceId = ftri.longValueExact();
×
2220
                    }
2221
                    
2222
                    OptionalFileParams optionalFileParams = null;
×
2223
                    try {
2224
                        // (2b) Load up optional params via JSON
2225
                        //  - Will skip extra attributes which includes fileToReplaceId and forceReplace
2226
                        optionalFileParams = new OptionalFileParams(fileJson.toString());
×
2227

2228
                        String newFilename = null;
×
2229
                        String newFileContentType = null;
×
2230
                        String newStorageIdentifier = null;
×
2231
                        if ((fileToReplaceId !=-1) && optionalFileParams.hasStorageIdentifier()) {
×
2232
                            newStorageIdentifier = optionalFileParams.getStorageIdentifier();
×
2233
                            newStorageIdentifier = DataAccess.expandStorageIdentifierIfNeeded(newStorageIdentifier);
×
2234
                            if(!DataAccess.uploadToDatasetAllowed(dataset,  newStorageIdentifier)) {
×
2235
                                addErrorSevere("Dataset store configuration does not allow provided storageIdentifier.");
×
2236
                            }
2237
                            if (optionalFileParams.hasFileName()) {
×
2238
                                newFilename = optionalFileParams.getFileName();
×
2239
                                if (optionalFileParams.hasMimetype()) {
×
2240
                                    newFileContentType = optionalFileParams.getMimeType();
×
2241
                                }
2242
                            }
2243

2244
                            msgt("REPLACE!  = " + newFilename);
×
2245
                            if (forceReplace) {
×
2246
                                runForceReplaceFile(fileToReplaceId, newFilename, newFileContentType,
×
2247
                                        newStorageIdentifier, null, dataset, optionalFileParams, true);
2248
                            } else {
2249
                                runReplaceFile(fileToReplaceId, newFilename, newFileContentType, newStorageIdentifier,
×
2250
                                        null, dataset, optionalFileParams, true);
2251
                            }
2252
                            if (hasError()) {
×
2253
                                JsonObjectBuilder fileoutput = Json.createObjectBuilder()
×
2254
                                        .add("storageIdentifier", newStorageIdentifier)
×
2255
                                        .add("errorMessage", getHttpErrorCode().toString() +":"+ getErrorMessagesAsString("\n"))
×
2256
                                        .add("fileDetails", fileJson);
×
2257
                                jarr.add(fileoutput);
×
2258
                            } else {
×
2259
                                JsonObject successresult = getSuccessResultAsJsonObjectBuilder().build();
×
2260
                                String duplicateWarning = getDuplicateFileWarning();
×
2261

2262
                                if (duplicateWarning != null && !duplicateWarning.isEmpty()) {
×
2263
                                    JsonObjectBuilder fileoutput = Json.createObjectBuilder()
×
2264
                                            .add("storageIdentifier", newStorageIdentifier)
×
2265
                                            .add("warningMessage", getDuplicateFileWarning())
×
2266
                                            .add("fileDetails", successresult.getJsonArray("files").getJsonObject(0));
×
2267
                                    jarr.add(fileoutput);
×
2268
                                } else {
×
2269
                                    JsonObjectBuilder fileoutput = Json.createObjectBuilder()
×
2270
                                            .add("storageIdentifier", newStorageIdentifier)
×
2271
                                            .add("successMessage", "Replaced successfully in the dataset")
×
2272
                                            .add("fileDetails", successresult.getJsonArray("files").getJsonObject(0));
×
2273
                                    jarr.add(fileoutput);
×
2274
                                }
2275
                            successNumberofFiles = successNumberofFiles + 1;
×
2276
                            }
×
2277
                        } else {
2278
                            JsonObjectBuilder fileoutput = Json.createObjectBuilder()
×
2279
                                    .add("errorMessage", "You must provide a fileToReplaceId, storageidentifier, filename, and mimetype.")
×
2280
                                    .add("fileDetails", fileJson);
×
2281

2282
                            jarr.add(fileoutput);
×
2283
                        }
2284

2285
                    } catch (DataFileTagException ex) {
×
2286
                        logger.log(Level.SEVERE, null, ex);
×
2287
                        JsonObjectBuilder fileoutput = Json.createObjectBuilder()
×
2288
                                .add("errorCode", Response.Status.BAD_REQUEST.getStatusCode())
×
2289
                                .add("message", ex.getMessage())
×
2290
                                .add("fileDetails", fileJson);
×
2291
                        jarr.add(fileoutput);
×
2292

2293
                    }
2294
                    catch (NoFilesException ex) {
×
2295
                        logger.log(Level.SEVERE, null, ex);
×
2296
                        JsonObjectBuilder fileoutput = Json.createObjectBuilder()
×
2297
                                .add("errorCode", Response.Status.BAD_REQUEST.getStatusCode())
×
2298
                                .add("message", BundleUtil.getStringFromBundle("NoFileException!  Serious Error! See administrator!"))
×
2299
                                .add("fileDetails", fileJson);
×
2300
                        jarr.add(fileoutput);
×
2301
                    }
×
2302
                }// End of adding files
×
2303

2304
                DatasetLock eipLock = dataset.getLockFor(DatasetLock.Reason.EditInProgress);
×
2305
                if (eipLock == null) {
×
2306
                    logger.warning("Dataset not locked for EditInProgress ");
×
2307
                } else {
2308
                    datasetService.removeDatasetLocks(dataset, DatasetLock.Reason.EditInProgress);
×
NEW
2309
                    logger.fine("Removed EditInProgress lock ");
×
2310
                }
2311

2312
                try {
2313
                    Command<Dataset> cmd = new UpdateDatasetVersionCommand(dataset, dvRequest, filesToDelete, clone);
×
2314
                    ((UpdateDatasetVersionCommand) cmd).setValidateLenient(true);
×
2315
                    commandEngine.submit(cmd);
×
2316
                } catch (CommandException ex) {
×
2317
                    return error(Response.Status.INTERNAL_SERVER_ERROR, "CommandException updating DatasetVersion from addFiles job: " + ex.getMessage());
×
2318
                }
×
2319

2320
                fileService.finalizeFileDeletes(deleteFileStorageLocations);
×
2321
                
2322
                dataset = datasetService.find(dataset.getId());
×
2323

2324
                //ingest job
2325
                ingestService.startIngestJobsForDataset(dataset, (AuthenticatedUser) authUser);
×
2326

2327
            }
2328
        }
2329
        catch ( jakarta.json.stream.JsonParsingException ex) {
×
2330
            ex.printStackTrace();
×
2331
            return error(BAD_REQUEST, "Json Parsing Exception :" + ex.getMessage());
×
2332
        }
2333
        catch (Exception e) {
×
2334
            e.printStackTrace();
×
2335
            return error(BAD_REQUEST, e.getMessage());
×
2336
        }
×
2337

2338
        JsonObjectBuilder result = Json.createObjectBuilder()
×
2339
                .add("Total number of files", totalNumberofFiles)
×
2340
                .add("Number of files successfully replaced", successNumberofFiles);
×
2341

2342
        return Response.ok().entity(Json.createObjectBuilder()
×
2343
                .add("status", ApiConstants.STATUS_OK)
×
2344
                .add("data", Json.createObjectBuilder().add("Files", jarr).add("Result", result)).build() ).build();
×
2345
    }
2346

2347
    protected static Response error(Response.Status sts, String msg ) {
2348
        return Response.status(sts)
×
2349
                .entity( NullSafeJsonBuilder.jsonObjectBuilder()
×
2350
                        .add("status", ApiConstants.STATUS_ERROR)
×
2351
                        .add( "message", msg ).build()
×
2352
                ).type(MediaType.APPLICATION_JSON_TYPE).build();
×
2353
    }
2354

2355
} // end class
2356
  /*
2357
    DatasetPage sequence:
2358
    
2359
    (A) editFilesFragment.xhtml -> EditDataFilesPage.handleFileUpload
2360
    (B) EditDataFilesPage.java -> handleFileUpload
2361
        (1) UploadedFile uf  event.getFile() // UploadedFile
2362
            --------
2363
                UploadedFile interface:
2364
                    public String getFileName()
2365
                    public InputStream getInputstream() throws IOException;
2366
                    public long getSize();
2367
                    public byte[] getContents();
2368
                    public String getContentType();
2369
                    public void write(String string) throws Exception;
2370
            --------
2371
        (2) List<DataFile> dFileList = null;     
2372
        try {
2373
            // Note: A single file may be unzipped into multiple files
2374
            dFileList = ingestService.createDataFiles(workingVersion, uFile.getInputstream(), uFile.getFileName(), uFile.getContentType());
2375
        }
2376
    
2377
        (3) processUploadedFileList(dFileList);
2378

2379
    (C) EditDataFilesPage.java -> processUploadedFileList
2380
        - iterate through list of DataFile objects -- which COULD happen with a single .zip
2381
            - isDuplicate check
2382
            - if good:
2383
                - newFiles.add(dataFile);        // looks good
2384
                - fileMetadatas.add(dataFile.getFileMetadata());
2385
            - return null;    // looks good, return null
2386
    (D) save()  // in the UI, user clicks the button.  API is automatic if no errors
2387
        
2388
        (1) Look for constraintViolations:
2389
            // DatasetVersion workingVersion;
2390
            Set<ConstraintViolation> constraintViolations = workingVersion.validate();
2391
                if (!constraintViolations.isEmpty()) {
2392
                 //JsfHelper.addFlashMessage(JH.localize("dataset.message.validationError"));
2393
                 JH.addMessage(FacesMessage.SEVERITY_ERROR, JH.localize("dataset.message.validationError"));
2394
                //FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Validation Error", "See below for details."));
2395
                return "";
2396
            }
2397
    
2398
         (2) Use the ingestService for a final check
2399
            // ask Leonid if this is needed for API
2400
            // One last check before we save the files - go through the newly-uploaded 
2401
            // ones and modify their names so that there are no duplicates. 
2402
            // (but should we really be doing it here? - maybe a better approach to do it
2403
            // in the ingest service bean, when the files get uploaded.)
2404
            // Finally, save the files permanently: 
2405
            ingestService.saveAndAddFilesToDataset(workingVersion, newFiles);
2406

2407
         (3) Use the API to save the dataset
2408
            - make new CreateDatasetCommand
2409
                - check if dataset has a template
2410
            - creates UserNotification message
2411
    
2412
    */  
2413
    // Checks:
2414
    //   - Does the md5 already exist in the dataset?
2415
    //   - If it's a replace, has the name and/or extension changed?
2416
    //   On failure, send back warning
2417
    //
2418
    // - All looks good
2419
    // - Create a DataFile
2420
    // - Create a FileMetadata
2421
    // - Copy the Dataset version, making a new DRAFT
2422
    //      - If it's replace, don't copy the file being replaced
2423
    // - Add this new file.
2424
    // ....
2425
    
2426
    
2427

2428
/*
2429
    1) Recovery from adding same file and duplicate being found
2430
        - draft ok
2431
        - published verion - nope
2432
*/
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