blob: 5748049a35770f60c34a5788917adaddb3c41b06 [file] [log] [blame]
/********************************************************************************
* Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
********************************************************************************/
package org.eclipse.mdm.api.odsadapter.transaction;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.asam.ods.AoException;
import org.eclipse.mdm.api.base.file.FileService.ProgressListener;
import org.eclipse.mdm.api.base.model.Entity;
import org.eclipse.mdm.api.base.model.FileLink;
import org.eclipse.mdm.api.base.model.Value;
import org.eclipse.mdm.api.dflt.model.TemplateAttribute;
import org.eclipse.mdm.api.odsadapter.ODSContext;
import org.eclipse.mdm.api.odsadapter.filetransfer.CORBAFileService;
import org.eclipse.mdm.api.odsadapter.filetransfer.Transfer;
/**
* Manages new or removed externally linked files.
*
* @since 1.0.0
* @author Viktor Stoehr, Gigatronik Ingolstadt GmbH
*/
final class UploadService {
// ======================================================================
// Instance variables
// ======================================================================
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
private final Map<TemplateAttribute, Value> templateAttributeFileLinks = new HashMap<>();
private final List<FileLink> uploaded = new ArrayList<>();
private final Map<InputStream, String> remotePaths = new HashMap<>();
private final List<FileLink> toRemove = new ArrayList<>();
private final CORBAFileService fileService;
private final Entity entity;
// ======================================================================
// Constructors
// ======================================================================
/**
* Constructor.
*
* @param context Used for setup.
* @param entity Used for security checks.
* @param transfer The transfer type.
*/
UploadService(ODSContext context, Entity entity, Transfer transfer) {
fileService = new CORBAFileService(context, transfer);
this.entity = entity;
scheduler.scheduleAtFixedRate(() -> {
try {
context.getAoSession().getName();
} catch (AoException e) {
/*
* NOTE: This is done to keep the parent transaction's session alive till its
* commit or abort method is called. If this session refresh results in an
* error, then any running file transfer will abort with a proper error,
* therefore any exception here is completely ignored and explicitly NOT logged!
*/
}
}, 5, 5, TimeUnit.MINUTES);
}
// ======================================================================
// Public methods
// ======================================================================
/**
* Uploads new externally linked files stored in given
* {@link TemplateAttribute}s. The upload progress may be traced with a progress
* listener.
*
* @param templateAttributes The {@link TemplateAttribute}s.
* @param progressListener The progress listener.
* @throws IOException Thrown if unable to upload files.
*/
public void upload(Collection<TemplateAttribute> templateAttributes, ProgressListener progressListener)
throws IOException {
List<FileLink> fileLinks = new ArrayList<>();
for (TemplateAttribute templateAttribute : templateAttributes) {
Value defaultValue = templateAttribute.getDefaultValue();
if (!defaultValue.isValid()) {
continue;
}
if (defaultValue.getValueType().isFileLink()) {
fileLinks.add(defaultValue.extract());
} else if (defaultValue.getValueType().isFileLinkSequence()) {
fileLinks.addAll(Arrays.asList((FileLink[]) defaultValue.extract()));
} else {
throw new IllegalStateException("Template attribute's value type is not of type file link.");
}
templateAttributeFileLinks.put(templateAttribute, defaultValue);
}
if (!fileLinks.isEmpty()) {
uploadParallel(fileLinks, progressListener);
// remote paths available -> update template attribute
templateAttributeFileLinks.forEach((ta, v) -> ta.setDefaultValue(v.extract()));
}
}
/**
* Parallel upload of given {@link FileLink}s. Local {@link Path}s linked
* multiple times are uploaded only once. The upload progress may be traced with
* a progress listener.
*
* @param fileLinks Collection of {@code FileLink}s to upload.
* @param progressListener The progress listener.
* @throws IOException Thrown if unable to upload files.
*/
public void uploadParallel(Collection<FileLink> fileLinks, ProgressListener progressListener) throws IOException {
List<FileLink> filtered = retainForUpload(fileLinks);
try {
fileService.uploadParallel(entity, filtered, progressListener);
} finally {
filtered.stream().filter(FileLink::isRemote).forEach(fl -> {
remotePaths.put(fl.getLocalStream(), fl.getRemotePath());
uploaded.add(fl);
});
}
}
/**
* Once {@link #commit()} is called given {@link FileLink}s will be deleted from
* the remote storage.
*
* @param fileLinks Collection of {@code FileLink}s to delete.
*/
public void addToRemove(Collection<FileLink> fileLinks) {
toRemove.addAll(fileLinks);
}
/**
* Commits modifications of externally linked files.
*/
public void commit() {
fileService.delete(entity, toRemove);
scheduler.shutdown();
}
/**
* Aborts modifications of externally linked files.
*/
public void abort() {
fileService.delete(entity, uploaded);
uploaded.forEach(fl -> fl.setRemotePath(null));
templateAttributeFileLinks.forEach((ta, v) -> ta.setDefaultValue(v.extract()));
scheduler.shutdown();
}
// ======================================================================
// Private methods
// ======================================================================
/**
* Filters given {@link FileLink}s by removing already uploaded ones.
*
* @param fileLinks Will be filtered.
* @return Returns {@code FileLink}s which have to be uploaded.
*/
private List<FileLink> retainForUpload(Collection<FileLink> fileLinks) {
List<FileLink> filtered = new ArrayList<>(fileLinks);
for (FileLink fileLink : fileLinks) {
String remotePath = remotePaths.get(fileLink.getLocalStream());
if (remotePath != null && !remotePath.isEmpty()) {
fileLink.setRemotePath(remotePath);
filtered.remove(fileLink);
uploaded.add(fileLink);
}
}
return filtered;
}
}