ATFX Import of external references

Conflicts:
	org.eclipse.mdm.api.atfxadapter/src/main/java/org/eclipse/mdm/api/atfxadapter/ATFXContext.java
	org.eclipse.mdm.api.atfxadapter/src/main/java/org/eclipse/mdm/api/atfxadapter/ATFXContextFactory.java
	org.eclipse.mdm.apicopy/src/main/java/org/eclipse/mdm/apicopy/control/ImportTask.java
	org.eclipse.mdm.apicopy/src/main/java/org/eclipse/mdm/apicopy/control/TransferBase.java

Signed-off-by: Alexander Knoblauch <a.knoblauch@peak-solution.de>
diff --git a/org.eclipse.mdm.api.atfxadapter/src/main/java/org/eclipse/mdm/api/atfxadapter/filetransfer/ATFXFileService.java b/org.eclipse.mdm.api.atfxadapter/src/main/java/org/eclipse/mdm/api/atfxadapter/filetransfer/ATFXFileService.java
new file mode 100644
index 0000000..f83516a
--- /dev/null
+++ b/org.eclipse.mdm.api.atfxadapter/src/main/java/org/eclipse/mdm/api/atfxadapter/filetransfer/ATFXFileService.java
@@ -0,0 +1,219 @@
+/**

+ * 

+ */

+package org.eclipse.mdm.api.atfxadapter.filetransfer;

+

+import java.io.File;

+import java.io.FileInputStream;

+import java.io.IOException;

+import java.io.InputStream;

+import java.nio.file.Files;

+import java.nio.file.Path;

+import java.time.Duration;

+import java.time.LocalTime;

+import java.util.Collection;

+import java.util.List;

+import java.util.Map;

+import java.util.UUID;

+import java.util.concurrent.atomic.AtomicLong;

+import java.util.stream.Collectors;

+

+import org.eclipse.mdm.api.base.file.FileService;

+import org.eclipse.mdm.api.base.model.Entity;

+import org.eclipse.mdm.api.base.model.FileLink;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+

+/**

+ * @author akn

+ *

+ */

+public class ATFXFileService implements FileService {

+

+	private static final Logger LOGGER = LoggerFactory.getLogger(ATFXFileService.class);

+

+	private final Path parentDirectory;

+

+	public ATFXFileService(File parentDirectory) {

+		this.parentDirectory = parentDirectory.toPath();

+	}

+

+	/*

+	 * (non-Javadoc)

+	 * 

+	 * @see

+	 * org.eclipse.mdm.api.base.file.FileService#downloadSequential(org.eclipse.mdm.

+	 * api.base.model.Entity, java.nio.file.Path, java.util.Collection,

+	 * org.eclipse.mdm.api.base.file.FileService.ProgressListener)

+	 */

+	@Override

+	public void downloadSequential(Entity entity, Path target, Collection<FileLink> fileLinks,

+			ProgressListener progressListener) throws IOException {

+		Map<String, List<FileLink>> groups = fileLinks.stream().filter(FileLink::isRemote)

+				.collect(Collectors.groupingBy(FileLink::getRemotePath));

+

+		long totalSize = calculateDownloadSize(groups);

+		final AtomicLong transferred = new AtomicLong();

+		LocalTime start = LocalTime.now();

+		UUID id = UUID.randomUUID();

+		LOGGER.debug("Sequential download of {} file(s) with id '{}' started.", groups.size(), id);

+		for (List<FileLink> group : groups.values()) {

+			FileLink fileLink = group.get(0);

+

+			download(entity, target, fileLink, (b, p) -> {

+				double tranferredBytes = transferred.addAndGet(b);

+				if (progressListener != null) {

+					progressListener.progress(b, (float) (tranferredBytes / totalSize));

+				}

+			});

+

+			for (FileLink other : group.subList(1, group.size())) {

+				other.setLocalStream(fileLink.getLocalStream());

+			}

+		}

+		LOGGER.debug("Sequential download with id '{}' finished in {}.", id, Duration.between(start, LocalTime.now()));

+

+	}

+

+	private long calculateDownloadSize(Map<String, List<FileLink>> groups) {

+		List<FileLink> links = groups.values().stream().map(l -> l.get(0)).collect(Collectors.toList());

+		long totalSize = 0;

+		for (FileLink fileLink : links) {

+			File f = new File(fileLink.getRemotePath());

+			// overflow may occur in case of total size exceeds 9223 PB!

+			totalSize = Math.addExact(totalSize, f.length());

+		}

+

+		return totalSize;

+	}

+

+	/*

+	 * (non-Javadoc)

+	 * 

+	 * @see

+	 * org.eclipse.mdm.api.base.file.FileService#downloadParallel(org.eclipse.mdm.

+	 * api.base.model.Entity, java.nio.file.Path, java.util.Collection,

+	 * org.eclipse.mdm.api.base.file.FileService.ProgressListener)

+	 */

+	@Override

+	public void downloadParallel(Entity entity, Path target, Collection<FileLink> fileLinks,

+			ProgressListener progressListener) throws IOException {

+		// TODO Auto-generated method stub

+

+	}

+

+	/*

+	 * (non-Javadoc)

+	 * 

+	 * @see

+	 * org.eclipse.mdm.api.base.file.FileService#download(org.eclipse.mdm.api.base.

+	 * model.Entity, java.nio.file.Path, org.eclipse.mdm.api.base.model.FileLink,

+	 * org.eclipse.mdm.api.base.file.FileService.ProgressListener)

+	 */

+	@Override

+	public void download(Entity entity, Path target, FileLink fileLink, ProgressListener progressListener)

+			throws IOException {

+		if (Files.exists(target)) {

+			if (!Files.isDirectory(target)) {

+				throw new IllegalArgumentException("Target path is not a directory.");

+			}

+		} else {

+			Files.createDirectory(target);

+		}

+

+		try (InputStream inputStream = openStream(entity, fileLink)) {

+			Path absolutePath = target.resolve(fileLink.getFileName()).toAbsolutePath();

+			String remotePath = fileLink.getRemotePath();

+			LOGGER.debug("Starting download of file '{}' to '{}'.", remotePath, absolutePath);

+			LocalTime start = LocalTime.now();

+			Files.copy(inputStream, absolutePath);

+			LOGGER.debug("File '{}' successfully downloaded in {} to '{}'.", remotePath,

+					Duration.between(start, LocalTime.now()), absolutePath);

+		}

+	}

+

+	/*

+	 * (non-Javadoc)

+	 * 

+	 * @see

+	 * org.eclipse.mdm.api.base.file.FileService#openStream(org.eclipse.mdm.api.base

+	 * .model.Entity, org.eclipse.mdm.api.base.model.FileLink,

+	 * org.eclipse.mdm.api.base.file.FileService.ProgressListener)

+	 */

+	@Override

+	public InputStream openStream(Entity entity, FileLink fileLink, ProgressListener progressListener)

+			throws IOException {

+

+		return new FileInputStream(parentDirectory.resolve(fileLink.getRemotePath()).toFile());

+	}

+

+	/*

+	 * (non-Javadoc)

+	 * 

+	 * @see

+	 * org.eclipse.mdm.api.base.file.FileService#loadSize(org.eclipse.mdm.api.base.

+	 * model.Entity, org.eclipse.mdm.api.base.model.FileLink)

+	 */

+	@Override

+	public void loadSize(Entity entity, FileLink fileLink) throws IOException {

+		// TODO Auto-generated method stub

+

+	}

+

+	/*

+	 * (non-Javadoc)

+	 * 

+	 * @see

+	 * org.eclipse.mdm.api.base.file.FileService#uploadSequential(org.eclipse.mdm.

+	 * api.base.model.Entity, java.util.Collection,

+	 * org.eclipse.mdm.api.base.file.FileService.ProgressListener)

+	 */

+	@Override

+	public void uploadSequential(Entity entity, Collection<FileLink> fileLinks, ProgressListener progressListener)

+			throws IOException {

+		// TODO Auto-generated method stub

+

+	}

+

+	/*

+	 * (non-Javadoc)

+	 * 

+	 * @see

+	 * org.eclipse.mdm.api.base.file.FileService#uploadParallel(org.eclipse.mdm.api.

+	 * base.model.Entity, java.util.Collection,

+	 * org.eclipse.mdm.api.base.file.FileService.ProgressListener)

+	 */

+	@Override

+	public void uploadParallel(Entity entity, Collection<FileLink> fileLinks, ProgressListener progressListener)

+			throws IOException {

+		// TODO Auto-generated method stub

+

+	}

+

+	/*

+	 * (non-Javadoc)

+	 * 

+	 * @see

+	 * org.eclipse.mdm.api.base.file.FileService#delete(org.eclipse.mdm.api.base.

+	 * model.Entity, java.util.Collection)

+	 */

+	@Override

+	public void delete(Entity entity, Collection<FileLink> fileLinks) {

+		// TODO Auto-generated method stub

+

+	}

+

+	/*

+	 * (non-Javadoc)

+	 * 

+	 * @see

+	 * org.eclipse.mdm.api.base.file.FileService#delete(org.eclipse.mdm.api.base.

+	 * model.Entity, org.eclipse.mdm.api.base.model.FileLink)

+	 */

+	@Override

+	public void delete(Entity entity, FileLink fileLink) {

+		// TODO Auto-generated method stub

+

+	}

+

+}

diff --git a/org.eclipse.mdm.apicopy/src/main/java/org/eclipse/mdm/apicopy/control/TransferBase.java b/org.eclipse.mdm.apicopy/src/main/java/org/eclipse/mdm/apicopy/control/TransferBase.java
index e3bf757..7d335e8 100644
--- a/org.eclipse.mdm.apicopy/src/main/java/org/eclipse/mdm/apicopy/control/TransferBase.java
+++ b/org.eclipse.mdm.apicopy/src/main/java/org/eclipse/mdm/apicopy/control/TransferBase.java
@@ -13,9 +13,12 @@
  ********************************************************************************/
 package org.eclipse.mdm.apicopy.control;
 
+import java.io.IOException;
+import java.nio.file.Path;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -26,6 +29,7 @@
 import org.eclipse.mdm.api.base.ServiceNotProvidedException;
 import org.eclipse.mdm.api.base.Transaction;
 import org.eclipse.mdm.api.base.adapter.ModelManager;
+import org.eclipse.mdm.api.base.file.FileService;
 import org.eclipse.mdm.api.base.massdata.AnyTypeValuesBuilder;
 import org.eclipse.mdm.api.base.massdata.ComplexNumericalValuesBuilder;
 import org.eclipse.mdm.api.base.massdata.NumericalValuesBuilder;
@@ -45,6 +49,7 @@
 import org.eclipse.mdm.api.base.model.Test;
 import org.eclipse.mdm.api.base.model.TestStep;
 import org.eclipse.mdm.api.base.model.Value;
+import org.eclipse.mdm.api.base.model.ValueType;
 import org.eclipse.mdm.api.base.query.Filter;
 import org.eclipse.mdm.api.base.search.SearchService;
 import org.eclipse.mdm.api.dflt.ApplicationContext;
@@ -57,6 +62,7 @@
 import com.google.common.collect.LinkedListMultimap;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
+import com.google.common.io.Files;
 
 public abstract class TransferBase {
 	List<Class<? extends Entity>> supportedRootEntities = Arrays.asList(Project.class, Pool.class, Test.class,
@@ -69,6 +75,8 @@
 	EntityManager entityManagerDst;
 	EntityFactory entityFactoryDst;
 	ModelManager modelManagerDst;
+	FileService fileServiceDst;
+	FileService fileServiceSrc;
 
 	Map<EntityHolder, EntityHolder> mapSrcDstEntities = new HashMap<>();
 
@@ -84,6 +92,15 @@
 				.orElseThrow(() -> new ServiceNotProvidedException(EntityFactory.class));
 		modelManagerDst = contextDst.getModelManager()
 				.orElseThrow(() -> new ServiceNotProvidedException(ModelManager.class));
+
+		if (contextDst.getFileService().isPresent()) {
+			fileServiceDst = contextDst.getFileService().get();
+		}
+
+		if (contextSrc.getFileService().isPresent()) {
+			fileServiceSrc = contextSrc.getFileService().get();
+		}
+
 	}
 
 	ListMultimap<Class<? extends Entity>, Entity> loadParents(List<? extends Entity> entities) {
@@ -168,15 +185,75 @@
 		for (Map.Entry<String, Value> me : srcEntity.getValues().entrySet()) {
 			String key = me.getKey();
 			if (!ignoredAttributes.contains(key) && valueNamesDst.contains(key)) {
-				dstEntity.getValue(me.getKey()).set(me.getValue().extract());
+				Value valueSrc = me.getValue();
+
+				if (isFileLinkDataType(valueSrc.getValueType())) {
+					uploadFiles(srcEntity, dstEntity, valueSrc);
+				}
+
+				dstEntity.getValue(me.getKey()).set(valueSrc.extract());
 				Value value = dstEntity.getValue(me.getKey());
-				value.set(me.getValue().extract());
-				value.setValid(me.getValue().isValid());
+				value.set(valueSrc.extract());
+				value.setValid(valueSrc.isValid());
 			}
 		}
 
 	}
 
+	/**
+	 * 
+	 * @param valueType
+	 * @return
+	 */
+	private boolean isFileLinkDataType(ValueType<?> valueType) {
+		boolean isFileDataType = false;
+
+		if (ValueType.FILE_LINK_SEQUENCE.equals(valueType) || ValueType.FILE_LINK.equals(valueType)) {
+			isFileDataType = true;
+		}
+		return isFileDataType;
+	}
+
+	private void uploadFiles(Entity srcEntity, Entity dstEntity, Value valueSrc) {
+		try {
+			if (fileServiceSrc != null && fileServiceDst != null) {
+
+				if (ValueType.FILE_LINK_SEQUENCE.equals(valueSrc.getValueType())) {
+
+					List<FileLink> fileLinkList = downloadSourceFiles(srcEntity,
+							Arrays.asList(valueSrc.extract(ValueType.FILE_LINK_SEQUENCE)));
+					fileServiceDst.uploadSequential(dstEntity, fileLinkList, null);
+					valueSrc.set(fileLinkList.toArray(new FileLink[fileLinkList.size()]));
+				} else if (ValueType.FILE_LINK.equals(valueSrc.getValueType())) {
+					FileLink fileLink = downloadSourceFile(srcEntity, valueSrc.extract(ValueType.FILE_LINK));
+					fileServiceDst.uploadSequential(dstEntity, Collections.singletonList(fileLink), null);
+					valueSrc.set(fileLink);
+				}
+			}
+
+		} catch (IOException e) {
+			throw new ApiCopyException(e.getLocalizedMessage(), e);
+		}
+
+	}
+
+	private List<FileLink> downloadSourceFiles(Entity srcEntity, List<FileLink> fileLinkList) throws IOException {
+		List<FileLink> returnList = new ArrayList<FileLink>();
+
+		for (FileLink fileLink : fileLinkList) {
+			returnList.add(downloadSourceFile(srcEntity, fileLink));
+		}
+
+		return returnList;
+	}
+
+	private FileLink downloadSourceFile(Entity srcEntity, FileLink fileLinkSrc) throws IOException {
+		Path targetPath = Files.createTempDir().toPath();
+		Path filePathAbsolute = targetPath.resolve(fileLinkSrc.getFileName());
+		fileServiceSrc.download(srcEntity, targetPath, fileLinkSrc);
+		return FileLink.newLocal(filePathAbsolute);
+	}
+
 	WriteRequest createWriteRequest(ChannelGroup channelGroupDst, Channel channelDst, MeasuredValues measuredValues) {
 		WriteRequestBuilder wrb = WriteRequest.create(channelGroupDst, channelDst, measuredValues.getAxisType());
 		NumericalValuesBuilder builder = null;