Merge branch 'dev'

Change-Id: I8083ab4bdb43a542d6bc5bb764dd7ca00f7a6051
diff --git a/build.gradle b/build.gradle
index 1be97fb..60e39f3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -15,7 +15,7 @@
 
 description = 'MDM API - Base Model'
 group = 'org.eclipse.mdm'
-version = '5.1.0'
+version = '5.2.0M1-SNAPSHOT'
 
 apply plugin: 'java'
 apply plugin: 'maven'
diff --git a/src/main/java/org/eclipse/mdm/api/base/file/FileService.java b/src/main/java/org/eclipse/mdm/api/base/file/FileService.java
index b58e225..9a552cc 100644
--- a/src/main/java/org/eclipse/mdm/api/base/file/FileService.java
+++ b/src/main/java/org/eclipse/mdm/api/base/file/FileService.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.
@@ -88,6 +88,7 @@
 	void downloadParallel(Entity entity, Path target, Collection<FileLink> fileLinks, ProgressListener progressListener)
 			throws IOException;
 
+
 	/**
 	 * Downloads given {@link FileLink} into given target {@code Path}.
 	 *
@@ -182,5 +183,57 @@
 		void progress(int bytes, float percent);
 
 	}
+	
+	/**
+	 * 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.
+	 */
+	public void uploadSequential(Entity entity, Collection<FileLink> fileLinks, ProgressListener progressListener) throws IOException;
+
+	/**
+	 * 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.
+	 */
+	public void uploadParallel(Entity entity, Collection<FileLink> fileLinks, ProgressListener progressListener)
+			throws IOException;
+
+	/**
+	 * Deletes given {@link FileLink}s form the remote storage.
+	 *
+	 * @param entity    Used for security checks.
+	 * @param fileLinks Collection of {@code FileLink}s to delete.
+	 */
+	public void delete(Entity entity, Collection<FileLink> fileLinks);
+
+	/**
+	 * Deletes given {@link FileLink} form the remote storage.
+	 *
+	 * @param entity   Used for security checks.
+	 * @param fileLink The {@code FileLink}s to delete.
+	 */
+	public void delete(Entity entity, FileLink fileLink);
+
+
+
+
+
+
+
+
+
+
+
 
 }
diff --git a/src/main/java/org/eclipse/mdm/api/base/model/FileLink.java b/src/main/java/org/eclipse/mdm/api/base/model/FileLink.java
index a2e231a..f558b54 100644
--- a/src/main/java/org/eclipse/mdm/api/base/model/FileLink.java
+++ b/src/main/java/org/eclipse/mdm/api/base/model/FileLink.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.base.model;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
 import java.net.URLDecoder;
 import java.nio.charset.StandardCharsets;
@@ -40,7 +41,8 @@
 	private MimeType mimeType;
 	private String description;
 
-	private Path localPath;
+	private InputStream localStream;
+	private String localFileName;
 
 	private long size = -1;
 
@@ -57,7 +59,7 @@
 		remotePath = fileLink.remotePath;
 		mimeType = fileLink.mimeType;
 		description = fileLink.description;
-		localPath = fileLink.localPath;
+		localStream = fileLink.localStream;
 		size = fileLink.size;
 		state = fileLink.state;
 	}
@@ -81,16 +83,12 @@
 	 * @param localPath The local {@link Path}.
 	 * @throws IOException Thrown in case of errors.
 	 */
-	private FileLink(Path localPath) throws IOException {
-		this.localPath = localPath;
-		String type = Files.probeContentType(localPath);
-		mimeType = new MimeType(type == null ? "application/octet-stream" : type);
-		size = Files.size(localPath);
-		Path fileNamePath = localPath.getFileName();
-		if (fileNamePath != null) {
-			description = fileNamePath.toString();
-		}
-
+	private FileLink(InputStream localStream, String name, long size, MimeType mimeType, String description) throws IOException {
+		this.localStream = localStream;
+		this.mimeType = mimeType == null ? new MimeType("application/octet-stream") : mimeType;
+		this.size = size;
+		this.localFileName = name;
+		this.description = description;
 		state = State.LOCAL;
 	}
 
@@ -120,6 +118,10 @@
 	 * @throws IOException Thrown if unable to access file with given {@code
 	 * 		Path}       .
 	 */
+	public static FileLink newLocal(InputStream localStream, String name, long size, MimeType mimeType, String description) throws IOException {
+		return new FileLink(localStream, name, size, mimeType, description);
+	}
+	
 	public static FileLink newLocal(Path localPath) throws IOException {
 		if (Files.isDirectory(localPath)) {
 			throw new IllegalArgumentException("Local path is a directory.");
@@ -128,8 +130,13 @@
 		} else if (!Files.isReadable(localPath)) {
 			throw new IllegalArgumentException("Local path is not readable.");
 		}
-
-		return new FileLink(localPath);
+		
+		InputStream localStream = Files.newInputStream(localPath);
+		long size = Files.size(localPath);
+		String name = localPath.getFileName().toString();
+		String mimeType = Files.probeContentType(localPath);
+		MimeType mimeT = mimeType == null ? null : new MimeType(mimeType);
+		return new FileLink(localStream, name, size, mimeT, name);
 	}
 
 	/**
@@ -140,8 +147,9 @@
 	 */
 	public String getFileName() {
 		Path fileNamePath = null;
+		String fileName = null;
 		if (isLocal()) {
-			fileNamePath = getLocalPath().getFileName();
+			fileName = this.localFileName;
 		} else if (isRemote()) {
 			try {
 				// on Windows, Paths.get() cannot handle file urls in the form
@@ -151,13 +159,14 @@
 			} catch (UnsupportedEncodingException e) {
 				throw new IllegalStateException("Unable to decode remote path due to: " + e.getMessage(), e);
 			}
+			
+			if (fileNamePath == null) {
+				throw new IllegalStateException("File name is unknown.");
+			}
+			fileName = fileNamePath.toString();
 		}
 
-		if (fileNamePath == null) {
-			throw new IllegalStateException("File name is unknown.");
-		}
-
-		return fileNamePath.toString();
+		return fileName;
 	}
 
 	/**
@@ -175,37 +184,37 @@
 	 * @return Returns {@code true} if a local {@code Path} is available.
 	 */
 	public boolean isLocal() {
-		return localPath != null;
+		return localStream != null;
 	}
 
 	/**
-	 * Returns the local {@link Path} to the linked file. Calling this method is
+	 * Returns the local {@link InputStream} to the linked file. Calling this method is
 	 * only allowed if calling {@link #isLocal()} returns {@code
 	 * true}.
 	 *
 	 * @return The local {@code Path} to the linked file is returned.
 	 */
-	public Path getLocalPath() {
+	public InputStream getLocalStream() {
 		if (isLocal()) {
-			return localPath;
+			return localStream;
 		}
 
 		throw new IllegalStateException("Local path is not available.");
 	}
 
 	/**
-	 * This method is called by API providers to set the local {@link Path} once the
+	 * This method is called by API providers to set the local {@link InputStream} once the
 	 * remote file was downloaded.
 	 *
-	 * @param localPath The local {@code Path} of the downloaded file.
+	 * @param localPath The local {@code InputStream} of the downloaded file.
 	 * @throws IllegalStateException Thrown if this file link is 'LOCAL'.
 	 */
-	public void setLocalPath(Path localPath) {
+	public void setLocalStream(InputStream localStream) {
 		if (State.LOCAL == state) {
 			throw new IllegalStateException("It is not allowed to replace an existing local path.");
 		}
 
-		this.localPath = localPath;
+		this.localStream = localStream;
 	}
 
 	/**
@@ -301,7 +310,7 @@
 			if (!isLocal()) {
 				return "".hashCode();
 			}
-			return getLocalPath().hashCode();
+			return getLocalStream().hashCode();
 		}
 		if (!isRemote()) {
 			return "".hashCode();
@@ -321,7 +330,7 @@
 					if (!isLocal()) {
 						return !other.isLocal();
 					}
-					return getLocalPath().equals(other.getLocalPath());
+					return getLocalStream().equals(other.getLocalStream());
 				}
 				if (!isRemote()) {
 					return !other.isRemote();
@@ -345,7 +354,7 @@
 		}
 
 		if (isLocal()) {
-			sb.append(", LocalPath = ").append(getLocalPath().toAbsolutePath());
+			sb.append(", LocalPath = ").append(getLocalStream());
 		}
 
 		if (isRemote()) {
@@ -393,7 +402,7 @@
 	 *         {@code Path} which are equal.
 	 */
 	private static boolean isLocalPathEqual(FileLink o1, FileLink o2) {
-		return o1.isLocal() && o2.isLocal() && o1.getLocalPath().equals(o2.getLocalPath());
+		return o1.isLocal() && o2.isLocal() && o1.getLocalStream().equals(o2.getLocalStream());
 	}
 
 	/**