Repository: apache/streampipes
Pull request #2443 (2024-01-29 10:05:49)

#2144 Enhance Static Properties by UI validation V2
<!--
  ~ Licensed to the Apache Software Foundation (ASF) under one or more
  ~ contributor license agreements.  See the NOTICE file distributed with
  ~ this work for additional information regarding copyright ownership.
  ~ The ASF licenses this file to You under the Apache License, Version 2.0
  ~ (the "License"); you may not use this file except in compliance with
  ~ the License.  You may obtain a copy of the License at
  ~
  ~    http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  ~
  -->
  
  <!--
Thanks for contributing! Here are some tips you can follow to help us incorporate your contribution quickly and easily:
1. If this is your first time, please read our contributor guidelines:
    - https://streampipes.apache.org/getinvolved.html
    - https://cwiki.apache.org/confluence/display/STREAMPIPES/Getting+Started
2. Make sure the PR title is formatted like: `[#<GitHub issue id>] PR title ...`
3. If the PR is unfinished, add '[WIP]' in your PR title, e.g., `[WIP][#<GitHub issue id>] PR title ...`.
4. Please write your PR title to summarize what this PR proposes/fixes.
5. Link the PR to the corresponding GitHub issue (if present) in the `Development` section in the right menu bar. 
6. Be sure to keep the PR description updated to reflect all changes.
7. If possible, provide a concise example to reproduce the issue for a faster review.
8. Make sure tests pass via `mvn clean install`.
9. (Optional) If the contribution is large, please file an Apache ICLA
    - http://apache.org/licenses/icla.pdf
-->

### Purpose
<!--
Please clarify what changes you are proposing and describe how those changes will address the issue.
Furthermore, describe potential consequences the changes might have.
-->

### Remarks
<!--
Is there anything left we need to pay attention on?
Are there some references that might be important? E.g. links to Confluence, or discussions
on the mailing list or GitHub.
-->
PR introduces (a) breaking change(s): <yes/no>

PR introduces (a) deprecation(s): <yes/no>


tenthe: @IsaakKrut, there seems to be an issue with the unit test. Do you have an idea what is going wrong. 
I tried to mock the extractor but it did not work.
Cheers,
Philipp
IsaakKrut: @tenthe The 3 values(serverAddressValue, hostValue, portValue) from the extractor you are using in OpcUaAdapterMigrationV2 have to be present in the extractor. Without them I'm getting NullPointerException in the AbstractParameterExtractor.singleValueParameter method call. Could we instantiate an extractor with those properties inside the test? And if so, are they gonna be available during actual migration as well?
tenthe: @IsaakKrut, I tested the migration and there I did not get any exceptions.
I only geth the NullPointer when running the test. Maybe we can mock the extractor return a value when the function is called with the parameters (serverAddressValue, hostValue, portValue).
Do you think this will work?
IsaakKrut: @tenthe All these 3 fields (serverAddressValue, hostValue, portValue) are going to have a value during migration, right? If so we can add the defaultValue parameter for host and port for version 2 config in OpcUaAdapterVersionedConfig on lines 138, 140. And create an extractor inside the test with the same default values. Ran the test with those changes and it passed
![image](https://github.com/apache/streampipes/assets/25233902/77452e53-d84b-4e14-a822-d40cdcb8ca98)

![image](https://github.com/apache/streampipes/assets/25233902/8cde2fb8-2acc-4da1-8251-ae16916af801)

tenthe: Ok, great. Can you add your changes. Then we can close this PR.
IsaakKrut: Created a separate PR to update this branch since I don't have push access

#2454
github-actions[bot]: Hello there :wave:<br> <br> We noticed that it's been some time since activity occurred on your pull request :thinking:. In order to keep things moving forward, we're marking this PR as _stale_ and giving you 7 days to respond before it's automatically closed :alarm_clock:.<br> <br> Please take a moment to review your pull request and make any necessary updates or changes :man_technologist:. If you need more time or have any questions, please don't hesitate to let us know :speech_balloon:.<br> <br> Thank you for your contributions to our project, and we look forward to hearing back from you soon :pray:.
IsaakKrut: Hi @tenthe, did you have a chance to look at PR #2454?
tenthe: Hi @IsaakKrut,
sorry for the late reply. I'll have a look at it in the upcominig days.
Cheers,
Philipp

Patch:
diff --git a/installer/k8s/templates/core/extensions-all-iiot-deployment.yaml b/installer/k8s/templates/core/extensions-all-iiot-deployment.yaml
index cecc9a18f7..a008fcf43a 100644
--- a/installer/k8s/templates/core/extensions-all-iiot-deployment.yaml
+++ b/installer/k8s/templates/core/extensions-all-iiot-deployment.yaml
@@ -48,6 +48,8 @@ spec:
                 secretKeyRef:
                   name: "{{ .Values.streampipes.auth.secretName }}"
                   key: sp-initial-client-secret
+            - name: SP_COUCHDB_USER
+              value: "{{ .Values.external.couchdb.user }}"
             - name: SP_COUCHDB_PASSWORD
               valueFrom:
                 secretKeyRef:
diff --git a/streampipes-client-python/poetry.lock b/streampipes-client-python/poetry.lock
index 1e25417cef..38717c34a4 100644
--- a/streampipes-client-python/poetry.lock
+++ b/streampipes-client-python/poetry.lock
@@ -46,13 +46,13 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte
 
 [[package]]
 name = "autoflake"
-version = "2.2.0"
+version = "2.3.0"
 description = "Removes unused imports and unused variables"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "autoflake-2.2.0-py3-none-any.whl", hash = "sha256:de409b009a34c1c2a7cc2aae84c4c05047f9773594317c6a6968bd497600d4a0"},
-    {file = "autoflake-2.2.0.tar.gz", hash = "sha256:62e1f74a0fdad898a96fee6f99fe8241af90ad99c7110c884b35855778412251"},
+    {file = "autoflake-2.3.0-py3-none-any.whl", hash = "sha256:79a51eb8c0744759d2efe052455ab20aa6a314763510c3fd897499a402126327"},
+    {file = "autoflake-2.3.0.tar.gz", hash = "sha256:8c2011fa34701b9d7dcf05b9873bc4859d4fce4e62dfea90dffefd1576f5f01d"},
 ]
 
 [package.dependencies]
@@ -1560,12 +1560,12 @@ files = [
 
 [[package]]
 name = "nats-py"
-version = "2.6.0"
+version = "2.7.2"
 description = "NATS client for Python"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "nats-py-2.6.0.tar.gz", hash = "sha256:7c535fb62d8dc9c743b4afb42775d9f36924f6a1ca1486efdd2e29f1e4ee30b8"},
+    {file = "nats-py-2.7.2.tar.gz", hash = "sha256:0c97b4a57bed0ef1ff9ae6c19bc115ec7ca8ede5ab3e001fd00a377056a547cf"},
 ]
 
 [package.extras]
@@ -3475,4 +3475,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
 [metadata]
 lock-version = "2.0"
 python-versions = ">3.8.1,<3.12"
-content-hash = "dfcef4e8bbfa7cf20e4947f8bdea6ceaa57468af770fd6faaf30e1b31ee4c838"
+content-hash = "1239f0b1fd707d5f430ae28b8ce9fa8976584282dc58424e97e88367cbb94c5d"
diff --git a/streampipes-client-python/pyproject.toml b/streampipes-client-python/pyproject.toml
index 001c0e031c..92abceb00d 100644
--- a/streampipes-client-python/pyproject.toml
+++ b/streampipes-client-python/pyproject.toml
@@ -65,7 +65,7 @@ typing-extensions = "~4, >=4.5"
 optional = true
 
 [tool.poetry.group.dev.dependencies]
-autoflake = "2.2.0"
+autoflake = "2.3.0"
 black = "23.12.0"
 blacken-docs = "1.16.0"
 flake8 = "6.1.0"
diff --git a/streampipes-client/src/main/java/org/apache/streampipes/client/api/DataStreamApi.java b/streampipes-client/src/main/java/org/apache/streampipes/client/api/DataStreamApi.java
index 950f53a9af..d992ce6d02 100644
--- a/streampipes-client/src/main/java/org/apache/streampipes/client/api/DataStreamApi.java
+++ b/streampipes-client/src/main/java/org/apache/streampipes/client/api/DataStreamApi.java
@@ -29,11 +29,14 @@ import org.apache.streampipes.model.SpDataStream;
 import org.apache.streampipes.model.message.Message;
 
 import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import java.util.List;
 import java.util.Optional;
 
 public class DataStreamApi extends AbstractTypedClientApi<SpDataStream> implements IDataStreamApi {
 
+  private static final String STREAMS_PATH = "streams";
+
   public DataStreamApi(StreamPipesClientConfig clientConfig) {
     super(clientConfig, SpDataStream.class);
   }
@@ -41,7 +44,7 @@ public class DataStreamApi extends AbstractTypedClientApi<SpDataStream> implemen
   @Override
   public Optional<SpDataStream> get(String streamId) {
     return getSingle(StreamPipesApiPath.fromBaseApiPath()
-        .addToPath("streams").addToPath(streamId));
+        .addToPath(STREAMS_PATH).addToPath(streamId));
   }
 
   /**
@@ -61,7 +64,7 @@ public class DataStreamApi extends AbstractTypedClientApi<SpDataStream> implemen
    */
   @Override
   public void create(SpDataStream stream) {
-    post(StreamPipesApiPath.fromBaseApiPath().addToPath("streams"), stream);
+    post(StreamPipesApiPath.fromBaseApiPath().addToPath(STREAMS_PATH), stream);
   }
 
   /**
@@ -71,7 +74,7 @@ public class DataStreamApi extends AbstractTypedClientApi<SpDataStream> implemen
    */
   @Override
   public void delete(String streamId) {
-    delete(getBaseResourcePath().addToPath(URLEncoder.encode(streamId)), Message.class);
+    delete(getBaseResourcePath().addToPath(URLEncoder.encode(streamId, StandardCharsets.UTF_8)), Message.class);
   }
 
   @Override
@@ -113,6 +116,6 @@ public class DataStreamApi extends AbstractTypedClientApi<SpDataStream> implemen
   @Override
   protected StreamPipesApiPath getBaseResourcePath() {
     return StreamPipesApiPath.fromBaseApiPath()
-        .addToPath("streams");
+        .addToPath(STREAMS_PATH);
   }
 }
diff --git a/streampipes-commons/src/main/java/org/apache/streampipes/commons/zip/ZipFileGenerator.java b/streampipes-commons/src/main/java/org/apache/streampipes/commons/zip/ZipFileGenerator.java
deleted file mode 100644
index d31a135752..0000000000
--- a/streampipes-commons/src/main/java/org/apache/streampipes/commons/zip/ZipFileGenerator.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.apache.streampipes.commons.zip;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
-
-public class ZipFileGenerator {
-
-  private List<String> fileList;
-
-  private File inputDirectory;
-  private File outputFile;
-
-  public ZipFileGenerator(File inputDirectory, File outputFile) {
-    this(inputDirectory);
-    this.outputFile = outputFile;
-  }
-
-  public ZipFileGenerator(File inputDirectory) {
-    this.fileList = new ArrayList<>();
-    this.inputDirectory = inputDirectory;
-    generateFileList(inputDirectory);
-  }
-
-  public byte[] makeZipToBytes() {
-    ByteArrayOutputStream bos = new ByteArrayOutputStream();
-    makeZip(bos);
-    return bos.toByteArray();
-  }
-
-  public void makeZipToFile() {
-    try {
-      FileOutputStream fos = new FileOutputStream(outputFile);
-      makeZip(fos);
-    } catch (FileNotFoundException e) {
-      e.printStackTrace();
-    }
-  }
-
-  public void makeZip(OutputStream outputStream) {
-    byte[] buffer = new byte[1024];
-    ZipOutputStream zos = null;
-    try {
-      zos = new ZipOutputStream(outputStream);
-
-      FileInputStream in = null;
-
-      for (String file : this.fileList) {
-        ZipEntry ze = new ZipEntry(file);
-        zos.putNextEntry(ze);
-        try {
-          in = new FileInputStream(inputDirectory + File.separator + file);
-          int len;
-          while ((len = in.read(buffer)) > 0) {
-            zos.write(buffer, 0, len);
-          }
-        } finally {
-          in.close();
-        }
-      }
-      zos.closeEntry();
-    } catch (IOException ex) {
-      ex.printStackTrace();
-    } finally {
-      try {
-        zos.close();
-      } catch (IOException e) {
-        e.printStackTrace();
-      }
-    }
-  }
-
-  private void generateFileList(File node) {
-
-    if (node.isFile()) {
-      fileList.add(generateZipEntry(node.toString()));
-    }
-
-    if (node.isDirectory()) {
-      String[] subNote = node.list();
-      for (String filename : subNote) {
-        generateFileList(new File(node, filename));
-      }
-    }
-  }
-
-  private String generateZipEntry(String file) {
-    return file.substring(inputDirectory.toString().length() + 1, file.length());
-  }
-
-}
diff --git a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/schema/MoveTransformationRule.java b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/schema/MoveTransformationRule.java
index 6bb5bdcd33..f6506f376c 100644
--- a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/schema/MoveTransformationRule.java
+++ b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/schema/MoveTransformationRule.java
@@ -52,7 +52,7 @@ public class MoveTransformationRule implements TransformationRule {
       event.put(key, movedObject.get(key));
       return event;
     } else if (keys.size() == 1) {
-      if (event.get(keys.get(0)) != null && event.get(keys.get(0)) instanceof HashMap) {
+      if (event.get(keys.get(0)) instanceof HashMap) {
         String movedObjectKey = movedObject.keySet().iterator().next();
         ((Map<String, Object>) event.get(keys.get(0))).put(movedObjectKey, movedObject.get(movedObjectKey));
       } else {
diff --git a/streampipes-data-explorer-commons/src/main/java/org/apache/streampipes/dataexplorer/commons/influx/InfluxStore.java b/streampipes-data-explorer-commons/src/main/java/org/apache/streampipes/dataexplorer/commons/influx/InfluxStore.java
index 0a107b1dd4..1c16009ffa 100644
--- a/streampipes-data-explorer-commons/src/main/java/org/apache/streampipes/dataexplorer/commons/influx/InfluxStore.java
+++ b/streampipes-data-explorer-commons/src/main/java/org/apache/streampipes/dataexplorer/commons/influx/InfluxStore.java
@@ -108,7 +108,7 @@ public class InfluxStore {
   private void logNullFields(Event event) {
     List<String> nullFields = allEventProperties
         .stream()
-        .filter(ep -> ep instanceof EventPropertyPrimitive)
+        .filter(EventPropertyPrimitive.class::isInstance)
         .filter(ep -> {
           var runtimeName = ep.getRuntimeName();
           var field = event.getOptionalFieldByRuntimeName(runtimeName);
diff --git a/streampipes-data-export/src/main/java/org/apache/streampipes/export/dataimport/PerformImportGenerator.java b/streampipes-data-export/src/main/java/org/apache/streampipes/export/dataimport/PerformImportGenerator.java
index d35b8dbf1c..f056e0fefb 100644
--- a/streampipes-data-export/src/main/java/org/apache/streampipes/export/dataimport/PerformImportGenerator.java
+++ b/streampipes-data-export/src/main/java/org/apache/streampipes/export/dataimport/PerformImportGenerator.java
@@ -135,8 +135,8 @@ public class PerformImportGenerator extends ImportGenerator<Void> {
     var fileMetadata = resolver.readDocument(document);
     resolver.writeDocument(document);
     byte[] file = zipContent.get(
-        fileMetadata.getInternalFilename().substring(0, fileMetadata.getInternalFilename().lastIndexOf(".")));
-    new FileHandler().storeFile(fileMetadata.getInternalFilename(), new ByteArrayInputStream(file));
+        fileMetadata.getFilename().substring(0, fileMetadata.getFilename().lastIndexOf(".")));
+    new FileHandler().storeFile(fileMetadata.getFilename(), new ByteArrayInputStream(file));
   }
 
   @Override
diff --git a/streampipes-data-export/src/main/java/org/apache/streampipes/export/dataimport/PreviewImportGenerator.java b/streampipes-data-export/src/main/java/org/apache/streampipes/export/dataimport/PreviewImportGenerator.java
index 57ff84f809..55d3acf43e 100644
--- a/streampipes-data-export/src/main/java/org/apache/streampipes/export/dataimport/PreviewImportGenerator.java
+++ b/streampipes-data-export/src/main/java/org/apache/streampipes/export/dataimport/PreviewImportGenerator.java
@@ -116,7 +116,7 @@ public class PreviewImportGenerator extends ImportGenerator<AssetExportConfigura
   protected void handleFile(String document,
                             String fileMetadataId,
                             Map<String, byte[]> zipContent) throws JsonProcessingException {
-    addExportItem(fileMetadataId, new FileResolver().readDocument(document).getOriginalFilename(),
+    addExportItem(fileMetadataId, new FileResolver().readDocument(document).getFilename(),
         importConfig::addFile);
   }
 
diff --git a/streampipes-data-export/src/main/java/org/apache/streampipes/export/generator/ExportPackageGenerator.java b/streampipes-data-export/src/main/java/org/apache/streampipes/export/generator/ExportPackageGenerator.java
index e2365930ce..fc3663929f 100644
--- a/streampipes-data-export/src/main/java/org/apache/streampipes/export/generator/ExportPackageGenerator.java
+++ b/streampipes-data-export/src/main/java/org/apache/streampipes/export/generator/ExportPackageGenerator.java
@@ -121,7 +121,7 @@ public class ExportPackageGenerator {
 
       config.getFiles().forEach(item -> {
         var fileResolver = new FileResolver();
-        String filename = fileResolver.findDocument(item.getResourceId()).getInternalFilename();
+        String filename = fileResolver.findDocument(item.getResourceId()).getFilename();
         addDoc(builder, item, new FileResolver(), manifest::addFile);
         try {
           builder.addBinary(filename, Files.readAllBytes(FileManager.getFile(filename).toPath()));
diff --git a/streampipes-data-export/src/main/java/org/apache/streampipes/export/resolver/FileResolver.java b/streampipes-data-export/src/main/java/org/apache/streampipes/export/resolver/FileResolver.java
index a1493c9865..d30d66936a 100644
--- a/streampipes-data-export/src/main/java/org/apache/streampipes/export/resolver/FileResolver.java
+++ b/streampipes-data-export/src/main/java/org/apache/streampipes/export/resolver/FileResolver.java
@@ -44,7 +44,7 @@ public class FileResolver extends AbstractResolver<FileMetadata> {
 
   @Override
   public ExportItem convert(FileMetadata document) {
-    return new ExportItem(document.getFileId(), document.getOriginalFilename(), true);
+    return new ExportItem(document.getFileId(), document.getFilename(), true);
   }
 
   @Override
diff --git a/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/locales/LabelGenerator.java b/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/locales/LabelGenerator.java
index a1be4db1d0..dafe5f2799 100644
--- a/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/locales/LabelGenerator.java
+++ b/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/locales/LabelGenerator.java
@@ -50,15 +50,25 @@ public class LabelGenerator<T extends NamedStreamPipesEntity> {
 
   private T desc;
 
-  public LabelGenerator(T desc) {
+  private boolean replaceTitles;
+
+  public LabelGenerator(T desc, boolean replaceTitles) {
     this.desc = desc;
+    this.replaceTitles = replaceTitles;
+  }
+
+  public LabelGenerator(T desc) {
+    this(desc, true);
   }
 
   public T generateLabels() throws IOException {
     if (existsLocalesFile()) {
       Properties props = laodResourceAndMakeProperties();
-      desc.setName(getTitle(props, desc.getAppId()));
-      desc.setDescription(getDescription(props, desc.getAppId()));
+
+      if (replaceTitles) {
+        desc.setName(getTitle(props, desc.getAppId()));
+        desc.setDescription(getDescription(props, desc.getAppId()));
+      }
 
       if (isAdapter()) {
         ((AdapterDescription) desc).getConfig()
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/pom.xml b/streampipes-extensions/streampipes-connect-adapters-iiot/pom.xml
index f2917f08b7..6abe50afd5 100644
--- a/streampipes-extensions/streampipes-connect-adapters-iiot/pom.xml
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/pom.xml
@@ -106,6 +106,11 @@
             <artifactId>junit</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/IIoTAdaptersExtensionModuleExport.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/IIoTAdaptersExtensionModuleExport.java
index 9714149241..49ff96c34f 100644
--- a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/IIoTAdaptersExtensionModuleExport.java
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/IIoTAdaptersExtensionModuleExport.java
@@ -19,6 +19,7 @@
 package org.apache.streampipes.connect.iiot;
 
 import org.apache.streampipes.connect.iiot.adapters.iolink.IfmAlMqttAdapter;
+import org.apache.streampipes.connect.iiot.adapters.migrations.RosBridgeAdapterMigrationV1;
 import org.apache.streampipes.connect.iiot.adapters.ros.RosBridgeAdapter;
 import org.apache.streampipes.connect.iiot.adapters.simulator.machine.MachineDataSimulatorAdapter;
 import org.apache.streampipes.connect.iiot.protocol.stream.FileReplayAdapter;
@@ -52,6 +53,8 @@ public class IIoTAdaptersExtensionModuleExport implements IExtensionModuleExport
 
   @Override
   public List<IModelMigrator<?, ?>> migrators() {
-    return Collections.emptyList();
+    return List.of(
+            new RosBridgeAdapterMigrationV1()
+    );
   }
 }
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/migrations/RosBridgeAdapterMigrationV1.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/migrations/RosBridgeAdapterMigrationV1.java
new file mode 100644
index 0000000000..3d41e7fbf5
--- /dev/null
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/migrations/RosBridgeAdapterMigrationV1.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.connect.iiot.adapters.migrations;
+
+import org.apache.streampipes.extensions.api.extractor.IStaticPropertyExtractor;
+import org.apache.streampipes.extensions.api.migration.IAdapterMigrator;
+import org.apache.streampipes.model.connect.adapter.AdapterDescription;
+import org.apache.streampipes.model.extensions.svcdiscovery.SpServiceTagPrefix;
+import org.apache.streampipes.model.migration.MigrationResult;
+import org.apache.streampipes.model.migration.ModelMigratorConfig;
+import org.apache.streampipes.model.staticproperty.FreeTextStaticProperty;
+import org.apache.streampipes.model.staticproperty.StaticProperty;
+import org.apache.streampipes.vocabulary.XSD;
+
+import static org.apache.streampipes.connect.iiot.adapters.ros.RosBridgeAdapter.ROS_PORT_KEY;
+
+public class RosBridgeAdapterMigrationV1 implements IAdapterMigrator {
+
+  @Override
+  public ModelMigratorConfig config() {
+    return new ModelMigratorConfig(
+      "org.apache.streampipes.connect.iiot.adapters.ros",
+      SpServiceTagPrefix.ADAPTER,
+      0,
+      1);
+  }
+
+  @Override
+  public MigrationResult<AdapterDescription> migrate(AdapterDescription element,
+                                                     IStaticPropertyExtractor extractor) throws RuntimeException {
+
+    var portProperty = extractPortProperty(element);
+    portProperty.setRequiredDatatype(XSD.INTEGER);
+
+    return MigrationResult.success(element);
+  }
+
+  protected FreeTextStaticProperty extractPortProperty(AdapterDescription adapterDescription) {
+    return (FreeTextStaticProperty) adapterDescription.getConfig().stream()
+                                                      .filter(this::isPortConfig)
+                                                      .findFirst()
+                                                      .orElseThrow();
+  }
+
+  private boolean isPortConfig(StaticProperty config) {
+    return config.getInternalName().equals(ROS_PORT_KEY);
+  }
+}
\ No newline at end of file
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/ros/RosBridgeAdapter.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/ros/RosBridgeAdapter.java
index 675461ecbe..9ff1d9ecb2 100644
--- a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/ros/RosBridgeAdapter.java
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/ros/RosBridgeAdapter.java
@@ -60,9 +60,9 @@ public class RosBridgeAdapter implements StreamPipesAdapter, ResolvesContainerPr
 
   public static final String ID = "org.apache.streampipes.connect.iiot.adapters.ros";
 
-  private static final String ROS_HOST_KEY = "ROS_HOST_KEY";
-  private static final String ROS_PORT_KEY = "ROS_PORT_KEY";
-  private static final String TOPIC_KEY = "TOPIC_KEY";
+  public static final String ROS_HOST_KEY = "ROS_HOST_KEY";
+  public static final String ROS_PORT_KEY = "ROS_PORT_KEY";
+  public static final String TOPIC_KEY = "TOPIC_KEY";
 
   private String topic;
   private String host;
@@ -92,12 +92,12 @@ public class RosBridgeAdapter implements StreamPipesAdapter, ResolvesContainerPr
 
   @Override
   public IAdapterConfiguration declareConfig() {
-    return AdapterConfigurationBuilder.create(ID, 0, RosBridgeAdapter::new)
+    return AdapterConfigurationBuilder.create(ID, 1, RosBridgeAdapter::new)
         .withLocales(Locales.EN)
         .withAssets(Assets.DOCUMENTATION, Assets.ICON)
         .withCategory(AdapterType.Manufacturing)
         .requiredTextParameter(Labels.withId(ROS_HOST_KEY))
-        .requiredTextParameter(Labels.withId(ROS_PORT_KEY))
+        .requiredIntegerParameter(Labels.withId(ROS_PORT_KEY))
         .requiredSingleValueSelectionFromContainer(
             Labels.withId(TOPIC_KEY), Arrays.asList(ROS_HOST_KEY, ROS_PORT_KEY))
         .buildConfiguration();
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/simulator/machine/MachineDataSimulatorUtils.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/simulator/machine/MachineDataSimulatorUtils.java
index 64129c509c..f8d42d15af 100644
--- a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/simulator/machine/MachineDataSimulatorUtils.java
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/simulator/machine/MachineDataSimulatorUtils.java
@@ -58,13 +58,13 @@ public class MachineDataSimulatorUtils {
         .property(timestampProperty(TIMESTAMP))
         .sample(TIMESTAMP, System.currentTimeMillis())
         .property(PrimitivePropertyBuilder
-            .create(Datatypes.String, "sensorId")
+            .create(Datatypes.String, SENSOR_ID)
             .label("Sensor ID")
             .description("The ID of the sensor")
             .domainProperty(HAS_SENSOR_ID)
             .scope(PropertyScope.DIMENSION_PROPERTY)
             .build())
-        .sample("sensorId", "sensor01")
+        .sample(SENSOR_ID, "sensor01")
         .property(PrimitivePropertyBuilder
             .create(Datatypes.Float, "level")
             .label("Water Level")
@@ -89,13 +89,13 @@ public class MachineDataSimulatorUtils {
         .property(timestampProperty(TIMESTAMP))
         .sample(TIMESTAMP, System.currentTimeMillis())
         .property(PrimitivePropertyBuilder
-            .create(Datatypes.String, "sensorId")
+            .create(Datatypes.String, SENSOR_ID)
             .label("Sensor ID")
             .description("The ID of the sensor")
             .domainProperty(HAS_SENSOR_ID)
             .scope(PropertyScope.DIMENSION_PROPERTY)
             .build())
-        .sample("sensorId", "sensor01")
+        .sample(SENSOR_ID, "sensor01")
         .property(PrimitivePropertyBuilder
             .create(Datatypes.Float, "pressure")
             .label("Pressure")
@@ -119,7 +119,7 @@ public class MachineDataSimulatorUtils {
             .domainProperty(HAS_SENSOR_ID)
             .scope(PropertyScope.DIMENSION_PROPERTY)
             .build())
-        .sample("sensorId", "sensor01")
+        .sample(SENSOR_ID, "sensor01")
         .property(PrimitivePropertyBuilder
             .create(Datatypes.Float, MASS_FLOW)
             .label("Mass Flow")
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/OpcUaConnectorsModuleExport.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/OpcUaConnectorsModuleExport.java
index 8967148f6c..d551d979d0 100644
--- a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/OpcUaConnectorsModuleExport.java
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/OpcUaConnectorsModuleExport.java
@@ -24,6 +24,7 @@ import org.apache.streampipes.extensions.api.migration.IModelMigrator;
 import org.apache.streampipes.extensions.api.pe.IStreamPipesPipelineElement;
 import org.apache.streampipes.extensions.connectors.opcua.adapter.OpcUaAdapter;
 import org.apache.streampipes.extensions.connectors.opcua.migration.OpcUaAdapterMigrationV1;
+import org.apache.streampipes.extensions.connectors.opcua.migration.OpcUaAdapterMigrationV2;
 import org.apache.streampipes.extensions.connectors.opcua.sink.OpcUaSink;
 
 import java.util.List;
@@ -46,7 +47,8 @@ public class OpcUaConnectorsModuleExport implements IExtensionModuleExport {
   @Override
   public List<IModelMigrator<?, ?>> migrators() {
     return List.of(
-        new OpcUaAdapterMigrationV1()
+        new OpcUaAdapterMigrationV1(),
+        new OpcUaAdapterMigrationV2()
     );
   }
 }
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaAdapter.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaAdapter.java
index c4e397d718..4b93ade90a 100644
--- a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaAdapter.java
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaAdapter.java
@@ -231,7 +231,7 @@ public class OpcUaAdapter implements StreamPipesAdapter, IPullAdapter, SupportsR
 
   @Override
   public IAdapterConfiguration declareConfig() {
-    var builder = AdapterConfigurationBuilder.create(ID, 1, OpcUaAdapter::new)
+    var builder = AdapterConfigurationBuilder.create(ID, 2, OpcUaAdapter::new)
         .withAssets(Assets.DOCUMENTATION, Assets.ICON)
         .withLocales(Locales.EN)
         .withCategory(AdapterType.Generic, AdapterType.Manufacturing)
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/SharedUserConfiguration.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/SharedUserConfiguration.java
index a5691da311..cb95919174 100644
--- a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/SharedUserConfiguration.java
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/SharedUserConfiguration.java
@@ -69,7 +69,7 @@ public class SharedUserConfiguration {
                     Labels.withId(HOST_PORT),
                     StaticProperties.stringFreeTextProperty(
                         Labels.withId(OPC_SERVER_HOST)),
-                    StaticProperties.stringFreeTextProperty(
+                    StaticProperties.integerFreeTextProperty(
                         Labels.withId(OPC_SERVER_PORT))
                 ))
         )
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/migration/OpcUaAdapterMigrationV2.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/migration/OpcUaAdapterMigrationV2.java
new file mode 100644
index 0000000000..382d1e3514
--- /dev/null
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/migration/OpcUaAdapterMigrationV2.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.extensions.connectors.opcua.migration;
+
+import org.apache.streampipes.extensions.api.extractor.IStaticPropertyExtractor;
+import org.apache.streampipes.extensions.api.migration.IAdapterMigrator;
+import org.apache.streampipes.model.connect.adapter.AdapterDescription;
+import org.apache.streampipes.model.extensions.svcdiscovery.SpServiceTagPrefix;
+import org.apache.streampipes.model.migration.MigrationResult;
+import org.apache.streampipes.model.migration.ModelMigratorConfig;
+import org.apache.streampipes.model.staticproperty.FreeTextStaticProperty;
+import org.apache.streampipes.model.staticproperty.StaticPropertyAlternatives;
+import org.apache.streampipes.model.staticproperty.StaticPropertyGroup;
+import org.apache.streampipes.vocabulary.XSD;
+
+import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.OPC_HOST;
+import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.OPC_HOST_OR_URL;
+import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.OPC_SERVER_PORT;
+
+
+public class OpcUaAdapterMigrationV2 implements IAdapterMigrator {
+  @Override
+  public ModelMigratorConfig config() {
+    return new ModelMigratorConfig(
+        "org.apache.streampipes.connect.iiot.adapters.opcua",
+        SpServiceTagPrefix.ADAPTER,
+        1,
+        2
+    );
+  }
+
+  @Override
+  public MigrationResult<AdapterDescription> migrate(
+      AdapterDescription element,
+      IStaticPropertyExtractor extractor
+  ) throws RuntimeException {
+
+    var portStaticProperty = extractPortProperty(element);
+    portStaticProperty.setRequiredDatatype(XSD.INTEGER);
+
+    return MigrationResult.success(element);
+  }
+
+  /**
+   * This method searches for the static property of the port definition
+   */
+  protected FreeTextStaticProperty extractPortProperty(AdapterDescription adapterDescription) {
+    var staticPropertyAlternatives = getOpcHostOrUrl(adapterDescription);
+    var staticPropertyGroup = getOpcHost(staticPropertyAlternatives);
+    return getOpcServerPort(staticPropertyGroup);
+  }
+
+  private StaticPropertyAlternatives getOpcHostOrUrl(AdapterDescription adapterDescription) {
+    return (StaticPropertyAlternatives) adapterDescription
+        .getConfig()
+        .stream()
+        .filter(config -> config.getInternalName()
+                                .equals(OPC_HOST_OR_URL.name()))
+        .findFirst()
+        .orElseThrow();
+  }
+
+  private StaticPropertyGroup getOpcHost(StaticPropertyAlternatives staticPropertyAlternatives) {
+    return (StaticPropertyGroup) staticPropertyAlternatives
+        .getAlternatives()
+        .stream()
+        .filter(alternative -> alternative.getInternalName()
+                                          .equals(OPC_HOST.name()))
+        .findFirst()
+        .orElseThrow()
+        .getStaticProperty();
+  }
+
+  private FreeTextStaticProperty getOpcServerPort(StaticPropertyGroup staticPropertyGroup) {
+    return (FreeTextStaticProperty) staticPropertyGroup
+        .getStaticProperties()
+        .stream()
+        .filter(property -> property.getInternalName()
+                                    .equals(OPC_SERVER_PORT.name()))
+        .findFirst()
+        .orElseThrow();
+  }
+}
diff --git a/streampipes-extensions/streampipes-connectors-plc/pom.xml b/streampipes-extensions/streampipes-connectors-plc/pom.xml
index 2dd11589e7..5db1ad0263 100644
--- a/streampipes-extensions/streampipes-connectors-plc/pom.xml
+++ b/streampipes-extensions/streampipes-connectors-plc/pom.xml
@@ -71,6 +71,12 @@
             <scope>runtime</scope>
         </dependency>
 
+        <!-- Test dependencies -->
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
diff --git a/streampipes-extensions/streampipes-connectors-plc/src/main/java/org/apache/streampipes/extensions/connectors/plc/PlcConnectorsModuleExport.java b/streampipes-extensions/streampipes-connectors-plc/src/main/java/org/apache/streampipes/extensions/connectors/plc/PlcConnectorsModuleExport.java
index b632d9c5b5..b63ad2bd2c 100644
--- a/streampipes-extensions/streampipes-connectors-plc/src/main/java/org/apache/streampipes/extensions/connectors/plc/PlcConnectorsModuleExport.java
+++ b/streampipes-extensions/streampipes-connectors-plc/src/main/java/org/apache/streampipes/extensions/connectors/plc/PlcConnectorsModuleExport.java
@@ -22,6 +22,7 @@ import org.apache.streampipes.extensions.api.connect.StreamPipesAdapter;
 import org.apache.streampipes.extensions.api.declarer.IExtensionModuleExport;
 import org.apache.streampipes.extensions.api.migration.IModelMigrator;
 import org.apache.streampipes.extensions.api.pe.IStreamPipesPipelineElement;
+import org.apache.streampipes.extensions.connectors.plc.adapter.migration.Plc4xModbusAdapterMigrationV1;
 import org.apache.streampipes.extensions.connectors.plc.adapter.migration.Plc4xS7AdapterMigrationV1;
 import org.apache.streampipes.extensions.connectors.plc.adapter.modbus.Plc4xModbusAdapter;
 import org.apache.streampipes.extensions.connectors.plc.adapter.s7.Plc4xS7Adapter;
@@ -46,7 +47,8 @@ public class PlcConnectorsModuleExport implements IExtensionModuleExport {
   @Override
   public List<IModelMigrator<?, ?>> migrators() {
     return List.of(
-        new Plc4xS7AdapterMigrationV1()
+        new Plc4xS7AdapterMigrationV1(),
+        new Plc4xModbusAdapterMigrationV1()
     );
   }
 }
diff --git a/streampipes-extensions/streampipes-connectors-plc/src/main/java/org/apache/streampipes/extensions/connectors/plc/adapter/migration/Plc4xModbusAdapterMigrationV1.java b/streampipes-extensions/streampipes-connectors-plc/src/main/java/org/apache/streampipes/extensions/connectors/plc/adapter/migration/Plc4xModbusAdapterMigrationV1.java
new file mode 100644
index 0000000000..660de692e8
--- /dev/null
+++ b/streampipes-extensions/streampipes-connectors-plc/src/main/java/org/apache/streampipes/extensions/connectors/plc/adapter/migration/Plc4xModbusAdapterMigrationV1.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.extensions.connectors.plc.adapter.migration;
+
+import org.apache.streampipes.extensions.api.extractor.IStaticPropertyExtractor;
+import org.apache.streampipes.extensions.api.migration.IAdapterMigrator;
+import org.apache.streampipes.model.connect.adapter.AdapterDescription;
+import org.apache.streampipes.model.extensions.svcdiscovery.SpServiceTagPrefix;
+import org.apache.streampipes.model.migration.MigrationResult;
+import org.apache.streampipes.model.migration.ModelMigratorConfig;
+import org.apache.streampipes.model.staticproperty.FreeTextStaticProperty;
+import org.apache.streampipes.model.staticproperty.StaticProperty;
+import org.apache.streampipes.vocabulary.XSD;
+
+import static org.apache.streampipes.extensions.connectors.plc.adapter.modbus.Plc4xModbusAdapter.PLC_PORT;
+
+public class Plc4xModbusAdapterMigrationV1 implements IAdapterMigrator {
+  @Override
+  public ModelMigratorConfig config() {
+    return new ModelMigratorConfig(
+      "org.apache.streampipes.connect.iiot.adapters.plc4x.modbus",
+      SpServiceTagPrefix.ADAPTER,
+      0,
+      1
+    );
+  }
+
+  @Override
+  public MigrationResult<AdapterDescription> migrate(AdapterDescription element,
+                                                     IStaticPropertyExtractor extractor) throws RuntimeException {
+
+    var portProperty = extractPortProperty(element);
+    portProperty.setRequiredDatatype(XSD.INTEGER);
+
+    return MigrationResult.success(element);
+  }
+
+  protected FreeTextStaticProperty extractPortProperty(AdapterDescription adapterDescription) {
+    return (FreeTextStaticProperty) adapterDescription.getConfig().stream()
+        .filter(this::isPortConfig)
+        .findFirst()
+        .orElseThrow();
+  }
+
+  private boolean isPortConfig(StaticProperty config) {
+    return config.getInternalName().equals(PLC_PORT);
+  }
+}
diff --git a/streampipes-extensions/streampipes-connectors-plc/src/main/java/org/apache/streampipes/extensions/connectors/plc/adapter/modbus/Plc4xModbusAdapter.java b/streampipes-extensions/streampipes-connectors-plc/src/main/java/org/apache/streampipes/extensions/connectors/plc/adapter/modbus/Plc4xModbusAdapter.java
index 1487c77ae7..c9815e6892 100644
--- a/streampipes-extensions/streampipes-connectors-plc/src/main/java/org/apache/streampipes/extensions/connectors/plc/adapter/modbus/Plc4xModbusAdapter.java
+++ b/streampipes-extensions/streampipes-connectors-plc/src/main/java/org/apache/streampipes/extensions/connectors/plc/adapter/modbus/Plc4xModbusAdapter.java
@@ -76,14 +76,14 @@ public class Plc4xModbusAdapter implements StreamPipesAdapter, IPullAdapter {
   /**
    * Keys of user configuration parameters
    */
-  private static final String PLC_IP = "plc_ip";
-  private static final String PLC_PORT = "plc_port";
-
-  private static final String PLC_NODES = "plc_nodes";
-  private static final String PLC_NODE_ID = "plc_node_id";
-  private static final String PLC_NODE_RUNTIME_NAME = "plc_node_runtime_name";
-  private static final String PLC_NODE_ADDRESS = "plc_node_address";
-  private static final String PLC_NODE_TYPE = "plc_node_type";
+  public static final String PLC_IP = "plc_ip";
+  public static final String PLC_PORT = "plc_port";
+
+  public static final String PLC_NODES = "plc_nodes";
+  public static final String PLC_NODE_ID = "plc_node_id";
+  public static final String PLC_NODE_RUNTIME_NAME = "plc_node_runtime_name";
+  public static final String PLC_NODE_ADDRESS = "plc_node_address";
+  public static final String PLC_NODE_TYPE = "plc_node_type";
   private static final String CONFIGURE = "configure";
 
   /**
@@ -273,11 +273,11 @@ public class Plc4xModbusAdapter implements StreamPipesAdapter, IPullAdapter {
    */
   @Override
   public IAdapterConfiguration declareConfig() {
-    return AdapterConfigurationBuilder.create(ID, 0, Plc4xModbusAdapter::new)
+    return AdapterConfigurationBuilder.create(ID, 1, Plc4xModbusAdapter::new)
         .withLocales(Locales.EN)
         .withAssets(Assets.DOCUMENTATION, Assets.ICON)
         .withCategory(AdapterType.Manufacturing)
-        .requiredTextParameter(Labels.withId(PLC_IP)).requiredTextParameter(Labels.withId(PLC_PORT))
+        .requiredTextParameter(Labels.withId(PLC_IP)).requiredIntegerParameter(Labels.withId(PLC_PORT))
         .requiredTextParameter(Labels.withId(PLC_NODE_ID)).requiredCollection(Labels.withId(PLC_NODES),
             StaticProperties.stringFreeTextProperty(Labels.withId(PLC_NODE_RUNTIME_NAME)),
             StaticProperties.integerFreeTextProperty(Labels.withId(PLC_NODE_ADDRESS)),
diff --git a/streampipes-extensions/streampipes-connectors-plc/src/main/java/org/apache/streampipes/extensions/connectors/plc/adapter/s7/Plc4xS7Adapter.java b/streampipes-extensions/streampipes-connectors-plc/src/main/java/org/apache/streampipes/extensions/connectors/plc/adapter/s7/Plc4xS7Adapter.java
index 103e428cb1..5bcca0110a 100644
--- a/streampipes-extensions/streampipes-connectors-plc/src/main/java/org/apache/streampipes/extensions/connectors/plc/adapter/s7/Plc4xS7Adapter.java
+++ b/streampipes-extensions/streampipes-connectors-plc/src/main/java/org/apache/streampipes/extensions/connectors/plc/adapter/s7/Plc4xS7Adapter.java
@@ -77,6 +77,8 @@ public class Plc4xS7Adapter implements StreamPipesAdapter, IPullAdapter, PlcRead
 
   private static final Logger LOG = LoggerFactory.getLogger(Plc4xS7Adapter.class);
 
+  private static final String S7_URL = "s7://";
+
   /**
    * Keys of user configuration parameters
    */
@@ -127,7 +129,7 @@ public class Plc4xS7Adapter implements StreamPipesAdapter, IPullAdapter, PlcRead
     getConfigurations(extractor);
 
     this.driverManager = new PooledPlcDriverManager();
-    try (PlcConnection plcConnection = this.driverManager.getConnection("s7://" + this.ip)) {
+    try (PlcConnection plcConnection = this.driverManager.getConnection(S7_URL + this.ip)) {
       if (!plcConnection.getMetadata().canRead()) {
         LOG.error("The S7 on IP: " + this.ip + " does not support reading data");
       }
@@ -145,7 +147,7 @@ public class Plc4xS7Adapter implements StreamPipesAdapter, IPullAdapter, PlcRead
   @Override
   public void pullData() {
     // Create PLC read request
-    try (PlcConnection plcConnection = this.driverManager.getConnection("s7://" + this.ip)) {
+    try (PlcConnection plcConnection = this.driverManager.getConnection(S7_URL + this.ip)) {
       readPlcData(plcConnection, this);
     } catch (Exception e) {
       LOG.error("Error while reading from PLC with IP {} ", this.ip, e);
@@ -171,7 +173,7 @@ public class Plc4xS7Adapter implements StreamPipesAdapter, IPullAdapter, PlcRead
   }
 
   private Map<String, Object> readPlcDataSynchronized() throws Exception {
-    try (PlcConnection plcConnection = this.driverManager.getConnection("s7://" + this.ip)) {
+    try (PlcConnection plcConnection = this.driverManager.getConnection(S7_URL + this.ip)) {
       var readRequest = makeReadRequest(plcConnection);
       // Execute the request
       var readResponse = readRequest.execute().get(5000, TimeUnit.MILLISECONDS);
diff --git a/streampipes-extensions/streampipes-pipeline-elements-experimental-flink/src/main/java/org/apache/streampipes/pe/flink/processor/aggregation/AggregationController.java b/streampipes-extensions/streampipes-pipeline-elements-experimental-flink/src/main/java/org/apache/streampipes/pe/flink/processor/aggregation/AggregationController.java
index 5f4e72766c..352fb68aef 100644
--- a/streampipes-extensions/streampipes-pipeline-elements-experimental-flink/src/main/java/org/apache/streampipes/pe/flink/processor/aggregation/AggregationController.java
+++ b/streampipes-extensions/streampipes-pipeline-elements-experimental-flink/src/main/java/org/apache/streampipes/pe/flink/processor/aggregation/AggregationController.java
@@ -104,7 +104,7 @@ public class AggregationController extends FlinkDataProcessorDeclarer<Aggregatio
   public FlinkDataProcessorProgram<AggregationParameters> getProgram(DataProcessorInvocation graph,
                                                                      ProcessingElementParameterExtractor extractor) {
 
-    List<String> groupBy = extractor.mappingPropertyValues("groupBy");
+    List<String> groupBy = extractor.mappingPropertyValues(GROUP_BY_KEY);
 
     List<String> aggregateKeyList = extractor.mappingPropertyValues(AGGREGATE_KEY_LIST);
     String aggregateOperation = extractor.selectedSingleValueInternalName(OPERATION_KEY, String.class);
diff --git a/streampipes-extensions/streampipes-processors-geo-jvm/src/main/java/org/apache/streampipes/processors/geo/jvm/jts/helper/SpReprojectionBuilder.java b/streampipes-extensions/streampipes-processors-geo-jvm/src/main/java/org/apache/streampipes/processors/geo/jvm/jts/helper/SpReprojectionBuilder.java
index 9781d65eba..6d5011e6c5 100755
--- a/streampipes-extensions/streampipes-processors-geo-jvm/src/main/java/org/apache/streampipes/processors/geo/jvm/jts/helper/SpReprojectionBuilder.java
+++ b/streampipes-extensions/streampipes-processors-geo-jvm/src/main/java/org/apache/streampipes/processors/geo/jvm/jts/helper/SpReprojectionBuilder.java
@@ -49,7 +49,7 @@ public class SpReprojectionBuilder {
   public static Geometry reprojectSpGeometry(Geometry geom, Integer targetEPSG)
       throws SpNotSupportedGeometryException {
 
-    Geometry output = null;
+    Geometry output;
 
     CoordinateReferenceSystem sourcerCRS = getCRS(geom.getSRID());
     CoordinateReferenceSystem targetrCRS = getCRS(targetEPSG);
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/file/FileMetadata.java b/streampipes-model/src/main/java/org/apache/streampipes/model/file/FileMetadata.java
index 656ea6861c..f4ee123ecc 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/file/FileMetadata.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/file/FileMetadata.java
@@ -28,8 +28,7 @@ public class FileMetadata {
 
   private @SerializedName("_rev") String rev;
 
-  private String internalFilename;
-  private String originalFilename;
+  private String filename;
   private String filetype;
 
   private long createdAt;
@@ -53,20 +52,12 @@ public class FileMetadata {
     this.rev = rev;
   }
 
-  public String getInternalFilename() {
-    return internalFilename;
+  public String getFilename() {
+    return filename;
   }
 
-  public void setInternalFilename(String internalFilename) {
-    this.internalFilename = internalFilename;
-  }
-
-  public String getOriginalFilename() {
-    return originalFilename;
-  }
-
-  public void setOriginalFilename(String originalFilename) {
-    this.originalFilename = originalFilename;
+  public void setFilename(String filename) {
+    this.filename = filename;
   }
 
   public long getCreatedAt() {
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/FreeTextStaticProperty.java b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/FreeTextStaticProperty.java
index 26eba23b33..6dda7dbe0c 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/FreeTextStaticProperty.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/FreeTextStaticProperty.java
@@ -19,6 +19,7 @@
 package org.apache.streampipes.model.staticproperty;
 
 import java.net.URI;
+import java.util.Objects;
 
 public class FreeTextStaticProperty extends StaticProperty {
 
@@ -67,7 +68,7 @@ public class FreeTextStaticProperty extends StaticProperty {
 
   public FreeTextStaticProperty(String internalName, String label, String description, URI type) {
     super(StaticPropertyType.FreeTextStaticProperty, internalName, label, description);
-    this.requiredDomainProperty = type;
+    this.requiredDatatype = type;
   }
 
   public FreeTextStaticProperty(String internalName, String label, String description, URI type, String mapsTo) {
@@ -163,4 +164,58 @@ public class FreeTextStaticProperty extends StaticProperty {
   public void accept(StaticPropertyVisitor visitor) {
     visitor.visit(this);
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof FreeTextStaticProperty that)) {
+      return false;
+    }
+    if (!super.equals(o)) {
+      return false;
+    }
+
+    if (multiLine != that.multiLine) {
+      return false;
+    }
+    if (htmlAllowed != that.htmlAllowed) {
+      return false;
+    }
+    if (htmlFontFormat != that.htmlFontFormat) {
+      return false;
+    }
+    if (placeholdersSupported != that.placeholdersSupported) {
+      return false;
+    }
+    if (!Objects.equals(value, that.value)) {
+      return false;
+    }
+    if (!Objects.equals(requiredDatatype, that.requiredDatatype)) {
+      return false;
+    }
+    if (!Objects.equals(requiredDomainProperty, that.requiredDomainProperty)) {
+      return false;
+    }
+    if (!Objects.equals(mapsTo, that.mapsTo)) {
+      return false;
+    }
+    return Objects.equals(valueSpecification, that.valueSpecification);
+  }
+
+  @Override
+  public int hashCode() {
+    int result = super.hashCode();
+    result = 31 * result + (value != null ? value.hashCode() : 0);
+    result = 31 * result + (requiredDatatype != null ? requiredDatatype.hashCode() : 0);
+    result = 31 * result + (requiredDomainProperty != null ? requiredDomainProperty.hashCode() : 0);
+    result = 31 * result + (mapsTo != null ? mapsTo.hashCode() : 0);
+    result = 31 * result + (multiLine ? 1 : 0);
+    result = 31 * result + (htmlAllowed ? 1 : 0);
+    result = 31 * result + (htmlFontFormat ? 1 : 0);
+    result = 31 * result + (placeholdersSupported ? 1 : 0);
+    result = 31 * result + (valueSpecification != null ? valueSpecification.hashCode() : 0);
+    return result;
+  }
 }
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticProperty.java b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticProperty.java
index 6b8e406827..0c952061f7 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticProperty.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticProperty.java
@@ -23,6 +23,8 @@ import org.apache.streampipes.model.shared.annotation.TsModel;
 import com.fasterxml.jackson.annotation.JsonSubTypes;
 import com.fasterxml.jackson.annotation.JsonTypeInfo;
 
+import java.util.Objects;
+
 @JsonSubTypes({
     @JsonSubTypes.Type(AnyStaticProperty.class),
     @JsonSubTypes.Type(CodeInputStaticProperty.class),
@@ -150,4 +152,45 @@ public abstract class StaticProperty {
 
   public abstract void accept(StaticPropertyVisitor visitor);
 
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof StaticProperty that)) {
+      return false;
+    }
+
+    if (valueRequired != that.valueRequired) {
+      return false;
+    }
+    if (index != that.index) {
+      return false;
+    }
+    if (predefined != that.predefined) {
+      return false;
+    }
+    if (staticPropertyType != that.staticPropertyType) {
+      return false;
+    }
+    if (!Objects.equals(label, that.label)) {
+      return false;
+    }
+    if (!Objects.equals(description, that.description)) {
+      return false;
+    }
+    return Objects.equals(internalName, that.internalName);
+  }
+
+  @Override
+  public int hashCode() {
+    int result = (valueRequired ? 1 : 0);
+    result = 31 * result + (staticPropertyType != null ? staticPropertyType.hashCode() : 0);
+    result = 31 * result + index;
+    result = 31 * result + (label != null ? label.hashCode() : 0);
+    result = 31 * result + (description != null ? description.hashCode() : 0);
+    result = 31 * result + (internalName != null ? internalName.hashCode() : 0);
+    result = 31 * result + (predefined ? 1 : 0);
+    return result;
+  }
 }
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticPropertyAlternative.java b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticPropertyAlternative.java
index 0e76e7493d..78e3d53001 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticPropertyAlternative.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticPropertyAlternative.java
@@ -20,6 +20,8 @@ package org.apache.streampipes.model.staticproperty;
 import org.apache.streampipes.model.util.Cloner;
 import org.apache.streampipes.model.util.ElementIdGenerator;
 
+import java.util.Objects;
+
 public class StaticPropertyAlternative extends StaticProperty {
 
   private String elementId;
@@ -76,4 +78,37 @@ public class StaticPropertyAlternative extends StaticProperty {
   public void accept(StaticPropertyVisitor visitor) {
     visitor.visit(this);
   }
+
+  /**
+   * equals() excludes elementId
+   */
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof StaticPropertyAlternative that)) {
+      return false;
+    }
+    if (!super.equals(o)) {
+      return false;
+    }
+
+    if (selected != that.selected) {
+      return false;
+    }
+
+    return Objects.equals(staticProperty, that.staticProperty);
+  }
+
+  /**
+   * hashCode() excludes elementId
+   */
+  @Override
+  public int hashCode() {
+    int result = super.hashCode();
+    result = 31 * result + (selected ? 1 : 0);
+    result = 31 * result + (staticProperty != null ? staticProperty.hashCode() : 0);
+    return result;
+  }
 }
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticPropertyAlternatives.java b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticPropertyAlternatives.java
index 28a3c3d486..9f76dc3d08 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticPropertyAlternatives.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticPropertyAlternatives.java
@@ -54,4 +54,36 @@ public class StaticPropertyAlternatives extends StaticProperty {
   public void accept(StaticPropertyVisitor visitor) {
     visitor.visit(this);
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof StaticPropertyAlternatives that)) {
+      return false;
+    }
+    if (!super.equals(o)) {
+      return false;
+    }
+
+    if (!(this.alternatives.size() == that.alternatives.size())) {
+      return false;
+    }
+
+    for (var i = 0; i < alternatives.size(); i++){
+      if (!alternatives.get(i).equals(that.alternatives.get(i))){
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = super.hashCode();
+    result = 31 * result + (alternatives != null ? alternatives.hashCode() : 0);
+    return result;
+  }
 }
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticPropertyGroup.java b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticPropertyGroup.java
index bbf1d52dbc..c81ba0b407 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticPropertyGroup.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticPropertyGroup.java
@@ -20,6 +20,7 @@ package org.apache.streampipes.model.staticproperty;
 import org.apache.streampipes.model.util.Cloner;
 
 import java.util.List;
+import java.util.Objects;
 
 public class StaticPropertyGroup extends StaticProperty {
 
@@ -83,4 +84,41 @@ public class StaticPropertyGroup extends StaticProperty {
   public void accept(StaticPropertyVisitor visitor) {
     visitor.visit(this);
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof StaticPropertyGroup that)) {
+      return false;
+    }
+    if (!super.equals(o)) {
+      return false;
+    }
+
+    if (horizontalRendering != that.horizontalRendering) {
+      return false;
+    }
+
+    if (!(this.staticProperties.size() == that.staticProperties.size())) {
+      return false;
+    }
+
+    for (var i = 0; i < staticProperties.size(); i++){
+      if (!staticProperties.get(i).equals(that.staticProperties.get(i))){
+        return false;
+      }
+    }
+    return Objects.equals(showLabel, that.showLabel);
+  }
+
+  @Override
+  public int hashCode() {
+    int result = super.hashCode();
+    result = 31 * result + (staticProperties != null ? staticProperties.hashCode() : 0);
+    result = 31 * result + (showLabel != null ? showLabel.hashCode() : 0);
+    result = 31 * result + (horizontalRendering ? 1 : 0);
+    return result;
+  }
 }
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/file/FileHandler.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/file/FileHandler.java
index b5403a6f94..943da3d95f 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/file/FileHandler.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/file/FileHandler.java
@@ -18,13 +18,19 @@
 package org.apache.streampipes.manager.file;
 
 import org.apache.commons.io.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 
 public class FileHandler {
 
+  Logger logger = LoggerFactory.getLogger(FileHandler.class);
+
   public void storeFile(String filename, InputStream fileInputStream) throws IOException {
     File targetFile = makeFile(filename);
     FileUtils.copyInputStreamToFile(fileInputStream, targetFile);
@@ -39,6 +45,20 @@ public class FileHandler {
     return FileUtils.getFile(makeFile(filename));
   }
 
+  public void renameFile(String oldFilename, String newFilename) {
+    try {
+      var fileInputStream = new FileInputStream(getFile(oldFilename));
+      deleteFile(oldFilename);
+      storeFile(newFilename, fileInputStream);
+    } catch (FileNotFoundException e) {
+      logger.error(
+          "Failed to find the old file locally with internalFilename as the identifier, this is most likely a mismatch "
+              + "between local file and FileMetadata stored in CouchDB. Raw exception message: " + e.getMessage());
+    } catch (IOException e) {
+      logger.error("Failed to save renamed file locally: " + e.getMessage());
+    }
+  }
+
   private File makeFile(String filename) {
     File fileDir = new File(makeFileLocation());
     if (!fileDir.exists()) {
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/file/FileManager.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/file/FileManager.java
index fa860de9f4..20fece68b9 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/file/FileManager.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/file/FileManager.java
@@ -29,7 +29,6 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.util.Arrays;
 import java.util.List;
-import java.util.UUID;
 import java.util.stream.Collectors;
 
 public class FileManager {
@@ -48,13 +47,13 @@ public class FileManager {
 
     var file = allFiles
             .stream()
-            .filter(fileMetadata -> fileMetadata.getOriginalFilename().equals(originalName))
+            .filter(fileMetadata -> fileMetadata.getFilename().equals(originalName))
             .findFirst();
 
     if (file.isEmpty()){
       throw new IOException("No file with original name '%s' found".formatted(originalName));
     }
-    return new FileHandler().getFile(file.get().getInternalFilename());
+    return new FileHandler().getFile(file.get().getFilename());
   }
 
   /**
@@ -74,16 +73,15 @@ public class FileManager {
 
     fileInputStream = cleanFile(fileInputStream, filetype);
 
-    String internalFilename = makeInternalFilename(filetype);
-    FileMetadata fileMetadata = makeFileMetadata(user, filename, internalFilename, filetype);
-    new FileHandler().storeFile(internalFilename, fileInputStream);
+    FileMetadata fileMetadata = makeFileMetadata(user, filename, filetype);
+    new FileHandler().storeFile(filename, fileInputStream);
     storeFileMetadata(fileMetadata);
     return fileMetadata;
   }
 
   public static void deleteFile(String id) {
     FileMetadata fileMetadata = getFileMetadataStorage().getMetadataById(id);
-    new FileHandler().deleteFile(fileMetadata.getInternalFilename());
+    new FileHandler().deleteFile(fileMetadata.getFilename());
     getFileMetadataStorage().deleteFileMetadata(id);
   }
 
@@ -118,24 +116,18 @@ public class FileManager {
   }
 
   private static FileMetadata makeFileMetadata(String user,
-                                               String originalFilename,
-                                               String internalFilename,
+                                               String filename,
                                                String filetype) {
 
     FileMetadata fileMetadata = new FileMetadata();
     fileMetadata.setCreatedAt(System.currentTimeMillis());
     fileMetadata.setCreatedByUser(user);
     fileMetadata.setFiletype(filetype);
-    fileMetadata.setInternalFilename(internalFilename);
-    fileMetadata.setOriginalFilename(originalFilename);
+    fileMetadata.setFilename(filename);
 
     return fileMetadata;
   }
 
-  private static String makeInternalFilename(String filetype) {
-    return UUID.randomUUID() + "." + filetype;
-  }
-
   private static List<FileMetadata> filterFiletypes(List<FileMetadata> allFiles, String filetypes) {
     return allFiles
         .stream()
diff --git a/streampipes-rest-extensions/src/main/java/org/apache/streampipes/rest/extensions/migration/MigrateExtensionsResource.java b/streampipes-rest-extensions/src/main/java/org/apache/streampipes/rest/extensions/migration/MigrateExtensionsResource.java
index 07fc157f6c..fc05cd0766 100644
--- a/streampipes-rest-extensions/src/main/java/org/apache/streampipes/rest/extensions/migration/MigrateExtensionsResource.java
+++ b/streampipes-rest-extensions/src/main/java/org/apache/streampipes/rest/extensions/migration/MigrateExtensionsResource.java
@@ -21,7 +21,9 @@ package org.apache.streampipes.rest.extensions.migration;
 import org.apache.streampipes.extensions.api.extractor.IParameterExtractor;
 import org.apache.streampipes.extensions.api.migration.IModelMigrator;
 import org.apache.streampipes.extensions.management.init.DeclarersSingleton;
+import org.apache.streampipes.extensions.management.locales.LabelGenerator;
 import org.apache.streampipes.model.base.VersionedNamedStreamPipesEntity;
+import org.apache.streampipes.model.connect.adapter.AdapterDescription;
 import org.apache.streampipes.model.extensions.migration.MigrationRequest;
 import org.apache.streampipes.model.migration.MigrationResult;
 import org.apache.streampipes.model.migration.ModelMigratorConfig;
@@ -31,12 +33,13 @@ import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.IOException;
 import java.util.Optional;
 
 public abstract class MigrateExtensionsResource<
-        T extends VersionedNamedStreamPipesEntity,
-        ExT extends IParameterExtractor,
-        MmT extends IModelMigrator<T, ExT>> extends AbstractExtensionsResource {
+    T extends VersionedNamedStreamPipesEntity,
+    ExT extends IParameterExtractor,
+    MmT extends IModelMigrator<T, ExT>> extends AbstractExtensionsResource {
 
   private static final Logger LOG = LoggerFactory.getLogger(MigrateExtensionsResource.class);
 
@@ -49,11 +52,14 @@ public abstract class MigrateExtensionsResource<
    * @return Optional model migrator which is empty in case no appropriate migrator is found among the registered.
    */
   public Optional<MmT> getMigrator(ModelMigratorConfig modelMigratorConfig) {
-    return DeclarersSingleton.getInstance().getServiceDefinition().getMigrators()
-            .stream()
-            .filter(modelMigrator -> modelMigrator.config().equals(modelMigratorConfig))
-            .map(modelMigrator -> (MmT) modelMigrator)
-            .findFirst();
+    return DeclarersSingleton.getInstance()
+                             .getServiceDefinition()
+                             .getMigrators()
+                             .stream()
+                             .filter(modelMigrator -> modelMigrator.config()
+                                                                   .equals(modelMigratorConfig))
+                             .map(modelMigrator -> (MmT) modelMigrator)
+                             .findFirst();
   }
 
   /**
@@ -62,6 +68,7 @@ public abstract class MigrateExtensionsResource<
    * The result is always part of the response.
    * Independent, of the migration outcome, the returned response always has OK as status code.
    * It is the responsibility of the recipient to interpret the migration result and act accordingly.
+   *
    * @param migrationRequest Request that contains both the pipeline element to be migrated and the migration config.
    * @return A response with status code ok, that contains a migration result reflecting the outcome of the operation.
    */
@@ -70,10 +77,11 @@ public abstract class MigrateExtensionsResource<
     var pipelineElementDescription = migrationRequest.migrationElement();
     var migrationConfig = migrationRequest.modelMigratorConfig();
 
-    LOG.info("Received migration request for pipeline element '{}' to migrate from version {} to {}",
-            pipelineElementDescription.getElementId(),
-            migrationConfig.fromVersion(),
-            migrationConfig.toVersion()
+    LOG.info(
+        "Received migration request for pipeline element '{}' to migrate from version {} to {}",
+        pipelineElementDescription.getElementId(),
+        migrationConfig.fromVersion(),
+        migrationConfig.toVersion()
     );
 
     var migratorOptional = getMigrator(migrationConfig);
@@ -84,23 +92,24 @@ public abstract class MigrateExtensionsResource<
     }
     LOG.error("Migrator for migration config {} could not be found. Migration is cancelled.", migrationConfig);
     return MigrationResult.failure(
-            pipelineElementDescription,
-            String.format(
-                    "The given migration config '%s' could not be mapped to a registered migrator.",
-                    migrationConfig
-            )
+        pipelineElementDescription,
+        String.format(
+            "The given migration config '%s' could not be mapped to a registered migrator.",
+            migrationConfig
+        )
     );
   }
 
   /**
    * Executes the migration for the given pipeline element based on the given migrator.
-   * @param migrator migrator that executes the migration
+   *
+   * @param migrator                   migrator that executes the migration
    * @param pipelineElementDescription pipeline element to be migrated
    * @return the migration result containing either the migrated element or the original one in case of a failure
    */
   protected MigrationResult<T> executeMigration(
-          MmT migrator,
-          T pipelineElementDescription
+      MmT migrator,
+      T pipelineElementDescription
   ) {
 
     var extractor = getPropertyExtractor(pipelineElementDescription);
@@ -111,12 +120,18 @@ public abstract class MigrateExtensionsResource<
       if (result.success()) {
         LOG.info("Migration successfully finished.");
 
+        var migratedElement = result.element();
+
+        migratedElement = updateLabels(migratedElement);
+
         // Since adapter migration was successful, version can be adapted to the target version.
         // this step is explicitly performed here and not left to the migration itself to
         // prevent leaving this step out
-        var migratedProcessor = result.element();
-        migratedProcessor.setVersion(migrator.config().toVersion());
-        return MigrationResult.success(migratedProcessor);
+        migratedElement.setVersion(
+            migrator.config()
+                    .toVersion());
+
+        return MigrationResult.success(migratedElement);
 
       } else {
         LOG.error("Migration failed with the following reason: {}", result.message());
@@ -126,16 +141,28 @@ public abstract class MigrateExtensionsResource<
       }
     } catch (RuntimeException e) {
       LOG.error("An unexpected exception caused the migration to fail - "
-              + "sending exception report in migration result");
+                    + "sending exception report in migration result");
       return MigrationResult.failure(
-              pipelineElementDescription,
-              String.format(
-                      "Migration failed due to an unexpected exception: %s",
-                      StringUtils.join(e.getStackTrace(), "\n")
-              )
+          pipelineElementDescription,
+          String.format(
+              "Migration failed due to an unexpected exception: %s",
+              StringUtils.join(e.getStackTrace(), "\n")
+          )
       );
     }
   }
 
+  private static <T extends VersionedNamedStreamPipesEntity> T updateLabels(T migratedElement) {
+    if (migratedElement.isIncludesLocales()) {
+      try {
+        boolean replaceTitles = !(migratedElement instanceof AdapterDescription);
+        migratedElement = new LabelGenerator<>(migratedElement, replaceTitles).generateLabels();
+      } catch (IOException e) {
+        LOG.error("Failed to generate labels for migrated element: %s".formatted(migratedElement.getAppId()), e);
+      }
+    }
+    return migratedElement;
+  }
+
   protected abstract ExT getPropertyExtractor(T pipelineElementDescription);
 }
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineElementFile.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineElementFile.java
index 2a71a5a0a0..bcdfb5e5e8 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineElementFile.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineElementFile.java
@@ -135,7 +135,7 @@ public class PipelineElementFile extends AbstractAuthGuardedRestResource {
   public ResponseEntity<List<String>> getAllOriginalFilenames() {
     return ok(FileManager.getAllFiles()
                          .stream()
-                         .map(fileMetadata -> fileMetadata.getOriginalFilename()
+                         .map(fileMetadata -> fileMetadata.getFilename()
                                                           .toLowerCase())
                          .toList());
   }
diff --git a/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/StaticProperties.java b/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/StaticProperties.java
index a2d6092e21..ce8defc273 100644
--- a/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/StaticProperties.java
+++ b/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/StaticProperties.java
@@ -114,6 +114,13 @@ public class StaticProperties {
     return freeTextProperty(label, Datatypes.Integer);
   }
 
+  public static FreeTextStaticProperty integerFreeTextProperty(Label label, int defaultValue) {
+    var property = freeTextProperty(label, Datatypes.Integer);
+    property.setValue(String.valueOf(defaultValue));
+    return property;
+  }
+
+
   public static FreeTextStaticProperty doubleFreeTextProperty(Label label) {
     return freeTextProperty(label, Datatypes.Double);
   }
diff --git a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/AvailableMigrations.java b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/AvailableMigrations.java
index be23f8f0d5..2ee78fea02 100644
--- a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/AvailableMigrations.java
+++ b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/AvailableMigrations.java
@@ -25,7 +25,7 @@ import org.apache.streampipes.service.core.migrations.v070.CreateFileAssetTypeMi
 import org.apache.streampipes.service.core.migrations.v090.UpdateUsernameViewMigration;
 import org.apache.streampipes.service.core.migrations.v093.AdapterMigration;
 import org.apache.streampipes.service.core.migrations.v093.StoreEmailTemplatesMigration;
-import org.apache.streampipes.service.core.migrations.v095.DuplicateFilesRenameMigration;
+import org.apache.streampipes.service.core.migrations.v095.MergeFilenamesAndRenameDuplicatesMigration;
 
 import java.util.Arrays;
 import java.util.List;
@@ -40,7 +40,7 @@ public class AvailableMigrations {
         new UpdateUsernameViewMigration(),
         new AdapterMigration(),
         new StoreEmailTemplatesMigration(),
-        new DuplicateFilesRenameMigration()
+        new MergeFilenamesAndRenameDuplicatesMigration()
     );
   }
 }
diff --git a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v095/DuplicateFilesRenameMigration.java b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v095/DuplicateFilesRenameMigration.java
deleted file mode 100644
index e5e1f76470..0000000000
--- a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v095/DuplicateFilesRenameMigration.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.apache.streampipes.service.core.migrations.v095;
-
-import org.apache.streampipes.model.file.FileMetadata;
-import org.apache.streampipes.service.core.migrations.Migration;
-import org.apache.streampipes.storage.management.StorageDispatcher;
-
-import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-
-public class DuplicateFilesRenameMigration implements Migration {
-  @Override
-  public boolean shouldExecute() {
-    return true;
-  }
-
-  // Starting from v0.95, StreamPipes will use file name as the unique identifier of files
-  // This migration renames all the files that have duplicate names to ensure uniqueness
-  @Override
-  public void executeMigration() {
-    var fileMetadataStorage = StorageDispatcher.INSTANCE.getNoSqlStore()
-                                                        .getFileMetadataStorage();
-
-    var filesToUpdate = getFilesToUpdate(fileMetadataStorage.getAllFileMetadataDescriptions());
-
-    filesToUpdate.forEach(fileMetadata -> fileMetadataStorage.updateFileMetadata(fileMetadata));
-  }
-
-
-  /**
-   * Takes the list of files and groups all files with the same name together.
-   * The result is a list for each file name that has more than one file associated with it.
-   */
-  private List<List<FileMetadata>> getListsOfFilesWithSameName(List<FileMetadata> filesWithOldName) {
-    var duplicateFileMap = filesWithOldName.stream()
-                                           .collect(
-                                               Collectors.groupingBy(file -> file.getOriginalFilename()
-                                                                                 .toLowerCase()));
-    return duplicateFileMap.values()
-                           .stream()
-                           .filter(files -> files.size() > 1)
-                           .toList();
-  }
-
-  @Override
-  public String getDescription() {
-    return "Rename files with duplicate names.";
-  }
-
-  /**
-   * Takes the files searches for duplicates and renames them and returns the files that must be updated
-   */
-  protected List<FileMetadata> getFilesToUpdate(List<FileMetadata> filesWithOldName) {
-
-    var groupsOfFilesWithSameName = getListsOfFilesWithSameName(filesWithOldName);
-
-    return groupsOfFilesWithSameName.stream()
-                                    .flatMap(filesWithSameName ->
-                                                 renameFilesWithSameName(filesWithSameName).stream())
-                                    .collect(Collectors.toList());
-  }
-
-
-  /**
-   * Takes a list of files with the same name renames them and returns the updated list
-   */
-  private List<FileMetadata> renameFilesWithSameName(List<FileMetadata> filesWithSameName) {
-    return IntStream.range(1, filesWithSameName.size())
-                    .mapToObj(i -> {
-                      var metadata = filesWithSameName.get(i);
-                      return renameFile(metadata, i);
-                    })
-                    .toList();
-  }
-
-
-  /**
-   * Takes a file and renames it with a new name based the number of occurrences of the file name
-   */
-  private FileMetadata renameFile(FileMetadata fileMetadata, int index) {
-    var oldFilename = fileMetadata.getOriginalFilename();
-
-    var fileNameWithoutType = removeFileType(oldFilename);
-    var fileTypeSuffix = getFileType(oldFilename);
-
-    var newFileName = createNewFileName(index, fileNameWithoutType, fileTypeSuffix);
-
-    fileMetadata.setOriginalFilename(newFileName);
-
-    return fileMetadata;
-  }
-
-  /**
-   * Creates the new file name for a file with a duplicate name.
-   */
-  private String createNewFileName(
-      int index,
-      String fileName,
-      String fileType
-  ) {
-    return String.format(
-        "%s(%d)%s",
-        fileName,
-        index + 1,
-        fileType
-    );
-  }
-
-  /**
-   * Returns file name without file type suffix.
-   */
-  private String removeFileType(String fileName) {
-    var indexBeforeFileType = fileName.lastIndexOf('.');
-    return fileName.substring(0, indexBeforeFileType);
-  }
-
-  /**
-   * Returns the file type a given file name.
-   */
-  private String getFileType(String fileName) {
-    var indexBeforeFileType = fileName.lastIndexOf('.');
-    return fileName.substring(indexBeforeFileType);
-  }
-}
diff --git a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v095/MergeFilenamesAndRenameDuplicatesMigration.java b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v095/MergeFilenamesAndRenameDuplicatesMigration.java
new file mode 100644
index 0000000000..1330a109ce
--- /dev/null
+++ b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v095/MergeFilenamesAndRenameDuplicatesMigration.java
@@ -0,0 +1,228 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.service.core.migrations.v095;
+
+import org.apache.streampipes.manager.file.FileHandler;
+import org.apache.streampipes.model.file.FileMetadata;
+import org.apache.streampipes.service.core.migrations.Migration;
+import org.apache.streampipes.storage.api.IFileMetadataStorage;
+import org.apache.streampipes.storage.couchdb.utils.Utils;
+import org.apache.streampipes.storage.management.StorageDispatcher;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.lightcouch.CouchDbClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Scanner;
+
+public class MergeFilenamesAndRenameDuplicatesMigration implements Migration {
+
+  protected static final String ORIGINAL_FILENAME = "originalFilename";
+  protected static final String INTERNAL_FILENAME = "internalFilename";
+  protected static final String ID = "_id";
+  protected static final String FILETYPE = "filetype";
+
+  private CouchDbClient couchDbClient;
+
+  private ObjectMapper mapper = new ObjectMapper();
+
+  private IFileMetadataStorage fileMetadataStorage =
+      StorageDispatcher.INSTANCE.getNoSqlStore().getFileMetadataStorage();
+
+  private FileHandler fileHandler = new FileHandler();
+
+  Logger logger = LoggerFactory.getLogger(MergeFilenamesAndRenameDuplicatesMigration.class);
+
+  protected Map<String, List<FileMetadata>> fileMetadataGroupedByOriginalName = new HashMap<>();
+
+  private boolean isTesting = false;
+
+  public MergeFilenamesAndRenameDuplicatesMigration(boolean testing) {
+    isTesting = testing;
+  }
+
+  public MergeFilenamesAndRenameDuplicatesMigration() {
+    couchDbClient = Utils.getCouchDbFileMetadataClient();
+  }
+
+  // Starting from v0.95, StreamPipes will use a single file name as the unique identifier of files instead of an
+  // internal filename and an original filename. This migration merges them and renames all the files that have
+  // duplicate names to ensure uniqueness
+  @Override
+  public boolean shouldExecute() {
+    return true;
+  }
+
+  @Override
+  public void executeMigration() {
+    var couchDbRawFileMetadata = getCouchDbRawFileMetadata(getAllFileIds(fileMetadataStorage));
+    getFileMetadataToUpdate(couchDbRawFileMetadata);
+    fileMetadataGroupedByOriginalName.forEach(
+        (originalFilename, fileMetadataList) -> update(originalFilename, fileMetadataList));
+  }
+
+  /**
+   * Gets all fileMetadata that need to be updated grouped by originalFilename
+   * key is (possibly) duplicated originalFilename and value is that file's FileMetadata list (if duplicated)
+   */
+  protected void getFileMetadataToUpdate(List<Map<String, Object>> couchDbRawFileMetadata) {
+    couchDbRawFileMetadata.forEach(
+        rawFileMetadata -> checkDuplicateOriginalFilename(rawFileMetadata));
+  }
+
+  /**
+   * Fetches all fileIds stored in CouchDB
+   */
+  private List<String> getAllFileIds(IFileMetadataStorage fileMetadataStorage) {
+    return fileMetadataStorage.getAllFileMetadataDescriptions().stream().map(fileMetadata -> fileMetadata.getFileId())
+        .toList();
+  }
+
+  /**
+   * Takes the list of fileIds and searches for their raw metadata in CouchDB and returns them
+   */
+  private List<Map<String, Object>> getCouchDbRawFileMetadata(List<String> fileIds) {
+    return fileIds.stream()
+        .map(fileId -> convertInputStreamToMap(couchDbClient.find(fileId)))
+        .toList();
+  }
+
+  /**
+   * Converts InputStream (as stored in CouchDB) to Map, if there's an error, constructs a new Map
+   */
+  private Map<String, Object> convertInputStreamToMap(InputStream inputStream) {
+    try {
+      return mapper.readValue(inputStream, Map.class);
+    } catch (Exception e) {
+      Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");
+      String inputStreamString = scanner.hasNext() ? scanner.next() : "";
+      logger.error(
+          "Failed to construct a Map from InputStream stored in CouchDB, the data for this file is likely corrupted, "
+              + "skipping it for migration.\nThe original debug message is: " + e.getMessage()
+              + "\nThe original InputStream is: " + inputStreamString);
+      return new HashMap<>();
+    }
+  }
+
+  /**
+   * Takes raw data stored in CouchDB and constructs fileMetadataGroupedByOriginalName,
+   * key is (possibly) duplicated originalFilename and value is that file's FileMetadata list (if duplicated)
+   */
+  private void checkDuplicateOriginalFilename(Map<String, Object> rawFileMetadata) {
+    // If this file was already migrated or there was an error when converting InputStream to Map, skip it
+    if (rawFileMetadata.containsKey(ORIGINAL_FILENAME)) {
+      var originalFilename = rawFileMetadata.get(ORIGINAL_FILENAME).toString().toLowerCase();
+      if (!fileMetadataGroupedByOriginalName.containsKey(originalFilename)) {
+        fileMetadataGroupedByOriginalName.put(originalFilename, new ArrayList<>());
+      }
+      FileMetadata fileMetadata;
+      if (isTesting) {
+        fileMetadata = new FileMetadata();
+        fileMetadata.setFileId(rawFileMetadata.get(ID).toString());
+        fileMetadata.setFiletype(rawFileMetadata.get(FILETYPE).toString());
+      } else {
+        fileMetadata = fileMetadataStorage.getMetadataById(rawFileMetadata.get(ID).toString());
+      }
+      fileMetadataGroupedByOriginalName.get(originalFilename).add(fileMetadata);
+    }
+  }
+
+  /**
+   * For each of the file, calls updateFileMetadata() and updateLocalFile()
+   */
+  protected void update(String originalFilename, List<FileMetadata> fileMetadataList) {
+    var fileMetadata = fileMetadataList.get(0);
+    // just name the 1st one to its originalFilename
+    if (!isTesting) {
+      var internalFilename = getInternalFilenameFromFileMetadata(fileMetadata);
+      updateLocalFile(internalFilename, originalFilename);
+    }
+    updateFileMetadata(fileMetadata, originalFilename, isTesting);
+    for (int i = 1; i < fileMetadataList.size(); ++i) {
+      fileMetadata = fileMetadataList.get(i);
+      var newFilename = createNewFileName(i, removeFileType(originalFilename), fileMetadata.getFiletype());
+      if (!isTesting) {
+        var internalFilename = getInternalFilenameFromFileMetadata(fileMetadata);
+        updateLocalFile(internalFilename, newFilename);
+      }
+      updateFileMetadata(fileMetadata, newFilename, isTesting);
+    }
+  }
+
+  /**
+   * Updates FileMetadata: sets new merged filename to the given filename
+   */
+  private void updateFileMetadata(FileMetadata fileMetadata, String filename, boolean isTesting) {
+    fileMetadata.setFilename(filename);
+    if (!isTesting) {
+      fileMetadataStorage.updateFileMetadata(fileMetadata);
+    }
+  }
+
+  /**
+   * Updates the file stored locally: renames the file (i.e. before it's using internalFilename, now copy the
+   * InputStream stored and replace internalFilename with the given filename)
+   */
+  private void updateLocalFile(String internalFilename, String filename) {
+    fileHandler.renameFile(internalFilename, filename);
+  }
+
+  /**
+   * Gets the old internalFilename after merging
+   */
+  private String getInternalFilenameFromFileMetadata(FileMetadata fileMetadata) {
+    return convertInputStreamToMap(couchDbClient.find(fileMetadata.getFileId())).get(INTERNAL_FILENAME).toString();
+  }
+
+  /**
+   * Creates the new file name for a file with a duplicate name.
+   */
+  private String createNewFileName(
+      int index,
+      String fileName,
+      String fileType
+  ) {
+    return String.format(
+        "%s(%d).%s",
+        fileName,
+        index + 1,
+        fileType
+    );
+  }
+
+  /**
+   * Returns file name without file type suffix.
+   */
+  private String removeFileType(String fileName) {
+    var indexBeforeFileType = fileName.lastIndexOf('.');
+    return fileName.substring(0, indexBeforeFileType);
+  }
+
+  @Override
+  public String getDescription() {
+    return "Merge internalFilename and originalFilename. Additionally, rename"
+        + "duplicate files to ensure uniqueness.";
+  }
+}
diff --git a/ui/projects/streampipes/platform-services/src/lib/apis/files.service.ts b/ui/projects/streampipes/platform-services/src/lib/apis/files.service.ts
index 2c7f759bb5..e2c1423150 100644
--- a/ui/projects/streampipes/platform-services/src/lib/apis/files.service.ts
+++ b/ui/projects/streampipes/platform-services/src/lib/apis/files.service.ts
@@ -91,7 +91,7 @@ export class FilesService {
         );
     }
 
-    getAllOriginalFilenames(): Observable<string[]> {
+    getAllFilenames(): Observable<string[]> {
         return this.http.get<string[]>(
             this.platformServicesCommons.apiBasePath + '/files/allFilenames',
         );
diff --git a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts
index f1613ff344..6523bfe64a 100644
--- a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts
+++ b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts
@@ -1815,9 +1815,8 @@ export class FileMetadata {
     createdByUser: string;
     fileId: string;
     filetype: string;
-    internalFilename: string;
+    filename: string;
     lastModified: number;
-    originalFilename: string;
     rev: string;
 
     static fromData(data: FileMetadata, target?: FileMetadata): FileMetadata {
@@ -1829,9 +1828,8 @@ export class FileMetadata {
         instance.createdByUser = data.createdByUser;
         instance.fileId = data.fileId;
         instance.filetype = data.filetype;
-        instance.internalFilename = data.internalFilename;
+        instance.filename = data.filename;
         instance.lastModified = data.lastModified;
-        instance.originalFilename = data.originalFilename;
         instance.rev = data.rev;
         return instance;
     }
diff --git a/ui/src/app/assets/dialog/base-asset-links.directive.ts b/ui/src/app/assets/dialog/base-asset-links.directive.ts
index 6ab3baa447..121d60fc27 100644
--- a/ui/src/app/assets/dialog/base-asset-links.directive.ts
+++ b/ui/src/app/assets/dialog/base-asset-links.directive.ts
@@ -98,7 +98,7 @@ export abstract class BaseAssetLinksDirective {
                     a.measureName.localeCompare(b.measureName),
                 );
                 this.files = files.sort((a, b) =>
-                    a.originalFilename.localeCompare(b.originalFilename),
+                    a.filename.localeCompare(b.filename),
                 );
                 this.adapters = adapters.sort((a, b) =>
                     a.name.localeCompare(b.name),
diff --git a/ui/src/app/assets/dialog/manage-asset-links/manage-asset-links-dialog.component.html b/ui/src/app/assets/dialog/manage-asset-links/manage-asset-links-dialog.component.html
index d27e5d4f43..1b48663ffd 100644
--- a/ui/src/app/assets/dialog/manage-asset-links/manage-asset-links-dialog.component.html
+++ b/ui/src/app/assets/dialog/manage-asset-links/manage-asset-links-dialog.component.html
@@ -337,7 +337,7 @@
                             selectLink(
                                 $event.checked,
                                 element.fileId,
-                                element.originalFilename,
+                                element.filename,
                                 'file'
                             )
                         "
diff --git a/ui/src/app/assets/dialog/manage-asset-links/manage-asset-links-dialog.component.ts b/ui/src/app/assets/dialog/manage-asset-links/manage-asset-links-dialog.component.ts
index 0d9b9dc1e9..356acde8fd 100644
--- a/ui/src/app/assets/dialog/manage-asset-links/manage-asset-links-dialog.component.ts
+++ b/ui/src/app/assets/dialog/manage-asset-links/manage-asset-links-dialog.component.ts
@@ -53,7 +53,7 @@ export class SpManageAssetLinksDialogComponent
     elementIdFunction = el => el.elementId;
     fileIdFunction = el => el.fileId;
     nameFunction = el => el.name;
-    filenameFunction = el => el.originalFilename;
+    filenameFunction = el => el.filename;
     measureNameFunction = el => el.measureName;
 
     constructor(
diff --git a/ui/src/app/core-ui/static-properties/static-file-input/static-file-input.component.html b/ui/src/app/core-ui/static-properties/static-file-input/static-file-input.component.html
index 9fe022a957..0ac88d7e42 100644
--- a/ui/src/app/core-ui/static-properties/static-file-input/static-file-input.component.html
+++ b/ui/src/app/core-ui/static-properties/static-file-input/static-file-input.component.html
@@ -62,7 +62,7 @@
                         *ngFor="let fileMetadata of fileMetadata"
                         [value]="fileMetadata"
                     >
-                        {{ fileMetadata.originalFilename }}
+                        {{ fileMetadata.filename }}
                     </mat-option>
                 </mat-autocomplete>
             </mat-form-field>
diff --git a/ui/src/app/core-ui/static-properties/static-file-input/static-file-input.component.ts b/ui/src/app/core-ui/static-properties/static-file-input/static-file-input.component.ts
index 079a7449f6..42b145e6ed 100644
--- a/ui/src/app/core-ui/static-properties/static-file-input/static-file-input.component.ts
+++ b/ui/src/app/core-ui/static-properties/static-file-input/static-file-input.component.ts
@@ -89,15 +89,14 @@ export class StaticFileInputComponent
         return validators;
     }
 
-    fetchFileMetadata(internalFilenameToSelect?: any) {
+    fetchFileMetadata(filenameToSelect?: any) {
         this.filesService
             .getFileMetadata(this.staticProperty.requiredFiletypes)
             .subscribe(fm => {
                 this.fileMetadata = fm;
-                if (internalFilenameToSelect) {
+                if (filenameToSelect) {
                     this.selectedFile = this.fileMetadata.find(
-                        fmi =>
-                            fmi.internalFilename === internalFilenameToSelect,
+                        fmi => fmi.filename === filenameToSelect,
                     );
                     this.selectOption(this.selectedFile);
                     this.emitUpdate(true);
@@ -109,8 +108,7 @@ export class StaticFileInputComponent
                 } else if (this.staticProperty.locationPath) {
                     this.selectedFile = this.fileMetadata.find(
                         fmi =>
-                            fmi.internalFilename ===
-                            this.staticProperty.locationPath,
+                            fmi.filename === this.staticProperty.locationPath,
                     );
                 } else {
                     if (this.fileMetadata.length > 0) {
@@ -136,64 +134,57 @@ export class StaticFileInputComponent
 
     upload() {
         if (this.selectedUploadFile !== undefined) {
-            this.filesService
-                .getAllOriginalFilenames()
-                .subscribe(allFileNames => {
-                    if (
-                        !allFileNames.includes(
-                            this.selectedUploadFile.name.toLowerCase(),
-                        )
-                    ) {
-                        this.uploadStatus = 0;
-                        this.filesService
-                            .uploadFile(this.selectedUploadFile)
-                            .subscribe(
-                                event => {
-                                    if (
-                                        event.type ===
-                                        HttpEventType.UploadProgress
-                                    ) {
-                                        this.uploadStatus = Math.round(
-                                            (100 * event.loaded) / event.total,
-                                        );
-                                    } else if (event instanceof HttpResponse) {
-                                        const internalFilename =
-                                            event.body.internalFilename;
-                                        this.parentForm.controls[
-                                            this.fieldName
-                                        ].setValue(internalFilename);
-                                        this.fetchFileMetadata(
-                                            internalFilename,
-                                        );
-                                    }
-                                },
-                                error => {},
-                            );
-                    } else {
-                        this.openRenameDialog();
-                    }
-                });
+            this.filesService.getAllFilenames().subscribe(allFileNames => {
+                if (
+                    !allFileNames.includes(
+                        this.selectedUploadFile.name.toLowerCase(),
+                    )
+                ) {
+                    this.uploadStatus = 0;
+                    this.filesService
+                        .uploadFile(this.selectedUploadFile)
+                        .subscribe(
+                            event => {
+                                if (
+                                    event.type === HttpEventType.UploadProgress
+                                ) {
+                                    this.uploadStatus = Math.round(
+                                        (100 * event.loaded) / event.total,
+                                    );
+                                } else if (event instanceof HttpResponse) {
+                                    const filename = event.body.filename;
+                                    this.parentForm.controls[
+                                        this.fieldName
+                                    ].setValue(filename);
+                                    this.fetchFileMetadata(filename);
+                                }
+                            },
+                            error => {},
+                        );
+                } else {
+                    this.openRenameDialog();
+                }
+            });
         }
     }
 
     selectOption(fileMetadata: FileMetadata) {
-        this.staticProperty.locationPath = fileMetadata.internalFilename;
+        this.staticProperty.locationPath = fileMetadata.filename;
         const valid: boolean =
-            fileMetadata.internalFilename !== '' ||
-            fileMetadata.internalFilename !== undefined;
+            fileMetadata.filename !== '' || fileMetadata.filename !== undefined;
         this.updateEmitter.emit(
             new ConfigurationInfo(this.staticProperty.internalName, valid),
         );
     }
 
     displayFn(fileMetadata: FileMetadata) {
-        return fileMetadata ? fileMetadata.originalFilename : '';
+        return fileMetadata ? fileMetadata.filename : '';
     }
 
     onStatusChange(status: any) {}
 
     onValueChange(value: any) {
-        this.staticProperty.locationPath = value.internalFilename;
+        this.staticProperty.locationPath = value.filename;
         this.parentForm.updateValueAndValidity();
     }
 
diff --git a/ui/src/app/files/components/file-overview/file-overview.component.html b/ui/src/app/files/components/file-overview/file-overview.component.html
index 00ab831e65..ff6a4796cd 100644
--- a/ui/src/app/files/components/file-overview/file-overview.component.html
+++ b/ui/src/app/files/components/file-overview/file-overview.component.html
@@ -27,7 +27,7 @@
             <ng-container matColumnDef="filename">
                 <th mat-header-cell *matHeaderCellDef>Filename</th>
                 <td mat-cell *matCellDef="let fileMetadata">
-                    <h4>{{ fileMetadata.originalFilename }}</h4>
+                    <h4>{{ fileMetadata.filename }}</h4>
                 </td>
             </ng-container>
             <ng-container matColumnDef="filetype">
diff --git a/ui/src/app/files/components/file-overview/file-overview.component.ts b/ui/src/app/files/components/file-overview/file-overview.component.ts
index 472de9117d..dc8ca5c4fc 100644
--- a/ui/src/app/files/components/file-overview/file-overview.component.ts
+++ b/ui/src/app/files/components/file-overview/file-overview.component.ts
@@ -84,11 +84,9 @@ export class FileOverviewComponent implements OnInit {
     }
 
     downloadFile(fileMetadata: FileMetadata) {
-        this.filesService
-            .getFile(fileMetadata.internalFilename)
-            .subscribe(response => {
-                saveAs(response, fileMetadata.originalFilename);
-            });
+        this.filesService.getFile(fileMetadata.filename).subscribe(response => {
+            saveAs(response, fileMetadata.filename);
+        });
     }
 
     getFileColor(fileType: string) {
diff --git a/ui/src/app/files/dialog/file-rename/file-rename-dialog.component.html b/ui/src/app/files/dialog/file-rename/file-rename-dialog.component.html
index 121faeac02..8c849b8016 100644
--- a/ui/src/app/files/dialog/file-rename/file-rename-dialog.component.html
+++ b/ui/src/app/files/dialog/file-rename/file-rename-dialog.component.html
@@ -25,9 +25,7 @@
                         <b>
                             <h4>
                                 A file with the same name already exists. Please
-                                give your file a new name or select one of the
-                                existing files. it from existing files or rename
-                                it:
+                                select from existing files or rename it:
                             </h4>
                         </b>
                         <div class="input-group">
diff --git a/ui/src/app/files/dialog/file-upload/file-upload-dialog.component.ts b/ui/src/app/files/dialog/file-upload/file-upload-dialog.component.ts
index 81a26890ad..328cfe8494 100644
--- a/ui/src/app/files/dialog/file-upload/file-upload-dialog.component.ts
+++ b/ui/src/app/files/dialog/file-upload/file-upload-dialog.component.ts
@@ -58,7 +58,7 @@ export class FileUploadDialogComponent {
     }
 
     store() {
-        this.filesService.getAllOriginalFilenames().subscribe(data => {
+        this.filesService.getAllFilenames().subscribe(data => {
             const allFileNames = new Set(data);
             this.duplicateFileNames = this.fileNames.filter(fileName =>
                 allFileNames.has(fileName.toLowerCase()),


Test patch:
diff --git a/streampipes-data-explorer-commons/src/test/java/org/apache/streampipes/dataexplorer/commons/influx/serializer/TestRawFieldSerializer.java b/streampipes-data-explorer-commons/src/test/java/org/apache/streampipes/dataexplorer/commons/influx/serializer/TestRawFieldSerializer.java
index c9b85695d1..c31970644c 100644
--- a/streampipes-data-explorer-commons/src/test/java/org/apache/streampipes/dataexplorer/commons/influx/serializer/TestRawFieldSerializer.java
+++ b/streampipes-data-explorer-commons/src/test/java/org/apache/streampipes/dataexplorer/commons/influx/serializer/TestRawFieldSerializer.java
@@ -28,7 +28,7 @@ import static org.junit.Assert.assertEquals;
 
 public class TestRawFieldSerializer {
   private RawFieldSerializer rawFieldSerializer = new RawFieldSerializer();
-  private Map<String, Object> primitives = new HashMap<String, Object>();
+  private Map<String, Object> primitives = new HashMap<>();
 
   public TestRawFieldSerializer() {
     primitives.put("Integer", 1);
@@ -42,7 +42,7 @@ public class TestRawFieldSerializer {
   // Test able to deserialize back the original data
   @Test
   public void testRawFieldSerializerListInMap() {
-    var rawListField = new ArrayList<Object>();
+    var rawListField = new ArrayList<>();
     rawListField.addAll(primitives.values());
 
     var rawNestedField = new HashMap<String, Object>();
@@ -59,7 +59,7 @@ public class TestRawFieldSerializer {
     var rawNestedField = new HashMap<String, Object>();
     rawNestedField.putAll(primitives);
 
-    var rawListField = new ArrayList<Object>();
+    var rawListField = new ArrayList<>();
     rawListField.addAll(primitives.values());
     rawListField.add(rawNestedField);
 
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/test/java/org/apache/streampipes/connect/iiot/adapters/migrations/RosBridgeAdapterMigrationV1Test.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/test/java/org/apache/streampipes/connect/iiot/adapters/migrations/RosBridgeAdapterMigrationV1Test.java
new file mode 100644
index 0000000000..f6956da679
--- /dev/null
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/test/java/org/apache/streampipes/connect/iiot/adapters/migrations/RosBridgeAdapterMigrationV1Test.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.connect.iiot.adapters.migrations;
+
+import org.apache.streampipes.connect.iiot.adapters.migrations.config.RosBridgeAdapterVersionedConfig;
+import org.apache.streampipes.extensions.api.extractor.IStaticPropertyExtractor;
+import org.apache.streampipes.model.connect.adapter.AdapterDescription;
+import org.apache.streampipes.vocabulary.XSD;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.net.URI;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+
+public class RosBridgeAdapterMigrationV1Test {
+
+  private RosBridgeAdapterMigrationV1 migrationV1;
+
+  @Before
+  public void setUp() {
+    migrationV1 = new RosBridgeAdapterMigrationV1();
+  }
+
+  @Test
+  public void testMigrationV1(){
+    var rosBridgeAdapterDescriptionV0 = RosBridgeAdapterVersionedConfig.getRosBridgeAdapterDescriptionV0();
+    var extractorMock = mock(IStaticPropertyExtractor.class);
+
+    var rosBridgeAdapterDescriptionV1 = migrationV1.migrate(rosBridgeAdapterDescriptionV0 , extractorMock);
+
+    var typeOfPortProperty = getTypeOfPortProperty(rosBridgeAdapterDescriptionV1.element());
+    assertEquals(XSD.INTEGER, typeOfPortProperty);
+  }
+
+  private URI getTypeOfPortProperty(AdapterDescription adapterDescription) {
+    return migrationV1
+        .extractPortProperty(adapterDescription)
+        .getRequiredDatatype();
+  }
+
+}
\ No newline at end of file
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/test/java/org/apache/streampipes/connect/iiot/adapters/migrations/config/RosBridgeAdapterVersionedConfig.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/test/java/org/apache/streampipes/connect/iiot/adapters/migrations/config/RosBridgeAdapterVersionedConfig.java
new file mode 100644
index 0000000000..ef07a13a9e
--- /dev/null
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/test/java/org/apache/streampipes/connect/iiot/adapters/migrations/config/RosBridgeAdapterVersionedConfig.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.connect.iiot.adapters.migrations.config;
+
+import org.apache.streampipes.connect.iiot.adapters.ros.RosBridgeAdapter;
+import org.apache.streampipes.model.AdapterType;
+import org.apache.streampipes.model.connect.adapter.AdapterDescription;
+import org.apache.streampipes.sdk.builder.adapter.AdapterConfigurationBuilder;
+import org.apache.streampipes.sdk.helpers.Labels;
+import org.apache.streampipes.sdk.helpers.Locales;
+import org.apache.streampipes.sdk.utils.Assets;
+
+import java.util.Arrays;
+
+import static org.apache.streampipes.connect.iiot.adapters.ros.RosBridgeAdapter.ID;
+import static org.apache.streampipes.connect.iiot.adapters.ros.RosBridgeAdapter.ROS_HOST_KEY;
+import static org.apache.streampipes.connect.iiot.adapters.ros.RosBridgeAdapter.ROS_PORT_KEY;
+import static org.apache.streampipes.connect.iiot.adapters.ros.RosBridgeAdapter.TOPIC_KEY;
+
+public class RosBridgeAdapterVersionedConfig {
+
+  public static AdapterDescription getRosBridgeAdapterDescriptionV0(){
+    return AdapterConfigurationBuilder.create(ID, 0, RosBridgeAdapter::new)
+      .withLocales(Locales.EN)
+      .withAssets(Assets.DOCUMENTATION, Assets.ICON)
+      .withCategory(AdapterType.Manufacturing)
+      .requiredTextParameter(Labels.withId(ROS_HOST_KEY))
+      .requiredTextParameter(Labels.withId(ROS_PORT_KEY))
+      .requiredSingleValueSelectionFromContainer(
+        Labels.withId(TOPIC_KEY), Arrays.asList(ROS_HOST_KEY, ROS_PORT_KEY))
+      .buildConfiguration().getAdapterDescription();
+  }
+
+  public static AdapterDescription getRosBridgeAdapterDescriptionV1(){
+    return AdapterConfigurationBuilder.create(ID, 1, RosBridgeAdapter::new)
+      .withLocales(Locales.EN)
+      .withAssets(Assets.DOCUMENTATION, Assets.ICON)
+      .withCategory(AdapterType.Manufacturing)
+      .requiredTextParameter(Labels.withId(ROS_HOST_KEY))
+      .requiredIntegerParameter(Labels.withId(ROS_PORT_KEY))
+      .requiredSingleValueSelectionFromContainer(
+        Labels.withId(TOPIC_KEY), Arrays.asList(ROS_HOST_KEY, ROS_PORT_KEY))
+      .buildConfiguration().getAdapterDescription();
+  }
+}
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/test/java/org/apache/streampipes/extensions/connectors/opcua/migration/OpcUaAdapterMigrationV2Test.java b/streampipes-extensions/streampipes-connectors-opcua/src/test/java/org/apache/streampipes/extensions/connectors/opcua/migration/OpcUaAdapterMigrationV2Test.java
new file mode 100644
index 0000000000..2b7581b3c0
--- /dev/null
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/test/java/org/apache/streampipes/extensions/connectors/opcua/migration/OpcUaAdapterMigrationV2Test.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.extensions.connectors.opcua.migration;
+
+import org.apache.streampipes.extensions.api.extractor.IStaticPropertyExtractor;
+import org.apache.streampipes.extensions.connectors.opcua.migration.config.OpcUaAdapterVersionedConfig;
+import org.apache.streampipes.model.connect.adapter.AdapterDescription;
+import org.apache.streampipes.vocabulary.XSD;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.net.URI;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+public class OpcUaAdapterMigrationV2Test {
+
+  private OpcUaAdapterMigrationV2 migrationV2;
+
+  @Before
+  public void setUp() {
+    migrationV2 = new OpcUaAdapterMigrationV2();
+  }
+
+  @Test
+  public void testOPCUAAdapterMigrationV2() {
+    var adapterDescriptionV1 = OpcUaAdapterVersionedConfig.getOpcUaAdapterDescriptionV1();
+    var extractorMock = mock(IStaticPropertyExtractor.class);
+
+    var adapterDescriptionV2 = migrationV2.migrate(adapterDescriptionV1, extractorMock)
+                                          .element();
+
+    assertEquals(XSD.INTEGER, getTypeOfPortProperty(adapterDescriptionV2));
+  }
+
+  private URI getTypeOfPortProperty(AdapterDescription adapterDescriptionV2) {
+    return migrationV2
+        .extractPortProperty(adapterDescriptionV2)
+        .getRequiredDatatype();
+  }
+}
\ No newline at end of file
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/test/java/org/apache/streampipes/extensions/connectors/opcua/migration/config/OpcUaAdapterVersionedConfig.java b/streampipes-extensions/streampipes-connectors-opcua/src/test/java/org/apache/streampipes/extensions/connectors/opcua/migration/config/OpcUaAdapterVersionedConfig.java
new file mode 100644
index 0000000000..fbf1514957
--- /dev/null
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/test/java/org/apache/streampipes/extensions/connectors/opcua/migration/config/OpcUaAdapterVersionedConfig.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.extensions.connectors.opcua.migration.config;
+
+import org.apache.streampipes.extensions.connectors.opcua.adapter.OpcUaAdapter;
+import org.apache.streampipes.model.AdapterType;
+import org.apache.streampipes.model.connect.adapter.AdapterDescription;
+import org.apache.streampipes.sdk.StaticProperties;
+import org.apache.streampipes.sdk.builder.adapter.AdapterConfigurationBuilder;
+import org.apache.streampipes.sdk.helpers.Alternatives;
+import org.apache.streampipes.sdk.helpers.Labels;
+import org.apache.streampipes.sdk.helpers.Locales;
+import org.apache.streampipes.sdk.utils.Assets;
+
+import java.util.List;
+
+import static org.apache.streampipes.extensions.connectors.opcua.adapter.OpcUaAdapter.ID;
+import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.ACCESS_MODE;
+import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.ADAPTER_TYPE;
+import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.AVAILABLE_NODES;
+import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.HOST_PORT;
+import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.OPC_HOST;
+import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.OPC_HOST_OR_URL;
+import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.OPC_SERVER_HOST;
+import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.OPC_SERVER_PORT;
+import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.OPC_SERVER_URL;
+import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.OPC_URL;
+import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.PASSWORD;
+import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.PULLING_INTERVAL;
+import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.PULL_MODE;
+import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.SUBSCRIPTION_MODE;
+import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.UNAUTHENTICATED;
+import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.USERNAME;
+import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.USERNAME_GROUP;
+
+public class OpcUaAdapterVersionedConfig {
+
+  public static AdapterDescription getOpcUaAdapterDescriptionV1(){
+    var builder = AdapterConfigurationBuilder.create(ID, 1, OpcUaAdapter::new)
+        .withAssets(Assets.DOCUMENTATION, Assets.ICON)
+        .withLocales(Locales.EN)
+        .withCategory(AdapterType.Generic, AdapterType.Manufacturing)
+        .requiredAlternatives(Labels.withId(ADAPTER_TYPE),
+        Alternatives.from(Labels.withId(PULL_MODE),
+          StaticProperties.integerFreeTextProperty(
+            Labels.withId(PULLING_INTERVAL))),
+        Alternatives.from(Labels.withId(SUBSCRIPTION_MODE)));
+    var dependsOn = List.of(
+        ADAPTER_TYPE.name(),
+        ACCESS_MODE.name(),
+        OPC_HOST_OR_URL.name());
+    builder
+        .requiredAlternatives(Labels.withId(ACCESS_MODE),
+        Alternatives.from(Labels.withId(UNAUTHENTICATED)),
+        Alternatives.from(Labels.withId(USERNAME_GROUP),
+          StaticProperties.group(
+            Labels.withId(USERNAME_GROUP),
+            StaticProperties.stringFreeTextProperty(
+              Labels.withId(USERNAME)),
+            StaticProperties.secretValue(Labels.withId(PASSWORD))
+          ))
+      )
+        .requiredAlternatives(Labels.withId(OPC_HOST_OR_URL),
+        Alternatives.from(
+          Labels.withId(OPC_URL),
+          StaticProperties.stringFreeTextProperty(
+            Labels.withId(OPC_SERVER_URL), "opc.tcp://localhost:4840"))
+        ,
+        Alternatives.from(Labels.withId(OPC_HOST),
+          StaticProperties.group(
+            Labels.withId(HOST_PORT),
+            StaticProperties.stringFreeTextProperty(
+              Labels.withId(OPC_SERVER_HOST)),
+            StaticProperties.stringFreeTextProperty(
+              Labels.withId(OPC_SERVER_PORT))
+          ))
+      )
+        .requiredRuntimeResolvableTreeInput(
+          Labels.withId(AVAILABLE_NODES.name()),
+          dependsOn,
+          true,
+          true
+        );
+    return builder.buildConfiguration().getAdapterDescription();
+  }
+}
diff --git a/streampipes-extensions/streampipes-connectors-plc/src/test/java/org/apache/streampipes/extensions/connectors/plc/adapter/migration/Plc4xModbusAdapterMigrationTest.java b/streampipes-extensions/streampipes-connectors-plc/src/test/java/org/apache/streampipes/extensions/connectors/plc/adapter/migration/Plc4xModbusAdapterMigrationTest.java
new file mode 100644
index 0000000000..fcb7708a5c
--- /dev/null
+++ b/streampipes-extensions/streampipes-connectors-plc/src/test/java/org/apache/streampipes/extensions/connectors/plc/adapter/migration/Plc4xModbusAdapterMigrationTest.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.extensions.connectors.plc.adapter.migration;
+
+import org.apache.streampipes.extensions.api.extractor.IStaticPropertyExtractor;
+import org.apache.streampipes.extensions.connectors.plc.adapter.migration.config.Plc4xModbusAdapterVersionedConfig;
+import org.apache.streampipes.model.connect.adapter.AdapterDescription;
+import org.apache.streampipes.vocabulary.XSD;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.net.URI;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+public class Plc4xModbusAdapterMigrationTest {
+
+  private Plc4xModbusAdapterMigrationV1 migrationV1;
+
+  @Before
+  public void setUp() {
+    migrationV1 = new Plc4xModbusAdapterMigrationV1();
+  }
+
+  @Test
+  public void testMigrationV1() {
+    var modbusAdapterDescriptionV0 = Plc4xModbusAdapterVersionedConfig.getPlc4xModbusAdapterDescriptionV0();
+    var extractorMock = mock(IStaticPropertyExtractor.class);
+
+    var modbusAdapterDescriptionV1 = migrationV1.migrate(modbusAdapterDescriptionV0, extractorMock);
+
+    var typeOfPortProperty = getTypeOfPortProperty(modbusAdapterDescriptionV1.element());
+    assertEquals(XSD.INTEGER, typeOfPortProperty);
+  }
+
+  private URI getTypeOfPortProperty(AdapterDescription adapterDescription) {
+    return migrationV1
+        .extractPortProperty(adapterDescription)
+        .getRequiredDatatype();
+  }
+
+}
\ No newline at end of file
diff --git a/streampipes-extensions/streampipes-connectors-plc/src/test/java/org/apache/streampipes/extensions/connectors/plc/adapter/migration/config/Plc4xModbusAdapterVersionedConfig.java b/streampipes-extensions/streampipes-connectors-plc/src/test/java/org/apache/streampipes/extensions/connectors/plc/adapter/migration/config/Plc4xModbusAdapterVersionedConfig.java
new file mode 100644
index 0000000000..11509f5d5d
--- /dev/null
+++ b/streampipes-extensions/streampipes-connectors-plc/src/test/java/org/apache/streampipes/extensions/connectors/plc/adapter/migration/config/Plc4xModbusAdapterVersionedConfig.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.extensions.connectors.plc.adapter.migration.config;
+
+import org.apache.streampipes.extensions.connectors.plc.adapter.modbus.Plc4xModbusAdapter;
+import org.apache.streampipes.model.AdapterType;
+import org.apache.streampipes.model.connect.adapter.AdapterDescription;
+import org.apache.streampipes.sdk.StaticProperties;
+import org.apache.streampipes.sdk.builder.adapter.AdapterConfigurationBuilder;
+import org.apache.streampipes.sdk.helpers.Labels;
+import org.apache.streampipes.sdk.helpers.Locales;
+import org.apache.streampipes.sdk.helpers.Options;
+import org.apache.streampipes.sdk.utils.Assets;
+
+import static org.apache.streampipes.extensions.connectors.plc.adapter.modbus.Plc4xModbusAdapter.ID;
+import static org.apache.streampipes.extensions.connectors.plc.adapter.modbus.Plc4xModbusAdapter.PLC_IP;
+import static org.apache.streampipes.extensions.connectors.plc.adapter.modbus.Plc4xModbusAdapter.PLC_NODES;
+import static org.apache.streampipes.extensions.connectors.plc.adapter.modbus.Plc4xModbusAdapter.PLC_NODE_ADDRESS;
+import static org.apache.streampipes.extensions.connectors.plc.adapter.modbus.Plc4xModbusAdapter.PLC_NODE_ID;
+import static org.apache.streampipes.extensions.connectors.plc.adapter.modbus.Plc4xModbusAdapter.PLC_NODE_RUNTIME_NAME;
+import static org.apache.streampipes.extensions.connectors.plc.adapter.modbus.Plc4xModbusAdapter.PLC_NODE_TYPE;
+import static org.apache.streampipes.extensions.connectors.plc.adapter.modbus.Plc4xModbusAdapter.PLC_PORT;
+
+public class Plc4xModbusAdapterVersionedConfig {
+
+  public static AdapterDescription getPlc4xModbusAdapterDescriptionV0() {
+    return AdapterConfigurationBuilder.create(ID, 0, Plc4xModbusAdapter::new)
+                                      .withLocales(Locales.EN)
+                                      .withAssets(Assets.DOCUMENTATION, Assets.ICON)
+                                      .withCategory(AdapterType.Manufacturing)
+                                      .requiredTextParameter(Labels.withId(PLC_IP))
+                                      .requiredTextParameter(Labels.withId(PLC_PORT))
+                                      .requiredTextParameter(Labels.withId(PLC_NODE_ID))
+                                      .requiredCollection(
+                                          Labels.withId(PLC_NODES),
+                                          StaticProperties.stringFreeTextProperty(Labels.withId(PLC_NODE_RUNTIME_NAME)),
+                                          StaticProperties.integerFreeTextProperty(Labels.withId(PLC_NODE_ADDRESS)),
+                                          StaticProperties.singleValueSelection(
+                                              Labels.withId(PLC_NODE_TYPE),
+                                              Options.from("DiscreteInput", "Coil", "InputRegister", "HoldingRegister")
+                                          )
+                                      )
+                                      .buildConfiguration()
+                                      .getAdapterDescription();
+  }
+
+}
diff --git a/streampipes-service-core/src/test/java/org/apache/streampipes/service/core/migrations/v095/DuplicateFilesRenameMigrationTest.java b/streampipes-service-core/src/test/java/org/apache/streampipes/service/core/migrations/v095/DuplicateFilesRenameMigrationTest.java
deleted file mode 100644
index 18b35846f6..0000000000
--- a/streampipes-service-core/src/test/java/org/apache/streampipes/service/core/migrations/v095/DuplicateFilesRenameMigrationTest.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.apache.streampipes.service.core.migrations.v095;
-
-
-import org.apache.streampipes.model.file.FileMetadata;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.List;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-public class DuplicateFilesRenameMigrationTest {
-
-  private DuplicateFilesRenameMigration migration;
-
-  private static final String FILE_NAME = "file.txt";
-  private static final String NEW_FILE_NAME_2 = "file(2).txt";
-  private static final String NEW_FILE_NAME_3 = "file(3).txt";
-
-  @Before
-  public void setUp() {
-    migration = new DuplicateFilesRenameMigration();
-  }
-
-  @Test
-  public void getFilesToUpdateHandlesNoDuplicates() {
-    var filesWithOldName = createFiles(List.of(
-        FILE_NAME
-    ));
-    var result = migration.getFilesToUpdate(filesWithOldName);
-    assertTrue(result.isEmpty());
-  }
-
-  @Test
-  public void getFilesToUpdateHandlesSingleDuplicate() {
-    var filesWithOldName = createFiles(List.of(
-        FILE_NAME,
-        FILE_NAME
-    ));
-    var result = migration.getFilesToUpdate(filesWithOldName);
-    assertEquals(1, result.size());
-    assertEquals(
-        NEW_FILE_NAME_2,
-        result.get(0)
-              .getOriginalFilename()
-    );
-  }
-
-  @Test
-  public void getFilesToUpdateHandlesMultipleDuplicates() {
-    var filesWithOldName = createFiles(List.of(
-        FILE_NAME,
-        FILE_NAME,
-        FILE_NAME
-    ));
-    var result = migration.getFilesToUpdate(filesWithOldName);
-    assertEquals(2, result.size());
-    assertEquals(
-        NEW_FILE_NAME_2,
-        result.get(0)
-              .getOriginalFilename()
-    );
-    assertEquals(
-        NEW_FILE_NAME_3,
-        result.get(1)
-              .getOriginalFilename()
-    );
-  }
-
-  @Test
-  public void getFilesToUpdateHandlesMixedDuplicatesAndUniqueFiles() {
-    var filesWithOldName = createFiles(List.of(
-        FILE_NAME,
-        FILE_NAME,
-        "unique.txt"
-    ));
-    var result = migration.getFilesToUpdate(filesWithOldName);
-    assertEquals(1, result.size());
-    assertEquals(
-        NEW_FILE_NAME_2,
-        result.get(0)
-              .getOriginalFilename()
-    );
-  }
-
-  /**
-   * Creates a list of FileMetadata objects from a list of file names.
-   */
-  private List<FileMetadata> createFiles(List<String> fileNames) {
-    return fileNames.stream()
-                    .map(this::createFileMetadata)
-                    .toList();
-  }
-
-  private FileMetadata createFileMetadata(String fileName) {
-    var fileMetadata = new FileMetadata();
-    fileMetadata.setOriginalFilename(fileName);
-    return fileMetadata;
-  }
-}
\ No newline at end of file
diff --git a/streampipes-service-core/src/test/java/org/apache/streampipes/service/core/migrations/v095/MergeFilenamesAndRenameDuplicatesMigrationTest.java b/streampipes-service-core/src/test/java/org/apache/streampipes/service/core/migrations/v095/MergeFilenamesAndRenameDuplicatesMigrationTest.java
new file mode 100644
index 0000000000..b11fca54d0
--- /dev/null
+++ b/streampipes-service-core/src/test/java/org/apache/streampipes/service/core/migrations/v095/MergeFilenamesAndRenameDuplicatesMigrationTest.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.service.core.migrations.v095;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.streampipes.service.core.migrations.v095.MergeFilenamesAndRenameDuplicatesMigration.FILETYPE;
+import static org.apache.streampipes.service.core.migrations.v095.MergeFilenamesAndRenameDuplicatesMigration.ID;
+import static org.apache.streampipes.service.core.migrations.v095.MergeFilenamesAndRenameDuplicatesMigration.INTERNAL_FILENAME;
+import static org.apache.streampipes.service.core.migrations.v095.MergeFilenamesAndRenameDuplicatesMigration.ORIGINAL_FILENAME;
+import static org.junit.Assert.assertEquals;
+
+public class MergeFilenamesAndRenameDuplicatesMigrationTest {
+
+  private static final Map RAW_FILEMETADATA_1 = new HashMap<String, Object>() {
+    {
+      put(ID, "id1");
+      put(ORIGINAL_FILENAME, "file.txt");
+      put(INTERNAL_FILENAME, "doesn't matter");
+      put(FILETYPE, "txt");
+    }
+  };
+
+  private static final Map RAW_FILEMETADATA_2 = new HashMap<String, Object>() {
+    {
+      put(ID, "id2");
+      put(ORIGINAL_FILENAME, "FILE.txt");
+      put(INTERNAL_FILENAME, "doesn't matter");
+      put(FILETYPE, "TXT");
+    }
+  };
+
+  private static final Map RAW_FILEMETADATA_3 = new HashMap<String, Object>() {
+    {
+      put(ID, "id2");
+      put(ORIGINAL_FILENAME, "fIlE.TxT");
+      put(INTERNAL_FILENAME, "doesn't matter");
+      put(FILETYPE, "TxT");
+    }
+  };
+
+  private static final Map RAW_FILEMETADATA_4 = new HashMap<String, Object>() {
+    {
+      put(ID, "id3");
+      put(ORIGINAL_FILENAME, "file.csv");
+      put(INTERNAL_FILENAME, "doesn't matter");
+      put(FILETYPE, "csv");
+    }
+  };
+
+  private List<Map<String, Object>> couchDbRawFileMetadata;
+
+  private MergeFilenamesAndRenameDuplicatesMigration migration;
+
+  @Before
+  public void setUp() {
+    migration = new MergeFilenamesAndRenameDuplicatesMigration(true);
+    couchDbRawFileMetadata = new ArrayList<>() {
+      {
+        add(RAW_FILEMETADATA_1);
+        add(RAW_FILEMETADATA_2);
+        add(RAW_FILEMETADATA_3);
+        add(RAW_FILEMETADATA_4);
+      }
+    };
+  }
+
+  @Test
+  public void testMigration() {
+    // Test that the migration successfully groups FileMetadata by originalFilename
+    migration.getFileMetadataToUpdate(couchDbRawFileMetadata);
+    assertEquals(2, migration.fileMetadataGroupedByOriginalName.size());
+    assertEquals(3, migration.fileMetadataGroupedByOriginalName.get("file.txt").size());
+    assertEquals(1, migration.fileMetadataGroupedByOriginalName.get("file.csv").size());
+
+    // Test that the migration successfully renames duplicate files
+    migration.fileMetadataGroupedByOriginalName.forEach(
+        (originalFilename, fileMetadataList) -> migration.update(originalFilename, fileMetadataList));
+    assertEquals("file.txt", migration.fileMetadataGroupedByOriginalName.get("file.txt").get(0).getFilename());
+    assertEquals("file(2).TXT", migration.fileMetadataGroupedByOriginalName.get("file.txt").get(1).getFilename());
+    assertEquals("file(3).TxT", migration.fileMetadataGroupedByOriginalName.get("file.txt").get(2).getFilename());
+    assertEquals("file.csv", migration.fileMetadataGroupedByOriginalName.get("file.csv").get(0).getFilename());
+  }
+}
\ No newline at end of file