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

IQSS / dataverse / #23077

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

Pull #10781

github

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

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

417 existing lines in 9 files now uncovered.

17530 of 84679 relevant lines covered (20.7%)

0.21 hits per line

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

0.0
/src/main/java/edu/harvard/iq/dataverse/globus/GlobusServiceBean.java
1
package edu.harvard.iq.dataverse.globus;
2

3
import com.github.benmanes.caffeine.cache.Cache;
4
import com.github.benmanes.caffeine.cache.Caffeine;
5
import com.github.benmanes.caffeine.cache.Scheduler;
6
import com.google.gson.FieldNamingPolicy;
7
import com.google.gson.GsonBuilder;
8
import edu.harvard.iq.dataverse.*;
9
import jakarta.ejb.Asynchronous;
10
import jakarta.ejb.EJB;
11
import jakarta.ejb.Stateless;
12
import jakarta.ejb.TransactionAttribute;
13
import jakarta.ejb.TransactionAttributeType;
14
import jakarta.inject.Inject;
15
import jakarta.inject.Named;
16
import jakarta.json.Json;
17
import jakarta.json.JsonArray;
18
import jakarta.json.JsonArrayBuilder;
19
import jakarta.json.JsonObject;
20
import jakarta.json.JsonObjectBuilder;
21
import jakarta.json.JsonPatch;
22
import jakarta.json.JsonString;
23
import jakarta.json.JsonValue.ValueType;
24
import jakarta.json.stream.JsonParsingException;
25
import jakarta.ws.rs.HttpMethod;
26

27
import static edu.harvard.iq.dataverse.util.json.JsonPrinter.json;
28
import static edu.harvard.iq.dataverse.util.json.JsonPrinter.toJsonArray;
29

30
import java.io.*;
31

32
import java.net.HttpURLConnection;
33
import java.net.MalformedURLException;
34
import java.net.URL;
35
import java.sql.Timestamp;
36
import java.text.SimpleDateFormat;
37
import java.time.Duration;
38
import java.time.temporal.ChronoUnit;
39
import java.util.*;
40
import java.util.concurrent.CompletableFuture;
41
import java.util.concurrent.ExecutionException;
42
import java.util.concurrent.Executor;
43
import java.util.concurrent.Executors;
44
import java.util.logging.FileHandler;
45
import java.util.logging.Level;
46
import java.util.logging.Logger;
47
import java.util.stream.Collectors;
48
import java.util.stream.IntStream;
49

50
import org.apache.commons.codec.binary.StringUtils;
51
import org.primefaces.PrimeFaces;
52

53
import com.google.gson.Gson;
54
import edu.harvard.iq.dataverse.api.ApiConstants;
55
import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean;
56
import edu.harvard.iq.dataverse.authorization.users.ApiToken;
57
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
58
import edu.harvard.iq.dataverse.authorization.users.PrivateUrlUser;
59
import edu.harvard.iq.dataverse.authorization.users.User;
60
import edu.harvard.iq.dataverse.dataaccess.DataAccess;
61
import edu.harvard.iq.dataverse.dataaccess.GlobusAccessibleStore;
62
import edu.harvard.iq.dataverse.dataaccess.StorageIO;
63
import edu.harvard.iq.dataverse.datasetutility.AddReplaceFileHelper;
64
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
65
import edu.harvard.iq.dataverse.ingest.IngestServiceBean;
66
import edu.harvard.iq.dataverse.privateurl.PrivateUrl;
67
import edu.harvard.iq.dataverse.privateurl.PrivateUrlServiceBean;
68
import edu.harvard.iq.dataverse.settings.FeatureFlags;
69
import edu.harvard.iq.dataverse.settings.JvmSettings;
70
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
71
import edu.harvard.iq.dataverse.util.FileUtil;
72
import edu.harvard.iq.dataverse.util.StringUtil;
73
import edu.harvard.iq.dataverse.util.SystemConfig;
74
import edu.harvard.iq.dataverse.util.URLTokenUtil;
75
import edu.harvard.iq.dataverse.util.UrlSignerUtil;
76
import edu.harvard.iq.dataverse.util.json.JsonUtil;
77
import jakarta.json.JsonReader;
78
import jakarta.persistence.EntityManager;
79
import jakarta.persistence.PersistenceContext;
80
import jakarta.servlet.http.HttpServletRequest;
81
import jakarta.ws.rs.core.Response;
82
import org.apache.http.util.EntityUtils;
83

84
@Stateless
85
@Named("GlobusServiceBean")
86
public class GlobusServiceBean implements java.io.Serializable {
×
87

88
    @EJB
89
    protected DatasetServiceBean datasetSvc;
90
    @EJB
91
    protected SettingsServiceBean settingsSvc;
92
    @Inject
93
    DataverseSession session;
94
    @Inject
95
    DataverseRequestServiceBean dataverseRequestSvc;
96
    @EJB
97
    protected AuthenticationServiceBean authSvc;
98
    @EJB
99
    EjbDataverseEngine commandEngine;
100
    @EJB
101
    UserNotificationServiceBean userNotificationService;
102
    @EJB
103
    PrivateUrlServiceBean privateUrlService;
104
    @EJB
105
    FileDownloadServiceBean fileDownloadService;
106
    @EJB
107
    DataFileServiceBean dataFileSvc;
108
    @EJB
109
    PermissionServiceBean permissionSvc;
110
    @EJB 
111
    IngestServiceBean ingestSvc;
112
    @EJB
113
    SystemConfig systemConfig;
114
    @PersistenceContext(unitName = "VDCNet-ejbPU")
115
    private EntityManager em;
116

117
    private static final Logger logger = Logger.getLogger(GlobusServiceBean.class.getCanonicalName());
×
118
    private static final SimpleDateFormat logFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss");
×
119

120
    private String getRuleId(GlobusEndpoint endpoint, String principal, String permissions)
121
            throws MalformedURLException {
122

123
        String principalType = "identity";
×
124

125
        URL url = new URL("https://transfer.api.globusonline.org/v0.10/endpoint/" + endpoint.getId() + "/access_list");
×
126
        MakeRequestResponse result = makeRequest(url, "Bearer", endpoint.getClientToken(), "GET", null);
×
127
        if (result.status == 200) {
×
128
            AccessList al = parseJson(result.jsonResponse, AccessList.class, false);
×
129

130
            for (int i = 0; i < al.getDATA().size(); i++) {
×
131
                Permissions pr = al.getDATA().get(i);
×
132

133
                if ((pr.getPath().equals(endpoint.getBasePath() + "/") || pr.getPath().equals(endpoint.getBasePath()))
×
134
                        && pr.getPrincipalType().equals(principalType)
×
135
                        && ((principal == null) || (principal != null && pr.getPrincipal().equals(principal)))
×
136
                        && pr.getPermissions().equals(permissions)) {
×
137
                    return pr.getId();
×
138
                } else {
139
                    logger.fine(pr.getPath() + " === " + endpoint.getBasePath() + " == " + pr.getPrincipalType());
×
140
                    continue;
×
141
                }
142
            }
143
        }
144
        return null;
×
145
    }
146

147
    /**
148
     * Call to delete a globus rule related to the specified dataset.
149
     * 
150
     * @param ruleId       - Globus rule id - assumed to be associated with the
151
     *                     dataset's file path (should not be called with a user
152
     *                     specified rule id w/o further checking)
153
     * @param dataset    - the dataset associated with the rule
154
     * @param globusLogger - a separate logger instance, may be null
155
     */
156
    public void deletePermission(String ruleId, Dataset dataset, Logger globusLogger) {
157
        globusLogger.fine("Start deleting rule " + ruleId + " for dataset " + dataset.getId());
×
158
        if (ruleId.length() > 0) {
×
159
            if (dataset != null) {
×
160
                GlobusEndpoint endpoint = getGlobusEndpoint(dataset);
×
161
                if (endpoint != null) {
×
162
                    String accessToken = endpoint.getClientToken();
×
163
                    globusLogger.info("Start deleting permissions.");
×
164
                    try {
165
                        URL url = new URL("https://transfer.api.globusonline.org/v0.10/endpoint/" + endpoint.getId()
×
166
                                + "/access/" + ruleId);
167
                        MakeRequestResponse result = makeRequest(url, "Bearer", accessToken, "DELETE", null);
×
168
                        if (result.status != 200) {
×
169
                            globusLogger.warning("Cannot delete access rule " + ruleId);
×
170
                        } else {
171
                            globusLogger.info("Access rule " + ruleId + " was deleted successfully");
×
172
                        }
173
                    } catch (MalformedURLException ex) {
×
174
                        logger.log(Level.WARNING,
×
175
                                "Failed to delete access rule " + ruleId + " on endpoint " + endpoint.getId(), ex);
×
176
                    }
×
177
                }
178
            }
179
        }
180
    }
×
181

182
    /**
183
     * Request read/write access for the specified principal and generate a list of
184
     * accessible paths for new files for the specified dataset.
185
     * 
186
     * @param principal     - the id of the Globus principal doing the transfer
187
     * @param dataset
188
     * @param numberOfPaths - how many files are to be transferred
189
     * @return
190
     */
191
    public JsonObject requestAccessiblePaths(String principal, Dataset dataset, int numberOfPaths) {
192

193
        GlobusEndpoint endpoint = getGlobusEndpoint(dataset);
×
194
        String principalType = "identity";
×
195

196
        Permissions permissions = new Permissions();
×
197
        permissions.setDATA_TYPE("access");
×
198
        permissions.setPrincipalType(principalType);
×
199
        permissions.setPrincipal(principal);
×
200
        permissions.setPath(endpoint.getBasePath() + "/");
×
201
        permissions.setPermissions("rw");
×
202
        
203
        JsonObjectBuilder response = Json.createObjectBuilder();
×
204
        //Try to create the directory (202 status) if it does not exist (502-already exists)
205
        int mkDirStatus = makeDirs(endpoint, dataset);
×
206
        if (!(mkDirStatus== 202 || mkDirStatus == 502)) {
×
207
            return response.add("status", mkDirStatus).build();
×
208
        }
209
        //The dir for the dataset's data exists, so try to request permission for the principal
210
        int requestPermStatus = requestPermission(endpoint, dataset, permissions);
×
211
        response.add("status", requestPermStatus);
×
212
        if (requestPermStatus == 201) {
×
213
            String driverId = dataset.getEffectiveStorageDriverId();
×
214
            JsonObjectBuilder paths = Json.createObjectBuilder();
×
215
            for (int i = 0; i < numberOfPaths; i++) {
×
216
                String storageIdentifier = DataAccess.getNewStorageIdentifier(driverId);
×
217
                int lastIndex = Math.max(storageIdentifier.lastIndexOf("/"), storageIdentifier.lastIndexOf(":"));
×
218
                paths.add(storageIdentifier, endpoint.getBasePath() + "/" + storageIdentifier.substring(lastIndex + 1));
×
219

220
            }
221
            response.add("paths", paths.build());
×
222
        }
223
        return response.build();
×
224
    }
225

226
    /**
227
     * Call to create the directories for the specified dataset.
228
     * 
229
     * @param dataset
230
     * @return - an error status at whichever subdir the process fails at or the
231
     *         final success status
232
     */
233
    private int makeDirs(GlobusEndpoint endpoint, Dataset dataset) {
234
        logger.fine("Creating dirs: " + endpoint.getBasePath());
×
235
        int index = endpoint.getBasePath().lastIndexOf(dataset.getAuthorityForFileStorage())
×
236
                + dataset.getAuthorityForFileStorage().length();
×
237
        String nextDir = endpoint.getBasePath().substring(0, index);
×
238
        int response = makeDir(endpoint, nextDir);
×
239
        String identifier = dataset.getIdentifierForFileStorage();
×
240
        //Usually identifiers will have 0 or 1 slashes (e.g. FK2/ABCDEF) but the while loop will handle any that could have more
241
        //Will skip if the first makeDir above failed
242
        while ((identifier.length() > 0) && ((response == 202 || response == 502))) {
×
243
            index = identifier.indexOf('/');
×
244
            if (index == -1) {
×
245
                //Last dir to create
246
                response = makeDir(endpoint, nextDir + "/" + identifier);
×
247
                identifier = "";
×
248
            } else {
249
                //The next dir to create
250
                nextDir = nextDir + "/" + identifier.substring(0, index);
×
251
                response = makeDir(endpoint, nextDir);
×
252
                //The rest of the identifier
253
                identifier = identifier.substring(index + 1);
×
254
            }
255
        }
256
        return response;
×
257
    }
258
    
259
    private int makeDir(GlobusEndpoint endpoint, String dir) {
260
        MakeRequestResponse result = null;
×
261
        String body = "{\"DATA_TYPE\":\"mkdir\",\"path\":\"" + dir + "\"}";
×
262
        try {
263
            logger.fine(body);
×
264
            URL url = new URL(
×
265
                    "https://transfer.api.globusonline.org/v0.10/operation/endpoint/" + endpoint.getId() + "/mkdir");
×
266
            result = makeRequest(url, "Bearer", endpoint.getClientToken(), "POST", body);
×
267

268
            switch (result.status) {
×
269
            case 202:
270
                logger.fine("Dir " + dir + " was created successfully.");
×
271
                break;
×
272
            case 502:
273
                logger.fine("Dir " + dir + " already exists.");
×
274
                break;
×
275
            default:
276
                logger.warning("Status " + result.status + " received when creating dir " + dir);
×
277
                logger.fine("Response: " + result.jsonResponse);
×
278
            }
279
        } catch (MalformedURLException ex) {
×
280
            // Misconfiguration
281
            logger.warning("Failed to create dir on " + endpoint.getId());
×
282
            return 500;
×
283
        }
×
284
        return result.status;
×
285
    }
286
    
287
    private int requestPermission(GlobusEndpoint endpoint, Dataset dataset, Permissions permissions) {
288
        Gson gson = new GsonBuilder().create();
×
289
        MakeRequestResponse result = null;
×
290
        logger.fine("Start creating the rule");
×
291

292
        try {
293
            URL url = new URL("https://transfer.api.globusonline.org/v0.10/endpoint/" + endpoint.getId() + "/access");
×
294
            result = makeRequest(url, "Bearer", endpoint.getClientToken(), "POST", gson.toJson(permissions));
×
295

296
            switch (result.status) {
×
297
            case 404:
298
                logger.severe("Endpoint " + endpoint.getId() + " was not found");
×
299
                break;
×
300
            case 400:
301
                logger.severe("Path " + permissions.getPath() + " is not valid");
×
302
                break;
×
303
            case 409:
304
                logger.warning("ACL already exists or Endpoint ACL already has the maximum number of access rules");
×
305
                break;
×
306
            case 201:
307
                JsonObject globusResponse = JsonUtil.getJsonObject(result.jsonResponse);
×
308
                if (globusResponse != null && globusResponse.containsKey("access_id")) {
×
309
                    permissions.setId(globusResponse.getString("access_id"));
×
310
                    monitorTemporaryPermissions(permissions.getId(), dataset.getId());
×
311
                    logger.fine("Access rule " + permissions.getId() + " was created successfully");
×
312
                } else {
313
                    // Shouldn't happen!
314
                    logger.warning("Access rule id not returned for dataset " + dataset.getId());
×
315
                }
316
            }
317
            return result.status;
×
318
        } catch (MalformedURLException ex) {
×
319
            // Misconfiguration
320
            logger.warning("Failed to create access rule URL for " + endpoint.getId());
×
321
            return 500;
×
322
        }
323
    }
324

325
    /**
326
     * Given an array of remote files to be referenced in the dataset, create a set
327
     * of valid storage identifiers and return a map of the remote file paths to
328
     * storage identifiers.
329
     * 
330
     * @param dataset
331
     * @param referencedFiles - a JSON array of remote files to be referenced in the
332
     *                        dataset - each should be a string with the <Globus
333
     *                        endpoint>/path/to/file
334
     * @return - a map of supplied paths to valid storage identifiers
335
     */
336
    public JsonObject requestReferenceFileIdentifiers(Dataset dataset, JsonArray referencedFiles) {
337
        String driverId = dataset.getEffectiveStorageDriverId();
×
338
        JsonArray endpoints = GlobusAccessibleStore.getReferenceEndpointsWithPaths(driverId);
×
339

340
        JsonObjectBuilder fileMap = Json.createObjectBuilder();
×
341
        referencedFiles.forEach(value -> {
×
342
            if (value.getValueType() != ValueType.STRING) {
×
343
                throw new JsonParsingException("ReferencedFiles must be strings", null);
×
344
            }
345
            String referencedFile = ((JsonString) value).getString();
×
346
            boolean valid = false;
×
347
            for (int i = 0; i < endpoints.size(); i++) {
×
348
                if (referencedFile.startsWith(((JsonString) endpoints.get(i)).getString())) {
×
349
                    valid = true;
×
350
                }
351
            }
352
            if (!valid) {
×
353
                throw new IllegalArgumentException(
×
354
                        "Referenced file " + referencedFile + " is not in an allowed endpoint/path");
355
            }
356
            String storageIdentifier = DataAccess.getNewStorageIdentifier(driverId);
×
357
            fileMap.add(referencedFile, storageIdentifier + "//" + referencedFile);
×
358
        });
×
359
        return fileMap.build();
×
360
    }
361

362
    /**
363
     * A cache of temporary permission requests - for upload (rw) and download (r)
364
     * access. When a temporary permission request is created, it is added to the
365
     * cache. After GLOBUS_CACHE_MAXAGE minutes, if a transfer has not been started,
366
     * the permission will be revoked/deleted. (If a transfer has been started, the
367
     * permission will not be revoked/deleted until the transfer is complete. This
368
     * is handled in other methods.)
369
     */
370
    // ToDo - nominally this doesn't need to be as long as the allowed time for the
371
    // downloadCache so there could be two separate settings.
372
    // Single cache of open rules/permission requests
373
    private final Cache<String, Long> rulesCache = Caffeine.newBuilder()
×
374
            .expireAfterWrite(Duration.of(JvmSettings.GLOBUS_CACHE_MAXAGE.lookup(Integer.class), ChronoUnit.MINUTES))
×
375
            .scheduler(Scheduler.systemScheduler()).evictionListener((ruleId, datasetId, cause) -> {
×
376
                // Delete rules that expire
377
                logger.fine("Rule " + ruleId + " expired");
×
378
                Dataset dataset = datasetSvc.find(datasetId);
×
379
                deletePermission((String) ruleId, dataset, logger);
×
380
            })
×
381

382
            .build();
×
383

384
    // Convenience method to add a temporary permission request to the cache -
385
    // allows logging of temporary permission requests
386
    private void monitorTemporaryPermissions(String ruleId, long datasetId) {
387
        logger.fine("Adding rule " + ruleId + " for dataset " + datasetId);
×
388
        rulesCache.put(ruleId, datasetId);
×
389
    }
×
390

391
    /**
392
     * Call the Globus API to get info about the transfer.
393
     * 
394
     * @param accessToken
395
     * @param taskId       - the Globus task id supplied by the user
396
     * @param globusLogger - the transaction-specific logger to use (separate log
397
     *                     files are created in general, some calls may use the
398
     *                     class logger)
399
     * @return
400
     * @throws MalformedURLException
401
     */
402
    public GlobusTaskState getTask(String accessToken, String taskId, Logger globusLogger) {
403

NEW
404
        Logger myLogger = globusLogger != null ? globusLogger : logger;
×
405

406
        URL url;
407
        try {
NEW
408
            url = new URL("https://transfer.api.globusonline.org/v0.10/endpoint_manager/task/" + taskId);
×
NEW
409
        } catch (MalformedURLException mue) {
×
NEW
410
            myLogger.warning("Malformed URL exception when trying to contact Globus. Globus API url: "
×
411
                    + "https://transfer.api.globusonline.org/v0.10/endpoint_manager/task/"
412
                    + taskId);
NEW
413
            return null;
×
NEW
414
        }
×
415

416
        MakeRequestResponse result = makeRequest(url, "Bearer", accessToken, "GET", null);
×
417

NEW
418
        GlobusTaskState task = null;
×
419

420
        if (result.status == 200) {
×
NEW
421
            task = parseJson(result.jsonResponse, GlobusTaskState.class, false);
×
422
        }
423
        if (result.status != 200) {
×
424
            // @todo It should probably retry it 2-3 times before giving up;
425
            // similarly, it should probably differentiate between a "no such task" 
426
            // response and something intermittent like a server/network error or 
427
            // an expired token... i.e. something that's recoverable (?)
NEW
428
            myLogger.warning("Cannot find information for the task " + taskId + " : Reason :   "
×
UNCOV
429
                    + result.jsonResponse.toString());
×
430
        }
431

432
        return task;
×
433
    }
434

435
    /**
436
     * Globus call to get an access token for the user using the long-term token we
437
     * hold.
438
     * 
439
     * @param globusBasicToken - the base64 encoded Globus Basic token comprised of
440
     *                         the <Globus user id>:<key>
441
     * @return - a valid Globus access token
442
     */
443
    public static AccessToken getClientToken(String globusBasicToken) {
444
        URL url;
445
        AccessToken clientTokenUser = null;
×
446

447
        try {
448
            url = new URL(
×
449
                    "https://auth.globus.org/v2/oauth2/token?scope=openid+email+profile+urn:globus:auth:scope:transfer.api.globus.org:all&grant_type=client_credentials");
450

451
            MakeRequestResponse result = makeRequest(url, "Basic", globusBasicToken, "POST", null);
×
452
            if (result.status == 200) {
×
453
                clientTokenUser = parseJson(result.jsonResponse, AccessToken.class, true);
×
454
            }
455
        } catch (MalformedURLException e) {
×
456
            // On a statically defined URL...
457
            e.printStackTrace();
×
458
        }
×
459
        return clientTokenUser;
×
460
    }
461

462
    private static MakeRequestResponse makeRequest(URL url, String authType, String authCode, String method,
463
            String jsonString) {
464
        String str = null;
×
465
        HttpURLConnection connection = null;
×
466
        int status = 0;
×
467
        try {
468
            connection = (HttpURLConnection) url.openConnection();
×
469
            // Basic
470
            logger.fine("For URL: " + url.toString());
×
471
            connection.setRequestProperty("Authorization", authType + " " + authCode);
×
472
            // connection.setRequestProperty("Content-Type",
473
            // "application/x-www-form-urlencoded");
474
            connection.setRequestMethod(method);
×
475
            if (jsonString != null) {
×
476
                connection.setRequestProperty("Content-Type", "application/json");
×
477
                connection.setRequestProperty("Accept", "application/json");
×
478
                logger.fine(jsonString);
×
479
                connection.setDoOutput(true);
×
480

481
                OutputStreamWriter wr = new OutputStreamWriter(connection.getOutputStream());
×
482
                wr.write(jsonString);
×
483
                wr.flush();
×
484
            }
485

486
            status = connection.getResponseCode();
×
487
            logger.fine("Status now " + status);
×
488
            InputStream result = connection.getInputStream();
×
489
            if (result != null) {
×
490
                str = readResultJson(result).toString();
×
491
                logger.fine("str is " + result.toString());
×
492
            } else {
493
                logger.fine("Result is null");
×
494
                str = null;
×
495
            }
496

497
            logger.fine("status: " + status);
×
498
        } catch (IOException ex) {
×
499
            logger.severe(ex.getMessage());
×
500
            logger.fine(ex.getCause().toString());
×
501
            logger.fine(ex.getStackTrace().toString());
×
502
        } finally {
503
            if (connection != null) {
×
504
                connection.disconnect();
×
505
            }
506
        }
507
        MakeRequestResponse r = new MakeRequestResponse(str, status);
×
508
        return r;
×
509

510
    }
511

512
    private static StringBuilder readResultJson(InputStream in) {
513
        StringBuilder sb = null;
×
514
        try (BufferedReader br = new BufferedReader(new InputStreamReader(in))) {
×
515
            sb = new StringBuilder();
×
516
            String line;
517
            while ((line = br.readLine()) != null) {
×
518
                sb.append(line + "\n");
×
519
            }
520
            br.close();
×
521
            logger.fine(sb.toString());
×
522
        } catch (IOException e) {
×
523
            sb = null;
×
524
            logger.severe(e.getMessage());
×
525
        }
×
526
        return sb;
×
527
    }
528

529
    private static <T> T parseJson(String sb, Class<T> jsonParserClass, boolean namingPolicy) {
530
        if (sb != null) {
×
531
            Gson gson = null;
×
532
            if (namingPolicy) {
×
533
                gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
×
534

535
            } else {
536
                gson = new GsonBuilder().create();
×
537
            }
538
            T jsonClass = gson.fromJson(sb, jsonParserClass);
×
539
            return jsonClass;
×
540
        } else {
541
            logger.severe("Bad respond from token rquest");
×
542
            return null;
×
543
        }
544
    }
545

546
    static class MakeRequestResponse {
547
        public String jsonResponse;
548
        public int status;
549

550
        MakeRequestResponse(String jsonResponse, int status) {
×
551
            this.jsonResponse = jsonResponse;
×
552
            this.status = status;
×
553
        }
×
554

555
    }
556

557
    /**
558
     * Cache of open download Requests This cache keeps track of the set of files
559
     * selected for transfer out (download) via Globus. It is a means of
560
     * transferring the list from the DatasetPage, where it is generated via user UI
561
     * actions, and the Datasets/globusDownloadParameters API.
562
     * 
563
     * Nominally, the dataverse-globus app will call that API endpoint and then
564
     * /requestGlobusDownload, at which point the cached info is sent to the app. If
565
     * the app doesn't call within 5 minutes (the time allowed to call
566
     * /globusDownloadParameters) + GLOBUS_CACHE_MAXAGE minutes (a ~longer period
567
     * giving the user time to make choices in the app), the cached info is deleted.
568
     * 
569
     */
570
    private final Cache<String, JsonObject> downloadCache = Caffeine.newBuilder()
×
571
            .expireAfterWrite(
×
572
                    Duration.of(JvmSettings.GLOBUS_CACHE_MAXAGE.lookup(Integer.class) + 5, ChronoUnit.MINUTES))
×
573
            .scheduler(Scheduler.systemScheduler()).evictionListener((downloadId, datasetId, cause) -> {
×
574
                // Delete downloads that expire
575
                logger.fine("Download for " + downloadId + " expired");
×
576
            })
×
577

578
            .build();
×
579

580
    public JsonObject getFilesForDownload(String downloadId) {
581
        return downloadCache.getIfPresent(downloadId);
×
582
    }
583

584
    public int setPermissionForDownload(Dataset dataset, String principal) {
585
        GlobusEndpoint endpoint = getGlobusEndpoint(dataset);
×
586
        String principalType = "identity";
×
587

588
        Permissions permissions = new Permissions();
×
589
        permissions.setDATA_TYPE("access");
×
590
        permissions.setPrincipalType(principalType);
×
591
        permissions.setPrincipal(principal);
×
592
        permissions.setPath(endpoint.getBasePath() + "/");
×
593
        permissions.setPermissions("r");
×
594

595
        return requestPermission(endpoint, dataset, permissions);
×
596
    }
597

598
    // Generates the URL to launch the Globus app for upload
599
    public String getGlobusAppUrlForDataset(Dataset d) {
600
        return getGlobusAppUrlForDataset(d, true, null);
×
601
    }
602

603
    /**
604
     * Generated the App URl for upload (in) or download (out)
605
     * 
606
     * @param d         - the dataset involved
607
     * @param upload    - boolean, true for upload, false for download
608
     * @param dataFiles - a list of the DataFiles to be downloaded
609
     * @return
610
     */
611
    public String getGlobusAppUrlForDataset(Dataset d, boolean upload, List<DataFile> dataFiles) {
612
        String localeCode = session.getLocaleCode();
×
613
        ApiToken apiToken = null;
×
614
        User user = session.getUser();
×
615

616
        if (user instanceof AuthenticatedUser) {
×
617
            apiToken = authSvc.findApiTokenByUser((AuthenticatedUser) user);
×
618

619
            if ((apiToken == null) || (apiToken.getExpireTime().before(new Date()))) {
×
620
                logger.fine("Created apiToken for user: " + user.getIdentifier());
×
621
                apiToken = authSvc.generateApiTokenForUser((AuthenticatedUser) user);
×
622
            }
623
        }
624
        String driverId = d.getEffectiveStorageDriverId();
×
625
        try {
626
        } catch (Exception e) {
627
            logger.warning("GlobusAppUrlForDataset: Failed to get storePrefix for " + driverId);
628
        }
629

630
        // Use URLTokenUtil for params currently in common with external tools.
631
        URLTokenUtil tokenUtil = new URLTokenUtil(d, null, apiToken, localeCode);
×
632
        String appUrl = settingsSvc.getValueForKey(SettingsServiceBean.Key.GlobusAppUrl, "http://localhost");
×
633
        String callback = null;
×
634
        if (upload) {
×
635
            appUrl = appUrl + "/upload?dvLocale={localeCode}";
×
636
            callback = SystemConfig.getDataverseSiteUrlStatic() + "/api/v1/datasets/" + d.getId()
×
637
                    + "/globusUploadParameters?locale=" + localeCode;
638
        } else {
639
            // Download
640
            JsonObject files = GlobusUtil.getFilesMap(dataFiles, d);
×
641

642
            String downloadId = UUID.randomUUID().toString();
×
643
            downloadCache.put(downloadId, files);
×
644
            appUrl = appUrl + "/download?dvLocale={localeCode}";
×
645
            callback = SystemConfig.getDataverseSiteUrlStatic() + "/api/v1/datasets/" + d.getId()
×
646
                    + "/globusDownloadParameters?locale=" + localeCode + "&downloadId=" + downloadId;
647

648
        }
649
        if (apiToken != null) {
×
650
            callback = UrlSignerUtil.signUrl(callback, 5, apiToken.getAuthenticatedUser().getUserIdentifier(),
×
651
                    HttpMethod.GET,
652
                    JvmSettings.API_SIGNING_SECRET.lookupOptional().orElse("") + apiToken.getTokenString());
×
653
        } else {
654
            // Shouldn't happen
655
            logger.warning("Unable to get api token for user: " + user.getIdentifier());
×
656
        }
657
        appUrl = appUrl + "&callback=" + Base64.getEncoder().encodeToString(StringUtils.getBytesUtf8(callback));
×
658

659
        String finalUrl = tokenUtil.replaceTokensWithValues(appUrl);
×
660
        logger.fine("Calling app: " + finalUrl);
×
661
        return finalUrl;
×
662
    }
663

664
    private String getGlobusDownloadScript(Dataset dataset, ApiToken apiToken, List<DataFile> downloadDFList) {
665
        return URLTokenUtil.getScriptForUrl(getGlobusAppUrlForDataset(dataset, false, downloadDFList));
×
666
    }
667

668
    @Asynchronous
669
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
670
    public void globusUpload(JsonObject jsonData, Dataset dataset, String httpRequestUrl,
671
            AuthenticatedUser authUser) throws IllegalArgumentException, ExecutionException, InterruptedException, MalformedURLException {
672

673
        // Before we do anything else, let's do some basic validation of what
674
        // we've been passed:
675
        
NEW
676
        JsonArray filesJsonArray = jsonData.getJsonArray("files");
×
677

NEW
678
        if (filesJsonArray == null || filesJsonArray.size() < 1) {
×
NEW
679
            throw new IllegalArgumentException("No valid json entries supplied for the files being uploaded");
×
680
        }
681
        
NEW
682
        Date startDate = new Date();
×
683
        
NEW
684
        String logTimestamp = logFormatter.format(startDate);
×
UNCOV
685
        Logger globusLogger = Logger.getLogger(
×
686
                "edu.harvard.iq.dataverse.upload.client.DatasetServiceBean." + "GlobusUpload" + logTimestamp);
NEW
687
        String logFileName = System.getProperty("com.sun.aas.instanceRoot") + File.separator + "logs" + File.separator + "globusUpload_" + dataset.getId() + "_" + logTimestamp
×
688
                + ".log";
689
        FileHandler fileHandler;
690

691
        try {
692
            fileHandler = new FileHandler(logFileName);
×
693
            globusLogger.setUseParentHandlers(false);
×
694
        } catch (IOException | SecurityException ex) {
×
695
            Logger.getLogger(DatasetServiceBean.class.getName()).log(Level.SEVERE, null, ex);
×
NEW
696
            fileHandler = null;
×
697
        }
×
698

NEW
699
        if (fileHandler != null) {
×
700
            globusLogger.addHandler(fileHandler);
×
701
        } else {
702
            globusLogger = logger;
×
703
        }
704

705
        logger.fine("json: " + JsonUtil.prettyPrint(jsonData));
×
706
        
NEW
707
        globusLogger.info("Globus upload initiated");
×
708

709
        String taskIdentifier = jsonData.getString("taskIdentifier");
×
710

711
        GlobusEndpoint endpoint = getGlobusEndpoint(dataset);
×
NEW
712
        GlobusTaskState taskState = getTask(endpoint.getClientToken(), taskIdentifier, globusLogger);
×
NEW
713
        String ruleId = getRuleId(endpoint, taskState.getOwner_id(), "rw");
×
714
        logger.fine("Found rule: " + ruleId);
×
715
        if (ruleId != null) {
×
716
            Long datasetId = rulesCache.getIfPresent(ruleId);
×
717
            if (datasetId != null) {
×
718
                // Will not delete rule
719
                rulesCache.invalidate(ruleId);
×
720
            }
721
        }
722
        
723
        // Wait before first check
724
        Thread.sleep(5000);
×
725
        
NEW
726
        if (FeatureFlags.GLOBUS_USE_EXPERIMENTAL_ASYNC_FRAMEWORK.enabled()) {
×
727
            
728
            // Save the task information in the database so that the Globus monitoring
729
            // service can continue checking on its progress.
730
            
NEW
731
            GlobusTaskInProgress taskInProgress = new GlobusTaskInProgress(taskIdentifier, GlobusTaskInProgress.TaskType.UPLOAD, dataset, endpoint.getClientToken(), authUser, ruleId, new Timestamp(startDate.getTime()));
×
NEW
732
            em.persist(taskInProgress);
×
733
            
734
            // Save the metadata entries that define the files that are being uploaded
735
            // in the database. These entries will be used once/if the uploads
736
            // completes successfully to add the files to the dataset. 
737

NEW
738
            for (JsonObject fileJsonObject : filesJsonArray.getValuesAs(JsonObject.class)) {
×
NEW
739
                ExternalFileUploadInProgress fileUploadRecord = new ExternalFileUploadInProgress(taskIdentifier, fileJsonObject.toString());
×
740

NEW
741
                em.persist(fileUploadRecord);
×
NEW
742
            }
×
743
            
NEW
744
            if (fileHandler != null) {
×
NEW
745
                fileHandler.close();
×
746
            }
747

748
            // return and forget
NEW
749
            return;
×
750
        }
751
        
752
        
753
        // the old implementation that relies on looping continuosly, 
754
        // sleeping-then-checking the task status repeatedly:
755
        
756
        // globus task status check
757
        // (the following method performs continuous looped checks of the remote
758
        // Globus API, monitoring it for as long as it takes for the task to 
759
        // finish one way or another!)
NEW
760
        taskState = globusStatusCheck(endpoint, taskIdentifier, globusLogger);
×
761
        // @todo null check, or make sure it's never null
NEW
762
        String taskStatus = GlobusUtil.getTaskStatus(taskState);
×
763

NEW
764
        boolean taskSuccess = GlobusUtil.isTaskCompleted(taskState);
×
765
        
NEW
766
        processCompletedUploadTask(dataset, filesJsonArray, authUser, ruleId, globusLogger, taskSuccess, taskStatus);
×
767
        
NEW
768
        if (fileHandler != null) {
×
NEW
769
            fileHandler.close();
×
770
        }
NEW
771
    }
×
772
    /**
773
     * As the name suggests, the method completes and finalizes an upload task, 
774
     * whether it completed successfully or failed. (In the latter case, it 
775
     * simply sends a failure notification and does some cleanup). 
776
     * The method is called in both task monitoring scenarios: the old method, 
777
     * that relies on continuous looping, and the new, implemented on the basis
778
     * of timer-like monitoring from a dedicated monitoring Singleton service.
779
     * @param dataset           the dataset
780
     * @param filesJsonArray    JsonArray containing files metadata entries as passed to /addGlobusFiles
781
     * @param authUser          the user that should be be performing the addFiles call 
782
     *                          finalizing adding the files to the Dataset. Note that this 
783
     *                          user will need to be obtained from the saved api token, when this
784
     *                          method is called via the TaskMonitoringService
785
     * @param ruleId            Globus rule/permission id associated with the task
786
     * @param myLogger          the Logger; if null, the main logger of the service bean will be used
787
     * @param fileHandler       FileHandler associated with the Logger, when not null
788
     * @param taskSuccess       boolean task status of the completed task
789
     * @param taskState         human-readable task status label as reported by the Globus API
790
     * the method should not throw any exceptions; all the exceptions thrown 
791
     * by the methods within are expected to be intercepted. 
792
     */
793
    private void processCompletedUploadTask(Dataset dataset, 
794
            JsonArray filesJsonArray, 
795
            AuthenticatedUser authUser, 
796
            String ruleId, 
797
            Logger globusLogger,
798
            boolean taskSuccess, 
799
            String taskStatus) {
800
        
NEW
801
        Logger myLogger = globusLogger == null ? logger : globusLogger;
×
802
        
UNCOV
803
        if (ruleId != null) {
×
804
            // Transfer is complete, so delete rule
NEW
805
            deletePermission(ruleId, dataset, myLogger);
×
806
        }
807
        
808
        // If success, switch to an EditInProgress lock - do this before removing the
809
        // GlobusUpload lock
810
        // Keeping a lock through the add datafiles API call avoids a conflicting edit
811
        // and keeps any open dataset page refreshing until the datafile appears.
812
        
NEW
813
        if (taskSuccess) {
×
NEW
814
            myLogger.info("Finished upload via Globus job.");
×
815

NEW
816
            DatasetLock editLock = datasetSvc.addDatasetLock(dataset.getId(), 
×
817
                    DatasetLock.Reason.EditInProgress, 
NEW
818
                    (authUser).getId(), 
×
819
                    "Completing Globus Upload");
NEW
820
            if (editLock != null) {
×
NEW
821
                dataset.addLock(editLock);
×
822
            } else {
NEW
823
                myLogger.log(Level.WARNING, "Failed to lock the dataset (dataset id={0})", dataset.getId());
×
824
            }
825
        }
826

827
        DatasetLock gLock = dataset.getLockFor(DatasetLock.Reason.GlobusUpload);
×
828
        if (gLock == null) {
×
829
            logger.log(Level.WARNING, "No lock found for dataset");
×
830
        } else {
831
            logger.log(Level.FINE, "Removing GlobusUpload lock " + gLock.getId());
×
832
            /*
833
             * Note: This call to remove a lock only works immediately because it is in
834
             * another service bean. Despite the removeDatasetLocks method having the
835
             * REQUIRES_NEW transaction annotation, when the globusUpload method and that
836
             * method were in the same bean (globusUpload was in the DatasetServiceBean to
837
             * start), the globus lock was still seen in the API call initiated in the
838
             * addFilesAsync method called within the globusUpload method. I.e. it appeared
839
             * that the lock removal was not committed/visible outside this method until
840
             * globusUpload itself ended.
841
             * (from @landreev:) If I understand the comment above correctly - annotations 
842
             * like "@TransactionAttribute(REQUIRES_NEW) do NOT work when you call a method 
843
             * directly within the same service bean. Strictly speaking, it's not the 
844
             * "within the same bean" part that is the key, rather, these annotations
845
             * only apply when calling a method via an @EJB-defined service. So it 
846
             * is generally possible to call another method within FooServiceBean 
847
             * with the REQUIRES_NEW transaction taking effect - but then it would need
848
             * to define *itself* as an @EJB - 
849
             * @EJB FooServiceBean fooSvc; 
850
             * ...
851
             * fooSvc.doSomethingInNewTransaction(...); 
852
             * etc. 
853
             */
854
            datasetSvc.removeDatasetLocks(dataset, DatasetLock.Reason.GlobusUpload);
×
855
        }
856
        
NEW
857
        if (!taskSuccess) {
×
858
            String comment; 
NEW
859
            if (taskStatus != null) {
×
NEW
860
                comment = "Reason : " + taskStatus.split("#")[1] + "<br> Short Description : "
×
NEW
861
                        + taskStatus.split("#")[2];
×
862
            } else {
NEW
863
                comment = "No further information available";
×
864
            }
865
            
NEW
866
            myLogger.info("Globus Upload task failed ");
×
867
            userNotificationService.sendNotification((AuthenticatedUser) authUser, new Timestamp(new Date().getTime()),
×
NEW
868
                    UserNotification.Type.GLOBUSUPLOADREMOTEFAILURE, dataset.getId(), comment, true);
×
869

870
        } else {
×
871
            try {
NEW
872
                processUploadedFiles(filesJsonArray, dataset, authUser, myLogger);
×
UNCOV
873
            } catch (Exception e) {
×
NEW
874
                logger.info("Exception from processUploadedFiles call ");
×
875
                e.printStackTrace();
×
NEW
876
                myLogger.info("Exception from processUploadedFiles call " + e.getMessage());
×
877
                datasetSvc.removeDatasetLocks(dataset, DatasetLock.Reason.EditInProgress);
×
878
            }
×
879
        }
880
        if (ruleId != null) {
×
NEW
881
            deletePermission(ruleId, dataset, myLogger);
×
NEW
882
            myLogger.info("Removed upload permission: " + ruleId);
×
883
        }
884
        //if (fileHandler != null) {
885
        //    fileHandler.close();
886
        //}
887
        
UNCOV
888
    }
×
889
    
890
    
891
    /**
892
     * The code in this method is copy-and-pasted from the previous Borealis 
893
     * implemenation. 
894
     * @todo see if it can be refactored and simplified a bit, the json manipulation 
895
     *       specifically (?)
896
     * @param filesJsonArray    JsonArray containing files metadata entries as passed to /addGlobusFiles
897
     * @param dataset           the dataset
898
     * @param authUser          the user that should be be performing the addFiles call 
899
     *                          finalizing adding the files to the Dataset. Note that this 
900
     *                          user will need to be obtained from the saved api token, when this
901
     *                          method is called via the TaskMonitoringService
902
     * @param myLogger          the Logger; if null, the main logger of the service bean will be used
903
     * @throws IOException, InterruptedException, ExecutionException @todo may need to throw more exceptions (?)
904
     */
905
    private void processUploadedFiles(JsonArray filesJsonArray, Dataset dataset, AuthenticatedUser authUser, Logger myLogger) throws IOException, InterruptedException, ExecutionException {
NEW
906
        myLogger = myLogger != null ? myLogger : logger; 
×
907
        
NEW
908
        Integer countAll = 0;
×
NEW
909
        Integer countSuccess = 0;
×
NEW
910
        Integer countError = 0;
×
NEW
911
        Integer countAddFilesSuccess = 0;
×
NEW
912
        String notificationErrorMessage = ""; 
×
913
        
NEW
914
        List<String> inputList = new ArrayList<String>();
×
915

NEW
916
        String datasetIdentifier = dataset.getAuthorityForFileStorage() + "/"
×
NEW
917
                + dataset.getIdentifierForFileStorage();
×
918

NEW
919
        for (JsonObject fileJsonObject : filesJsonArray.getValuesAs(JsonObject.class)) {
×
920

921
            // storageIdentifier s3://gcs5-bucket1:1781cfeb8a7-748c270a227c from
922
            // externalTool
NEW
923
            String storageIdentifier = fileJsonObject.getString("storageIdentifier");
×
NEW
924
            String[] parts = DataAccess.getDriverIdAndStorageLocation(storageIdentifier);
×
NEW
925
            String storeId = parts[0];
×
926
            // If this is an S3 store, we need to split out the bucket name
NEW
927
            String[] bits = parts[1].split(":");
×
NEW
928
            String bucketName = "";
×
NEW
929
            if (bits.length > 1) {
×
NEW
930
                bucketName = bits[0];
×
931
            }
NEW
932
            String fileId = bits[bits.length - 1];
×
933

934
            // fullpath s3://gcs5-bucket1/10.5072/FK2/3S6G2E/1781cfeb8a7-4ad9418a5873
935
            // or globus:///10.5072/FK2/3S6G2E/1781cfeb8a7-4ad9418a5873
NEW
936
            String fullPath = storeId + "://" + bucketName + "/" + datasetIdentifier + "/" + fileId;
×
NEW
937
            String fileName = fileJsonObject.getString("fileName");
×
938

NEW
939
            inputList.add(fileId + "IDsplit" + fullPath + "IDsplit" + fileName);
×
NEW
940
        }
×
941

942
        // calculateMissingMetadataFields: checksum, mimetype
NEW
943
        JsonObject newfilesJsonObject = calculateMissingMetadataFields(inputList, myLogger);
×
NEW
944
        JsonArray newfilesJsonArray = newfilesJsonObject.getJsonArray("files");
×
NEW
945
        logger.fine("Size: " + newfilesJsonArray.size());
×
NEW
946
        logger.fine("Val: " + JsonUtil.prettyPrint(newfilesJsonArray.getJsonObject(0)));
×
NEW
947
        JsonArrayBuilder addFilesJsonData = Json.createArrayBuilder();
×
948

NEW
949
        for (JsonObject fileJsonObject : filesJsonArray.getValuesAs(JsonObject.class)) {
×
950

NEW
951
            countAll++;
×
NEW
952
            String storageIdentifier = fileJsonObject.getString("storageIdentifier");
×
NEW
953
            String fileName = fileJsonObject.getString("fileName");
×
NEW
954
            String[] parts = DataAccess.getDriverIdAndStorageLocation(storageIdentifier);
×
955
            // If this is an S3 store, we need to split out the bucket name
NEW
956
            String[] bits = parts[1].split(":");
×
NEW
957
            if (bits.length > 1) {
×
958
            }
NEW
959
            String fileId = bits[bits.length - 1];
×
960

NEW
961
            List<JsonObject> newfileJsonObject = IntStream.range(0, newfilesJsonArray.size())
×
NEW
962
                    .mapToObj(index -> ((JsonObject) newfilesJsonArray.get(index)).getJsonObject(fileId))
×
NEW
963
                    .filter(Objects::nonNull).collect(Collectors.toList());
×
NEW
964
            if (newfileJsonObject != null) {
×
NEW
965
                logger.fine("List Size: " + newfileJsonObject.size());
×
966
                // if (!newfileJsonObject.get(0).getString("hash").equalsIgnoreCase("null")) {
NEW
967
                JsonPatch path = Json.createPatchBuilder()
×
NEW
968
                        .add("/md5Hash", newfileJsonObject.get(0).getString("hash")).build();
×
NEW
969
                fileJsonObject = path.apply(fileJsonObject);
×
NEW
970
                path = Json.createPatchBuilder()
×
NEW
971
                        .add("/mimeType", newfileJsonObject.get(0).getString("mime")).build();
×
NEW
972
                fileJsonObject = path.apply(fileJsonObject);
×
NEW
973
                addFilesJsonData.add(fileJsonObject);
×
NEW
974
                countSuccess++;
×
975
                // } else {
976
                // globusLogger.info(fileName
977
                // + " will be skipped from adding to dataset by second API due to missing
978
                // values ");
979
                // countError++;
980
                // }
NEW
981
            } else {
×
NEW
982
                myLogger.info(fileName
×
983
                        + " will be skipped from adding to dataset in the final AddReplaceFileHelper.addFiles() call. ");
NEW
984
                countError++;
×
985
            }
NEW
986
        }
×
987

NEW
988
        String newjsonData = addFilesJsonData.build().toString();
×
989

NEW
990
        myLogger.info("Successfully generated new JsonData for addFiles call");
×
991

NEW
992
        myLogger.info("Files passed to /addGlobusFiles: " + countAll);
×
NEW
993
        myLogger.info("Files processed successfully: " + countSuccess);
×
NEW
994
        myLogger.info("Files failures to process: " + countError);
×
995

NEW
996
        if (countSuccess < 1) {
×
997
            // We don't have any valid entries to call addFiles() for; so, no 
998
            // need to proceed. 
NEW
999
            notificationErrorMessage = "Failed to successfully process any of the file entries, "
×
1000
                    + "out of the " + countAll + " total as submitted to Dataverse";
NEW
1001
            userNotificationService.sendNotification((AuthenticatedUser) authUser,
×
NEW
1002
                        new Timestamp(new Date().getTime()), UserNotification.Type.GLOBUSUPLOADREMOTEFAILURE,
×
NEW
1003
                        dataset.getId(), notificationErrorMessage, true);
×
NEW
1004
            return;
×
NEW
1005
        } else if (countSuccess < countAll) {
×
NEW
1006
            notificationErrorMessage = "Out of the " + countAll + " file entries submitted to /addGlobusFiles " 
×
1007
                    + "only " + countSuccess + " could be successfully parsed and processed. ";
1008
        }
1009
        
1010
        // A new AddReplaceFileHelper implementation, replacing the old one that 
1011
        // was relying on calling /addFiles api via curl: 
1012
        
1013
        // Passing null for the HttpServletRequest to make a new DataverseRequest. 
1014
        // The parent method is always executed asynchronously, so the real request 
1015
        // that was associated with the original API call that triggered this upload
1016
        // cannot be obtained. 
NEW
1017
        DataverseRequest dataverseRequest = new DataverseRequest(authUser, (HttpServletRequest)null);
×
1018
        
NEW
1019
        AddReplaceFileHelper addFileHelper = new AddReplaceFileHelper(
×
1020
                dataverseRequest,
1021
                this.ingestSvc,
1022
                this.datasetSvc,
1023
                this.dataFileSvc,
1024
                this.permissionSvc,
1025
                this.commandEngine,
1026
                this.systemConfig
1027
        );
1028
                
1029
        // The old code had 2 sec. of sleep, so ...
NEW
1030
        Thread.sleep(2000);
×
1031

NEW
1032
        Response addFilesResponse = addFileHelper.addFiles(newjsonData, dataset, authUser);
×
1033

NEW
1034
        if (addFilesResponse == null) {
×
NEW
1035
            logger.info("null response from addFiles call");
×
1036
            //@todo add this case to the user notification in case of error
NEW
1037
            return;
×
1038
        }
1039
        
NEW
1040
        JsonObject addFilesJsonObject = JsonUtil.getJsonObject(addFilesResponse.getEntity().toString());
×
1041
        
1042
        // @todo null check?
NEW
1043
        String addFilesStatus = addFilesJsonObject.getString("status", null);
×
NEW
1044
        myLogger.info("addFilesResponse status: " + addFilesStatus);
×
1045
        
NEW
1046
        if (ApiConstants.STATUS_OK.equalsIgnoreCase(addFilesStatus)) {
×
NEW
1047
            if (addFilesJsonObject.containsKey("data") && addFilesJsonObject.getJsonObject("data").containsKey("Result")) {
×
1048

1049
                //Integer countAddFilesTotal = addFilesJsonObject.getJsonObject("data").getJsonObject("Result").getInt(ApiConstants.API_ADD_FILES_COUNT_PROCESSED, -1);
NEW
1050
                countAddFilesSuccess = addFilesJsonObject.getJsonObject("data").getJsonObject("Result").getInt(ApiConstants.API_ADD_FILES_COUNT_SUCCESSFUL, -1);
×
NEW
1051
                myLogger.info("Files successfully added by addFiles(): " + countAddFilesSuccess);
×
1052

1053
            } else {
NEW
1054
                myLogger.warning("Malformed addFiles response json: " + addFilesJsonObject.toString());
×
NEW
1055
                notificationErrorMessage = "Malformed response received when attempting to add the files to the dataset. ";
×
1056
            }
1057

NEW
1058
            myLogger.info("Completed addFiles call ");
×
NEW
1059
        } else if (ApiConstants.STATUS_ERROR.equalsIgnoreCase(addFilesStatus)) {
×
NEW
1060
            String addFilesMessage = addFilesJsonObject.getString("message", null);
×
1061

NEW
1062
            myLogger.log(Level.SEVERE,
×
1063
                    "******* Error while executing addFiles ", newjsonData);
NEW
1064
            myLogger.log(Level.SEVERE, "****** Output from addFiles: ", addFilesMessage);
×
NEW
1065
            notificationErrorMessage += "Error response received when attempting to add the files to the dataset: " + addFilesMessage + " "; 
×
1066

NEW
1067
        } else {
×
NEW
1068
            myLogger.log(Level.SEVERE,
×
1069
                    "******* Error while executing addFiles ", newjsonData);
NEW
1070
            notificationErrorMessage += "Unexpected error encountered when attemptingh to add the files to the dataset.";
×
1071
        }
1072
        
1073
        // if(!taskSkippedFiles)
NEW
1074
        if (countAddFilesSuccess == countAll) {
×
NEW
1075
            userNotificationService.sendNotification((AuthenticatedUser) authUser,
×
NEW
1076
                    new Timestamp(new Date().getTime()), UserNotification.Type.GLOBUSUPLOADCOMPLETED,
×
NEW
1077
                    dataset.getId(), countSuccess + " files added out of " + countAll, true);
×
NEW
1078
        } else if (countAddFilesSuccess > 0) {
×
1079
            // success, but partial:
NEW
1080
            userNotificationService.sendNotification((AuthenticatedUser) authUser,
×
NEW
1081
                    new Timestamp(new Date().getTime()),
×
NEW
1082
                    UserNotification.Type.GLOBUSUPLOADCOMPLETEDWITHERRORS, dataset.getId(),
×
1083
                    countSuccess + " files added out of " + countAll + notificationErrorMessage, true);
1084
        } else {
NEW
1085
            notificationErrorMessage = "".equals(notificationErrorMessage) 
×
NEW
1086
                    ? " No additional information is available." : notificationErrorMessage;
×
NEW
1087
            userNotificationService.sendNotification((AuthenticatedUser) authUser,
×
NEW
1088
                    new Timestamp(new Date().getTime()),
×
NEW
1089
                    UserNotification.Type.GLOBUSUPLOADLOCALFAILURE, dataset.getId(),
×
1090
                    notificationErrorMessage, true);
1091
        }
1092

1093
    }
×
1094
    
1095
    @Asynchronous
1096
    public void globusDownload(String jsonData, Dataset dataset, User authUser) throws MalformedURLException {
1097

1098
        String logTimestamp = logFormatter.format(new Date());
×
1099
        Logger globusLogger = Logger.getLogger(
×
1100
                "edu.harvard.iq.dataverse.upload.client.DatasetServiceBean." + "GlobusDownload" + logTimestamp);
1101

1102
        String logFileName = System.getProperty("com.sun.aas.instanceRoot") + File.separator + "logs" + File.separator + "globusDownload_id_" + dataset.getId() + "_" + logTimestamp
×
1103
                + ".log";
1104
        FileHandler fileHandler;
1105
        boolean fileHandlerSuceeded;
1106
        try {
1107
            fileHandler = new FileHandler(logFileName);
×
1108
            globusLogger.setUseParentHandlers(false);
×
1109
            fileHandlerSuceeded = true;
×
1110
        } catch (IOException | SecurityException ex) {
×
1111
            Logger.getLogger(DatasetServiceBean.class.getName()).log(Level.SEVERE, null, ex);
×
1112
            return;
×
1113
        }
×
1114

1115
        if (fileHandlerSuceeded) {
×
1116
            globusLogger.addHandler(fileHandler);
×
1117
        } else {
1118
            globusLogger = logger;
×
1119
        }
1120

1121
        globusLogger.info("Starting a globusDownload ");
×
1122

1123
        JsonObject jsonObject = null;
×
1124
        try {
1125
            jsonObject = JsonUtil.getJsonObject(jsonData);
×
1126
        } catch (Exception jpe) {
×
1127
            jpe.printStackTrace();
×
1128
            globusLogger.log(Level.SEVERE, "Error parsing dataset json. Json: {0}", jsonData);
×
1129
            // TODO: stop the process after this parsing exception.
1130
        }
×
1131

1132
        String taskIdentifier = jsonObject.getString("taskIdentifier");
×
1133

1134
        GlobusEndpoint endpoint = getGlobusEndpoint(dataset);
×
1135
        logger.fine("Endpoint path: " + endpoint.getBasePath());
×
1136

1137
        // If the rules_cache times out, the permission will be deleted. Presumably that
1138
        // doesn't affect a
1139
        // globus task status check
NEW
1140
        GlobusTaskState task = getTask(endpoint.getClientToken(), taskIdentifier, globusLogger);
×
1141
        String ruleId = getRuleId(endpoint, task.getOwner_id(), "r");
×
1142
        if (ruleId != null) {
×
1143
            logger.fine("Found rule: " + ruleId);
×
1144
            Long datasetId = rulesCache.getIfPresent(ruleId);
×
1145
            if (datasetId != null) {
×
1146
                logger.fine("Deleting from cache: rule: " + ruleId);
×
1147
                // Will not delete rule
1148
                rulesCache.invalidate(ruleId);
×
1149
            }
1150
        } else {
×
1151
            // Something is wrong - the rule should be there (a race with the cache timing
1152
            // out?)
1153
            logger.warning("ruleId not found for taskId: " + taskIdentifier);
×
1154
        }
1155
        task = globusStatusCheck(endpoint, taskIdentifier, globusLogger);
×
1156
        // @todo null check?
NEW
1157
        String taskStatus = GlobusUtil.getTaskStatus(task);
×
1158

1159
        // Transfer is done (success or failure) so delete the rule
1160
        if (ruleId != null) {
×
1161
            logger.fine("Deleting: rule: " + ruleId);
×
1162
            deletePermission(ruleId, dataset, globusLogger);
×
1163
        }
1164

1165
        if (taskStatus.startsWith("FAILED") || taskStatus.startsWith("INACTIVE")) {
×
1166
            String comment = "Reason : " + taskStatus.split("#")[1] + "<br> Short Description : "
×
1167
                    + taskStatus.split("#")[2];
×
1168
            if (authUser != null && authUser instanceof AuthenticatedUser) {
×
1169
                userNotificationService.sendNotification((AuthenticatedUser) authUser, new Timestamp(new Date().getTime()),
×
1170
                        UserNotification.Type.GLOBUSDOWNLOADCOMPLETEDWITHERRORS, dataset.getId(), comment, true);
×
1171
            }
1172
            
1173
            globusLogger.info("Globus task failed during download process: "+comment);
×
1174
        } else if (authUser != null && authUser instanceof AuthenticatedUser) {
×
1175
        
1176
            boolean taskSkippedFiles = (task.getSkip_source_errors() == null) ? false : task.getSkip_source_errors();
×
1177
            if (!taskSkippedFiles) {
×
1178
                userNotificationService.sendNotification((AuthenticatedUser) authUser,
×
1179
                        new Timestamp(new Date().getTime()), UserNotification.Type.GLOBUSDOWNLOADCOMPLETED,
×
1180
                        dataset.getId());
×
1181
            } else {
1182
                userNotificationService.sendNotification((AuthenticatedUser) authUser,
×
1183
                        new Timestamp(new Date().getTime()), UserNotification.Type.GLOBUSDOWNLOADCOMPLETEDWITHERRORS,
×
1184
                        dataset.getId(), "");
×
1185
            }
1186
        }
1187
    }
×
1188

1189
    Executor executor = Executors.newFixedThreadPool(10);
×
1190

1191
    private GlobusTaskState globusStatusCheck(GlobusEndpoint endpoint, String taskId, Logger globusLogger)
1192
            throws MalformedURLException {
NEW
1193
        boolean taskCompleted = false;
×
NEW
1194
        GlobusTaskState task = null;
×
1195
        int pollingInterval = SystemConfig.getIntLimitFromStringOrDefault(
×
1196
                settingsSvc.getValueForKey(SettingsServiceBean.Key.GlobusPollingInterval), 50);
×
1197
        do {
1198
            try {
1199
                globusLogger.info("checking globus transfer task   " + taskId);
×
1200
                Thread.sleep(pollingInterval * 1000);
×
1201
                // Call the (centralized) Globus API to check on the task state/status:
1202
                task = getTask(endpoint.getClientToken(), taskId, globusLogger);
×
NEW
1203
                taskCompleted = GlobusUtil.isTaskCompleted(task);
×
1204
            } catch (Exception ex) {
×
1205
                ex.printStackTrace();
×
1206
            }
×
1207

NEW
1208
        } while (!taskCompleted);
×
1209

1210
        globusLogger.info("globus transfer task completed successfully");
×
1211
        return task;
×
1212
    }
1213
    
1214
    public JsonObject calculateMissingMetadataFields(List<String> inputList, Logger globusLogger)
1215
            throws InterruptedException, ExecutionException, IOException {
1216

1217
        List<CompletableFuture<FileDetailsHolder>> hashvalueCompletableFutures = inputList.stream()
×
1218
                .map(iD -> calculateDetailsAsync(iD, globusLogger)).collect(Collectors.toList());
×
1219

1220
        CompletableFuture<Void> allFutures = CompletableFuture
×
1221
                .allOf(hashvalueCompletableFutures.toArray(new CompletableFuture[hashvalueCompletableFutures.size()]));
×
1222

1223
        CompletableFuture<List<FileDetailsHolder>> allCompletableFuture = allFutures.thenApply(future -> {
×
1224
            return hashvalueCompletableFutures.stream().map(completableFuture -> completableFuture.join())
×
1225
                    .collect(Collectors.toList());
×
1226
        });
1227

1228
        CompletableFuture<?> completableFuture = allCompletableFuture.thenApply(files -> {
×
1229
            return files.stream().map(d -> json(d)).collect(toJsonArray());
×
1230
        });
1231

1232
        JsonArrayBuilder filesObject = (JsonArrayBuilder) completableFuture.get();
×
1233

1234
        JsonObject output = Json.createObjectBuilder().add("files", filesObject).build();
×
1235

1236
        return output;
×
1237

1238
    }
1239

1240
    private CompletableFuture<FileDetailsHolder> calculateDetailsAsync(String id, Logger globusLogger) {
1241

1242
        return CompletableFuture.supplyAsync(() -> {
×
1243
            try {
1244
                Thread.sleep(2000);
×
1245
            } catch (InterruptedException e) {
×
1246
                e.printStackTrace();
×
1247
            }
×
1248
            try {
1249
                return (calculateDetails(id, globusLogger));
×
1250
            } catch (InterruptedException | IOException e) {
×
1251
                e.printStackTrace();
×
1252
            }
1253
            return null;
×
1254
        }, executor).exceptionally(ex -> {
×
1255
            return null;
×
1256
        });
1257
    }
1258

1259
    private FileDetailsHolder calculateDetails(String id, Logger globusLogger)
1260
            throws InterruptedException, IOException {
1261
        int count = 0;
×
1262
        String checksumVal = "";
×
1263
        InputStream in = null;
×
1264
        String fileId = id.split("IDsplit")[0];
×
1265
        String fullPath = id.split("IDsplit")[1];
×
1266
        String fileName = id.split("IDsplit")[2];
×
1267

1268
        // ToDo: what if the file does not exist in s3
1269
        // (L.A.) - good question. maybe it should call .open and .exists() here? 
1270
        //          otherwise, there doesn't seem to be any diagnostics as to which 
1271
        //          files uploaded successfully and which failed (?)
1272
        //          ... however, any partially successful upload cases should be 
1273
        //          properly handled later, during the .addFiles() call - only 
1274
        //          the files that actually exists in storage remotely will be 
1275
        //          added to the dataset permanently then. 
1276
        // ToDo: what if checksum calculation failed
1277
        // (L.A.) - this appears to have been addressed - by using "Not available in Dataverse"
1278
        //          in place of a checksum. 
1279

NEW
1280
        String storageDriverId = DataAccess.getDriverIdAndStorageLocation(fullPath)[0];
×
1281

NEW
1282
        if (StorageIO.isDataverseAccessible(storageDriverId)) {
×
1283
            do {
1284
                try {
NEW
1285
                    StorageIO<DvObject> dataFileStorageIO = DataAccess.getDirectStorageIO(fullPath);
×
NEW
1286
                        in = dataFileStorageIO.getInputStream();
×
NEW
1287
                    checksumVal = FileUtil.calculateChecksum(in, DataFile.ChecksumType.MD5);
×
NEW
1288
                    count = 3;
×
NEW
1289
                } catch (IOException ioex) {
×
NEW
1290
                    count = 3;
×
NEW
1291
                    logger.fine(ioex.getMessage());
×
NEW
1292
                    globusLogger.info(
×
1293
                            "DataFile (fullPath " + fullPath + ") does not appear to be accessible within Dataverse: ");
NEW
1294
                } catch (Exception ex) {
×
NEW
1295
                    count = count + 1;
×
NEW
1296
                    ex.printStackTrace();
×
NEW
1297
                    logger.info(ex.getMessage());
×
NEW
1298
                    Thread.sleep(5000);
×
NEW
1299
                }
×
NEW
1300
            } while (count < 3);
×
1301
        }
1302

1303
        if (checksumVal.length() == 0) {
×
1304
            checksumVal = "Not available in Dataverse";
×
1305
        }
1306

1307
        String mimeType = calculatemime(fileName);
×
1308
        globusLogger.info(" File Name " + fileName + "  File Details " + fileId + " checksum = " + checksumVal
×
1309
                + " mimeType = " + mimeType);
1310
        return new FileDetailsHolder(fileId, checksumVal, mimeType);
×
1311
        // getBytes(in)+"" );
1312
        // calculatemime(fileName));
1313
    }
1314

1315
    public String calculatemime(String fileName) throws InterruptedException {
1316

1317
        String finalType = FileUtil.MIME_TYPE_UNDETERMINED_DEFAULT;
×
1318
        String type = FileUtil.determineFileTypeByNameAndExtension(fileName);
×
1319

1320
        if (type != null && !type.isBlank()) {
×
1321
            if (FileUtil.useRecognizedType(finalType, type)) {
×
1322
                finalType = type;
×
1323
            }
1324
        }
1325

1326
        return finalType;
×
1327
    }
1328

1329
    private GlobusEndpoint getGlobusEndpoint(DvObject dvObject) {
1330
        Dataset dataset = null;
×
1331
        if (dvObject instanceof Dataset) {
×
1332
            dataset = (Dataset) dvObject;
×
1333
        } else if (dvObject instanceof DataFile) {
×
1334
            dataset = (Dataset) dvObject.getOwner();
×
1335
        } else {
1336
            throw new IllegalArgumentException("Unsupported DvObject type: " + dvObject.getClass().getName());
×
1337
        }
1338
        String driverId = dataset.getEffectiveStorageDriverId();
×
1339
        GlobusEndpoint endpoint = null;
×
1340

1341
        String directoryPath = GlobusAccessibleStore.getTransferPath(driverId);
×
1342

1343
        if (GlobusAccessibleStore.isDataverseManaged(driverId) && (dataset != null)) {
×
1344
            directoryPath = directoryPath + "/" + dataset.getAuthorityForFileStorage() + "/"
×
1345
                    + dataset.getIdentifierForFileStorage();
×
1346
        } else {
1347
            // remote store - may have path in file storageidentifier
1348
            String relPath = dvObject.getStorageIdentifier()
×
1349
                    .substring(dvObject.getStorageIdentifier().lastIndexOf("//") + 2);
×
1350
            int filenameStart = relPath.lastIndexOf("/") + 1;
×
1351
            if (filenameStart > 0) {
×
1352
                directoryPath = directoryPath + relPath.substring(0, filenameStart);
×
1353
            }
1354
        }
1355
        logger.fine("directoryPath finally: " + directoryPath);
×
1356

1357
        String endpointId = GlobusAccessibleStore.getTransferEndpointId(driverId);
×
1358

1359
        logger.fine("endpointId: " + endpointId);
×
1360

1361
        String globusToken = GlobusAccessibleStore.getGlobusToken(driverId);
×
1362

1363
        AccessToken accessToken = GlobusServiceBean.getClientToken(globusToken);
×
1364
        String clientToken = accessToken.getOtherTokens().get(0).getAccessToken();
×
1365
        endpoint = new GlobusEndpoint(endpointId, clientToken, directoryPath);
×
1366

1367
        return endpoint;
×
1368
    }
1369

1370
    // This helper method is called from the Download terms/guestbook/etc. popup,
1371
    // when the user clicks the "ok" button. We use it, instead of calling
1372
    // downloadServiceBean directly, in order to differentiate between single
1373
    // file downloads and multiple (batch) downloads - since both use the same
1374
    // terms/etc. popup.
1375
    public void writeGuestbookAndStartTransfer(GuestbookResponse guestbookResponse,
1376
            boolean doNotSaveGuestbookResponse) {
1377
        PrimeFaces.current().executeScript("PF('guestbookAndTermsPopup').hide()");
×
1378
        guestbookResponse.setEventType(GuestbookResponse.DOWNLOAD);
×
1379

1380
        ApiToken apiToken = null;
×
1381
        User user = session.getUser();
×
1382
        if (user instanceof AuthenticatedUser) {
×
1383
            apiToken = authSvc.findApiTokenByUser((AuthenticatedUser) user);
×
1384
        } else if (user instanceof PrivateUrlUser) {
×
1385
            PrivateUrlUser privateUrlUser = (PrivateUrlUser) user;
×
1386
            PrivateUrl privUrl = privateUrlService.getPrivateUrlFromDatasetId(privateUrlUser.getDatasetId());
×
1387
            apiToken = new ApiToken();
×
1388
            apiToken.setTokenString(privUrl.getToken());
×
1389
        }
1390

1391
        DataFile df = guestbookResponse.getDataFile();
×
1392
        if (df != null) {
×
1393
            logger.fine("Single datafile case for writeGuestbookAndStartTransfer");
×
1394
            List<DataFile> downloadDFList = new ArrayList<DataFile>(1);
×
1395
            downloadDFList.add(df);
×
1396
            if (!doNotSaveGuestbookResponse) {
×
1397
                fileDownloadService.writeGuestbookResponseRecord(guestbookResponse);
×
1398
            }
1399
            PrimeFaces.current().executeScript(getGlobusDownloadScript(df.getOwner(), apiToken, downloadDFList));
×
1400
        } else {
×
1401
            // Following FileDownloadServiceBean writeGuestbookAndStartBatchDownload
1402
            List<String> list = new ArrayList<>(Arrays.asList(guestbookResponse.getSelectedFileIds().split(",")));
×
1403
            List<DataFile> selectedFiles = new ArrayList<DataFile>();
×
1404
            for (String idAsString : list) {
×
1405
                try {
1406
                    Long fileId = Long.parseLong(idAsString);
×
1407
                    // If we need to create a GuestBookResponse record, we have to
1408
                    // look up the DataFile object for this file:
NEW
1409
                    df = dataFileSvc.findCheapAndEasy(fileId);
×
1410
                    selectedFiles.add(df);
×
1411
                    if (!doNotSaveGuestbookResponse) {
×
1412
                        guestbookResponse.setDataFile(df);
×
1413
                        fileDownloadService.writeGuestbookResponseRecord(guestbookResponse);
×
1414
                    }
1415
                } catch (NumberFormatException nfe) {
×
1416
                    logger.warning(
×
1417
                            "A file id passed to the writeGuestbookAndStartTransfer method as a string could not be converted back to Long: "
1418
                                    + idAsString);
1419
                    return;
×
1420
                }
×
1421

1422
            }
×
1423
            if (!selectedFiles.isEmpty()) {
×
1424
                // Use dataset from one file - files should all be from the same dataset
1425
                PrimeFaces.current().executeScript(getGlobusDownloadScript(df.getOwner(), apiToken, selectedFiles));
×
1426
            }
1427
        }
1428
    }
×
1429
    
1430
    public List<GlobusTaskInProgress> findAllOngoingTasks() {
NEW
1431
        return em.createQuery("select object(o) from GlobusTaskInProgress as o order by o.startTime", GlobusTaskInProgress.class).getResultList();
×
1432
    }
1433
    
1434
    public void deleteTask(GlobusTaskInProgress task) {
NEW
1435
        GlobusTaskInProgress mergedTask = em.merge(task);
×
NEW
1436
        em.remove(mergedTask);
×
NEW
1437
    }
×
1438
    
1439
    public List<ExternalFileUploadInProgress> findExternalUploadsByTaskId(String taskId) {
NEW
1440
        return em.createNamedQuery("ExternalFileUploadInProgress.findByTaskId").setParameter("taskId", taskId).getResultList();    
×
1441
    }
1442
    
1443
    public void processCompletedTask(GlobusTaskInProgress globusTask, boolean taskSuccess, String taskStatus, Logger taskLogger) {
NEW
1444
        String ruleId = globusTask.getRuleId();
×
NEW
1445
        Dataset dataset = globusTask.getDataset();
×
NEW
1446
        AuthenticatedUser authUser = globusTask.getLocalUser();
×
NEW
1447
        if (authUser == null) {
×
1448
            // @todo log error message; do nothing 
NEW
1449
            return;
×
1450
        }
1451

NEW
1452
        if (GlobusTaskInProgress.TaskType.UPLOAD.equals(globusTask.getTaskType())) {
×
NEW
1453
            List<ExternalFileUploadInProgress> fileUploadsInProgress = findExternalUploadsByTaskId(globusTask.getTaskId());
×
1454

NEW
1455
            if (fileUploadsInProgress == null || fileUploadsInProgress.size() < 1) {
×
1456
                // @todo log error message; do nothing
1457
                // (will this ever happen though?)
NEW
1458
                return;
×
1459
            }
1460

NEW
1461
            JsonArrayBuilder filesJsonArrayBuilder = Json.createArrayBuilder();
×
1462

NEW
1463
            for (ExternalFileUploadInProgress pendingFile : fileUploadsInProgress) {
×
NEW
1464
                String jsonInfoString = pendingFile.getFileInfo();
×
NEW
1465
                JsonObject fileObject = JsonUtil.getJsonObject(jsonInfoString);
×
NEW
1466
                filesJsonArrayBuilder.add(fileObject);
×
NEW
1467
            }
×
1468

NEW
1469
            JsonArray filesJsonArray = filesJsonArrayBuilder.build();
×
1470

NEW
1471
            processCompletedUploadTask(dataset, filesJsonArray, authUser, ruleId, taskLogger, taskSuccess, taskStatus);
×
1472
        } else {
1473
            // @todo eventually, extend this async. framework to handle Glonus downloads as well
1474
        }
1475

NEW
1476
    }
×
1477
            
1478
    public void deleteExternalUploadRecords(String taskId) {
NEW
1479
        em.createNamedQuery("ExternalFileUploadInProgress.deleteByTaskId")
×
NEW
1480
                .setParameter("taskId", taskId)
×
NEW
1481
                .executeUpdate();
×
NEW
1482
    }
×
1483
}
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