Merge "Add deprecated notice" into dev
diff --git a/README.md b/README.md
index 8d7eb1f..80e73da 100644
--- a/README.md
+++ b/README.md
@@ -57,7 +57,7 @@
 
 
 In addition to that a query string may be used to search for entities.
-This query string is evaluatated and processed by a full text search
+This query string is evaluated and processed by a full text search
 which returns a result consisting of Test, TestStep or Measurement
 entities.
 
@@ -79,11 +79,11 @@
 complete, one can pass a listener to track the overall progress.
 
 NOTE:
-<pre>
+```
 This service does not allow manually uploading or deleting files. Instead 
 files are automatically uploaded / deleted while entities are written 
 within a Section 2.5, "Transaction".
-</pre>
+```
 
 ### 2.4. EntityFactory ###
 
@@ -120,12 +120,12 @@
 uploaded files is send on a best effort basis.
 
 IMPORTANT:
-<pre>
+```
 Externally linked files of deleted entities are only removed if the
 entity is an instance of FilesAttachable (Test, TestStep, Measurement).
 In any other case, externally linked files are not removed since this 
 may have an impact on other entities which reference the same file.
-</pre>
+```
 
 Besides the modification of entities, it is possible to write mass data
 by defining write requests. A write request describes in detail how the
@@ -140,7 +140,7 @@
 takes parameters, shown in the example below, to establish a
 connection. On success an entity manager is returned.
 
-
+```java
     Map<String, String> connectionParameters = new HashMap<>();
     connectionParameters.put(ODSEntityManagerFactory.PARAM_NAMESERVICE, "corbaloc::1.2@<SERVER>:<PORT>/NameService");
     connectionParameters.put(ODSEntityManagerFactory.PARAM_SERVICENAME, "<SERVICE>.ASAM-ODS");
@@ -149,6 +149,7 @@
     EntityManager entityManager = new ODSEntityManagerFactory().connect(connectionParameters);
     // do something useful
     entityManager.close();
+```
 
 ## 4. Copyright and License ##
 Copyright (c) 2015-2018 Contributors to the Eclipse Foundation
diff --git a/src/main/java/org/eclipse/mdm/api/odsadapter/ODSEntityManager.java b/src/main/java/org/eclipse/mdm/api/odsadapter/ODSEntityManager.java
index 6d7c786..3be7627 100644
--- a/src/main/java/org/eclipse/mdm/api/odsadapter/ODSEntityManager.java
+++ b/src/main/java/org/eclipse/mdm/api/odsadapter/ODSEntityManager.java
@@ -1,5 +1,5 @@
 /********************************************************************************
- * Copyright (c) 2015-2019 Contributors to the Eclipse Foundation
+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
diff --git a/src/main/java/org/eclipse/mdm/api/odsadapter/filetransfer/CORBAFileService.java b/src/main/java/org/eclipse/mdm/api/odsadapter/filetransfer/CORBAFileService.java
index 6f35af0..2e84f2e 100644
--- a/src/main/java/org/eclipse/mdm/api/odsadapter/filetransfer/CORBAFileService.java
+++ b/src/main/java/org/eclipse/mdm/api/odsadapter/filetransfer/CORBAFileService.java
@@ -1,5 +1,5 @@
 /********************************************************************************
- * Copyright (c) 2015-2019 Contributors to the Eclipse Foundation
+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -98,10 +98,15 @@
 	@Override
 	public void downloadSequential(Entity entity, Path target, Collection<FileLink> fileLinks,
 			ProgressListener progressListener) throws IOException {
+		downloadSequential(toElemID(entity), target, fileLinks, progressListener);
+	}
+
+	private void downloadSequential(ElemId elemId, 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(entity, groups);
+		long totalSize = calculateDownloadSize(elemId, groups);
 		final AtomicLong transferred = new AtomicLong();
 		LocalTime start = LocalTime.now();
 		UUID id = UUID.randomUUID();
@@ -109,7 +114,7 @@
 		for (List<FileLink> group : groups.values()) {
 			FileLink fileLink = group.get(0);
 
-			download(entity, target, fileLink, (b, p) -> {
+			download(elemId, target, fileLink, (b, p) -> {
 				double tranferredBytes = transferred.addAndGet(b);
 				if (progressListener != null) {
 					progressListener.progress(b, (float) (tranferredBytes / totalSize));
@@ -117,7 +122,7 @@
 			});
 
 			for (FileLink other : group.subList(1, group.size())) {
-				other.setLocalPath(fileLink.getLocalPath());
+				other.setLocalStream(fileLink.getLocalStream());
 			}
 		}
 		LOGGER.debug("Sequential download with id '{}' finished in {}.", id, Duration.between(start, LocalTime.now()));
@@ -129,17 +134,22 @@
 	@Override
 	public void downloadParallel(Entity entity, Path target, Collection<FileLink> fileLinks,
 			ProgressListener progressListener) throws IOException {
+		downloadParallel(toElemID(entity), target, fileLinks, progressListener);
+	}
+
+	private void downloadParallel(ElemId elemId, 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(entity, groups);
+		long totalSize = calculateDownloadSize(elemId, groups);
 		final AtomicLong transferred = new AtomicLong();
 		List<Callable<Void>> downloadTasks = new ArrayList<>();
 		for (List<FileLink> group : groups.values()) {
 			downloadTasks.add(() -> {
 				FileLink fileLink = group.get(0);
 
-				download(entity, target, fileLink, (b, p) -> {
+				download(elemId, target, fileLink, (b, p) -> {
 					double tranferredBytes = transferred.addAndGet(b);
 					if (progressListener != null) {
 						progressListener.progress(b, (float) (tranferredBytes / totalSize));
@@ -147,7 +157,7 @@
 				});
 
 				for (FileLink other : group.subList(1, group.size())) {
-					other.setLocalPath(fileLink.getLocalPath());
+					other.setLocalStream(fileLink.getLocalStream());
 				}
 
 				return null;
@@ -187,6 +197,11 @@
 	@Override
 	public void download(Entity entity, Path target, FileLink fileLink, ProgressListener progressListener)
 			throws IOException {
+		download(toElemID(entity), target, fileLink, progressListener);
+	}
+
+	private void download(ElemId elemId, 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.");
@@ -195,13 +210,12 @@
 			Files.createDirectory(target);
 		}
 
-		try (InputStream inputStream = openStream(entity, fileLink, progressListener)) {
-			fileLink.setLocalPath(target.resolve(fileLink.getFileName()));
-			Path absolutePath = fileLink.getLocalPath().toAbsolutePath();
+		try (InputStream inputStream = openStream(elemId, fileLink, progressListener)) {
+			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, fileLink.getLocalPath());
+			Files.copy(inputStream, absolutePath);
 			LOGGER.debug("File '{}' successfully downloaded in {} to '{}'.", remotePath,
 					Duration.between(start, LocalTime.now()), absolutePath);
 		}
@@ -213,24 +227,28 @@
 	@Override
 	public InputStream openStream(Entity entity, FileLink fileLink, ProgressListener progressListener)
 			throws IOException {
+		return openStream(toElemID(entity), fileLink, progressListener);
+	}
+
+	private InputStream openStream(ElemId elemId, FileLink fileLink, ProgressListener progressListener)
+			throws IOException {
 		InputStream sourceStream;
 		if (fileLink.isLocal()) {
 			// file is locally available -> USE this shortcut!
-			sourceStream = Files.newInputStream(fileLink.getLocalPath());
+			sourceStream = fileLink.getLocalStream();
 		} else if (fileLink.isRemote()) {
-			sourceStream = fileServer.openStream(fileLink, toElemID(entity));
+			sourceStream = fileServer.openStream(fileLink, elemId);
 		} else {
 			throw new IllegalArgumentException("File link is neither in local nor remote state: " + fileLink);
 		}
 
 		// NOTE: Access to immediate input stream is buffered.
 		if (progressListener != null) {
-			loadSize(entity, fileLink);
+			loadSize(elemId, fileLink);
 			// NOTE: Progress updates immediately triggered by the stream
 			// consumer.
 			return new TracedInputStream(sourceStream, progressListener, fileLink.getSize());
 		}
-
 		return sourceStream;
 	}
 
@@ -239,32 +257,33 @@
 	 */
 	@Override
 	public void loadSize(Entity entity, FileLink fileLink) throws IOException {
+		loadSize(toElemID(entity), fileLink);
+	}
+
+	private void loadSize(ElemId elemId, FileLink fileLink) throws IOException {
 		if (fileLink.getSize() > -1) {
 			// file size is already known
 			return;
-		} else if (fileLink.isLocal()) {
-			fileLink.setFileSize(Files.size(fileLink.getLocalPath()));
 		} else if (fileLink.isRemote()) {
-			fileLink.setFileSize(fileServer.loadSize(fileLink, toElemID(entity)));
+			fileLink.setFileSize(fileServer.loadSize(fileLink, elemId));
 		} else {
 			throw new IllegalArgumentException("File link is neither in local nor remote state: " + fileLink);
 		}
 	}
 
 	/**
-	 * Sequential 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 entity           Used for security checks.
-	 * @param fileLinks        Collection of {@code FileLink}s to upload.
-	 * @param progressListener The progress listener.
-	 * @throws IOException Thrown if unable to upload files.
+	 * {@inheritDoc}
 	 */
+	@Override
 	public void uploadSequential(Entity entity, Collection<FileLink> fileLinks, ProgressListener progressListener)
 			throws IOException {
-		Map<Path, List<FileLink>> groups = fileLinks.stream().filter(FileLink::isLocal)
-				.collect(Collectors.groupingBy(FileLink::getLocalPath));
+		uploadSequential(toElemID(entity), fileLinks, progressListener);
+	}
+
+	private void uploadSequential(ElemId elemId, Collection<FileLink> fileLinks, ProgressListener progressListener)
+			throws IOException {
+		Map<InputStream, List<FileLink>> groups = fileLinks.stream().filter(FileLink::isLocal)
+				.collect(Collectors.groupingBy(FileLink::getLocalStream));
 
 		long totalSize = groups.values().stream().map(l -> l.get(0)).mapToLong(FileLink::getSize).sum();
 		final AtomicLong transferred = new AtomicLong();
@@ -274,7 +293,7 @@
 		for (List<FileLink> group : groups.values()) {
 			FileLink fileLink = group.get(0);
 
-			upload(entity, fileLink, (b, p) -> {
+			upload(elemId, fileLink, (b, p) -> {
 				double tranferredBytes = transferred.addAndGet(b);
 				if (progressListener != null) {
 					progressListener.progress(b, (float) (tranferredBytes / totalSize));
@@ -289,19 +308,18 @@
 	}
 
 	/**
-	 * 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 entity           Used for security checks.
-	 * @param fileLinks        Collection of {@code FileLink}s to upload.
-	 * @param progressListener The progress listener.
-	 * @throws IOException Thrown if unable to upload files.
+	 * {@inheritDoc}
 	 */
+	@Override
 	public void uploadParallel(Entity entity, Collection<FileLink> fileLinks, ProgressListener progressListener)
 			throws IOException {
-		Map<Path, List<FileLink>> groups = fileLinks.stream().filter(FileLink::isLocal)
-				.collect(Collectors.groupingBy(FileLink::getLocalPath));
+		uploadParallel(toElemID(entity), fileLinks, progressListener);
+	}
+
+	private void uploadParallel(ElemId elemId, Collection<FileLink> fileLinks, ProgressListener progressListener)
+			throws IOException {
+		Map<InputStream, List<FileLink>> groups = fileLinks.stream().filter(FileLink::isLocal)
+				.collect(Collectors.groupingBy(FileLink::getLocalStream));
 
 		long totalSize = groups.values().stream().map(l -> l.get(0)).mapToLong(FileLink::getSize).sum();
 		final AtomicLong transferred = new AtomicLong();
@@ -310,7 +328,7 @@
 			downloadTasks.add(() -> {
 				FileLink fileLink = group.get(0);
 
-				upload(entity, fileLink, (b, p) -> {
+				upload(elemId, fileLink, (b, p) -> {
 					double tranferredBytes = transferred.addAndGet(b);
 					if (progressListener != null) {
 						progressListener.progress(b, (float) (tranferredBytes / totalSize));
@@ -352,31 +370,35 @@
 	}
 
 	/**
-	 * Deletes given {@link FileLink}s form the remote storage.
-	 *
-	 * @param entity    Used for security checks.
-	 * @param fileLinks Collection of {@code FileLink}s to delete.
+	 * {@inheritDoc}
 	 */
+	@Override
 	public void delete(Entity entity, Collection<FileLink> fileLinks) {
+		delete(toElemID(entity), fileLinks);
+	}
+
+	private void delete(ElemId elemId, Collection<FileLink> fileLinks) {
 		fileLinks.stream().filter(FileLink::isRemote)
 				.collect(groupingBy(FileLink::getRemotePath, reducing((fl1, fl2) -> fl1))).values().stream()
-				.filter(Optional::isPresent).map(Optional::get).forEach(fl -> delete(entity, fl));
+				.filter(Optional::isPresent).map(Optional::get).forEach(fl -> delete(elemId, fl));
 	}
 
 	/**
-	 * Deletes given {@link FileLink} form the remote storage.
-	 *
-	 * @param entity   Used for security checks.
-	 * @param fileLink The {@code FileLink}s to delete.
+	 * {@inheritDoc}
 	 */
+	@Override
 	public void delete(Entity entity, FileLink fileLink) {
+		delete(toElemID(entity), fileLink);
+	}
+
+	private void delete(ElemId elemId, FileLink fileLink) {
 		if (!fileLink.isRemote()) {
 			// nothing to do
 			return;
 		}
 
 		try {
-			fileServer.delete(fileLink, toElemID(entity));
+			fileServer.delete(fileLink, elemId);
 			LOGGER.debug("File '{}' sucessfully deleted.", fileLink.getRemotePath());
 		} catch (IOException e) {
 			LOGGER.warn("Failed to delete remote file.", e);
@@ -386,17 +408,17 @@
 	// ======================================================================
 	// Private methods
 	// ======================================================================
-
+	
 	/**
 	 * Uploads given {@link FileLink}. The upload progress may be traced with a
 	 * progress listener.
 	 *
-	 * @param entity           Used for security checks.
+	 * @param elemId           Used for security checks.
 	 * @param fileLink         The {@code FileLink} to upload.
 	 * @param progressListener The progress listener.
 	 * @throws IOException Thrown if unable to upload file.
 	 */
-	private void upload(Entity entity, FileLink fileLink, ProgressListener progressListener) throws IOException {
+	private void upload(ElemId elemId, FileLink fileLink, ProgressListener progressListener) throws IOException {
 		if (fileLink.isRemote()) {
 			// nothing to do
 			return;
@@ -404,16 +426,15 @@
 			throw new IllegalArgumentException("File link does not have a local path.");
 		}
 
-		InputStream sourceStream = Files.newInputStream(fileLink.getLocalPath());
+		InputStream sourceStream = fileLink.getLocalStream();
 		if (progressListener != null) {
 			sourceStream = new TracedInputStream(sourceStream, progressListener, fileLink.getSize());
 		}
 
-		Path absolutePath = fileLink.getLocalPath().toAbsolutePath();
-		LOGGER.debug("Starting upload of file '{}'.", absolutePath);
+		LOGGER.debug("Starting upload of file '{}'.", fileLink.getFileName());
 		LocalTime start = LocalTime.now();
-		fileServer.uploadStream(sourceStream, fileLink, toElemID(entity));
-		LOGGER.debug("File '{}' successfully uploaded in {} to '{}'.", absolutePath,
+		fileServer.uploadStream(sourceStream, fileLink, elemId);
+		LOGGER.debug("File '{}' successfully uploaded in {} to '{}'.", fileLink.getFileName(),
 				Duration.between(start, LocalTime.now()), fileLink.getRemotePath());
 	}
 
@@ -437,11 +458,11 @@
 	 * @return The total download size is returned.
 	 * @throws IOException Thrown if unable to load the file size.
 	 */
-	private long calculateDownloadSize(Entity entity, Map<String, List<FileLink>> groups) throws IOException {
+	private long calculateDownloadSize(ElemId elemId, Map<String, List<FileLink>> groups) throws IOException {
 		List<FileLink> links = groups.values().stream().map(l -> l.get(0)).collect(Collectors.toList());
 		long totalSize = 0;
 		for (FileLink fileLink : links) {
-			loadSize(entity, fileLink);
+			loadSize(elemId, fileLink);
 			// overflow may occur in case of total size exceeds 9223 PB!
 			totalSize = Math.addExact(totalSize, fileLink.getSize());
 		}
diff --git a/src/main/java/org/eclipse/mdm/api/odsadapter/lookup/config/DefaultEntityConfigRepositoryLoader.java b/src/main/java/org/eclipse/mdm/api/odsadapter/lookup/config/DefaultEntityConfigRepositoryLoader.java
index a56d01f..0b6e839 100644
--- a/src/main/java/org/eclipse/mdm/api/odsadapter/lookup/config/DefaultEntityConfigRepositoryLoader.java
+++ b/src/main/java/org/eclipse/mdm/api/odsadapter/lookup/config/DefaultEntityConfigRepositoryLoader.java
@@ -1,5 +1,5 @@
 /********************************************************************************
- * Copyright (c) 2015-2019 Contributors to the Eclipse Foundation
+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -41,11 +41,15 @@
 import org.eclipse.mdm.api.dflt.model.CatalogSensor;
 import org.eclipse.mdm.api.dflt.model.Classification;
 import org.eclipse.mdm.api.dflt.model.Domain;
+import org.eclipse.mdm.api.dflt.model.ExtSystem;
+import org.eclipse.mdm.api.dflt.model.ExtSystemAttribute;
+import org.eclipse.mdm.api.dflt.model.MDMAttribute;
 import org.eclipse.mdm.api.dflt.model.Pool;
 import org.eclipse.mdm.api.dflt.model.Project;
 import org.eclipse.mdm.api.dflt.model.ProjectDomain;
 import org.eclipse.mdm.api.dflt.model.Role;
 import org.eclipse.mdm.api.dflt.model.Status;
+import org.eclipse.mdm.api.dflt.model.SystemParameter;
 import org.eclipse.mdm.api.dflt.model.TemplateAttribute;
 import org.eclipse.mdm.api.dflt.model.TemplateComponent;
 import org.eclipse.mdm.api.dflt.model.TemplateRoot;
@@ -53,6 +57,7 @@
 import org.eclipse.mdm.api.dflt.model.TemplateTest;
 import org.eclipse.mdm.api.dflt.model.TemplateTestStep;
 import org.eclipse.mdm.api.dflt.model.TemplateTestStepUsage;
+import org.eclipse.mdm.api.dflt.model.UserParameter;
 import org.eclipse.mdm.api.dflt.model.ValueList;
 import org.eclipse.mdm.api.dflt.model.ValueListValue;
 import org.eclipse.mdm.api.dflt.model.Versionable;
@@ -208,6 +213,21 @@
 		registerContextRoot(modelManager, ContextType.TESTSEQUENCE);
 		registerContextRoot(modelManager, ContextType.TESTEQUIPMENT);
 
+		entityConfigRepository
+				.register(create(modelManager, new Key<>(SystemParameter.class), "SystemParameter", false));
+		entityConfigRepository.register(create(modelManager, new Key<>(UserParameter.class), "UserParameter", false));
+
+		// MDMAttr for external systems
+		EntityConfig<MDMAttribute> mdmAttrConfig = create(modelManager, new Key<>(MDMAttribute.class), "MDMAttr", true);
+		entityConfigRepository.register(mdmAttrConfig);
+		// ExtSystemAttr for external systems
+		EntityConfig<ExtSystemAttribute> extSystemAttrConfig = create(modelManager, new Key<>(ExtSystemAttribute.class), "ExtSystemAttr", true);
+		extSystemAttrConfig.addChild(mdmAttrConfig);
+		entityConfigRepository.register(extSystemAttrConfig);
+		// The external systems themselves
+		EntityConfig<ExtSystem> extSystemConfig = create(modelManager, new Key<>(ExtSystem.class), "ExtSystem", false);
+		entityConfigRepository.register(extSystemConfig);
+
 		LOGGER.debug("Entity configurations loaded in {} ms.", System.currentTimeMillis() - start);
 		return entityConfigRepository;
 	}
diff --git a/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/InsertStatement.java b/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/InsertStatement.java
index 445e44a..2a54ac5 100644
--- a/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/InsertStatement.java
+++ b/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/InsertStatement.java
@@ -1,5 +1,5 @@
 /********************************************************************************
- * Copyright (c) 2015-2019 Contributors to the Eclipse Foundation
+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -58,10 +58,6 @@
  */
 final class InsertStatement extends BaseStatement {
 
-	// ======================================================================
-	// Class variables
-	// ======================================================================
-
 	private static final Logger LOGGER = LoggerFactory.getLogger(InsertStatement.class);
 
 	private final Map<Class<? extends Entity>, List<Entity>> childrenMap = new HashMap<>();
@@ -73,10 +69,6 @@
 	private final Map<String, SortIndexTestSteps> sortIndexTestSteps = new HashMap<>();
 	private boolean loadSortIndex;
 
-	// ======================================================================
-	// Constructors
-	// ======================================================================
-
 	/**
 	 * Constructor.
 	 *
@@ -89,10 +81,6 @@
 		loadSortIndex = getModelManager().getEntityType(TestStep.class).equals(getEntityType());
 	}
 
-	// ======================================================================
-	// Public methods
-	// ======================================================================
-
 	/**
 	 * {@inheritDoc}
 	 */
@@ -115,10 +103,6 @@
 		execute();
 	}
 
-	// ======================================================================
-	// Private methods
-	// ======================================================================
-
 	/**
 	 * Uploads new {@link FileLink}s, adjusts sort indices for new {@link TestStep}
 	 * entities and writes collected insertion data at once. Once new entities are
@@ -266,8 +250,7 @@
 		Filter filter = Filter.idsOnly(parentRelation, sortIndexTestSteps.keySet());
 		for (Result result : query.fetch(filter)) {
 			Record record = result.getRecord(testStep);
-			int sortIndex = (Integer) record.getValues()
-					.get(ODSConverter.getColumnName(attrSortIndex, Aggregation.MAXIMUM)).extract();
+			int sortIndex = result.getValue(attrSortIndex, Aggregation.MAXIMUM).extract();
 			sortIndexTestSteps.remove(record.getID(parentRelation).get()).setIndices(sortIndex + 1);
 		}
 
@@ -275,25 +258,13 @@
 		sortIndexTestSteps.values().forEach(tss -> tss.setIndices(0));
 	}
 
-	// ======================================================================
-	// Inner classes
-	// ======================================================================
-
 	/**
 	 * Utility class to write missing sort index of new {@link TestStep}s.
 	 */
 	private static final class SortIndexTestSteps {
 
-		// ======================================================================
-		// Instance variables
-		// ======================================================================
-
 		private List<Core> testStepCores = new ArrayList<>();
 
-		// ======================================================================
-		// Private methods
-		// ======================================================================
-
 		/**
 		 * Assigns sort indices to {@link Core}s starting at given index.
 		 *
diff --git a/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/UploadService.java b/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/UploadService.java
index 26b9d78..5748049 100644
--- a/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/UploadService.java
+++ b/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/UploadService.java
@@ -1,5 +1,5 @@
 /********************************************************************************
- * Copyright (c) 2015-2019 Contributors to the Eclipse Foundation
+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -15,6 +15,7 @@
 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;
@@ -52,7 +53,7 @@
 
 	private final Map<TemplateAttribute, Value> templateAttributeFileLinks = new HashMap<>();
 	private final List<FileLink> uploaded = new ArrayList<>();
-	private final Map<Path, String> remotePaths = new HashMap<>();
+	private final Map<InputStream, String> remotePaths = new HashMap<>();
 	private final List<FileLink> toRemove = new ArrayList<>();
 
 	private final CORBAFileService fileService;
@@ -142,7 +143,7 @@
 			fileService.uploadParallel(entity, filtered, progressListener);
 		} finally {
 			filtered.stream().filter(FileLink::isRemote).forEach(fl -> {
-				remotePaths.put(fl.getLocalPath(), fl.getRemotePath());
+				remotePaths.put(fl.getLocalStream(), fl.getRemotePath());
 				uploaded.add(fl);
 			});
 		}
@@ -189,7 +190,7 @@
 	private List<FileLink> retainForUpload(Collection<FileLink> fileLinks) {
 		List<FileLink> filtered = new ArrayList<>(fileLinks);
 		for (FileLink fileLink : fileLinks) {
-			String remotePath = remotePaths.get(fileLink.getLocalPath());
+			String remotePath = remotePaths.get(fileLink.getLocalStream());
 			if (remotePath != null && !remotePath.isEmpty()) {
 				fileLink.setRemotePath(remotePath);
 				filtered.remove(fileLink);