From 591b6440e6f1e5c8586e4baa89334ba6ed3f2905 Mon Sep 17 00:00:00 2001
From: Simon Templer <simon@wetransform.to>
Date: Mon, 5 Feb 2024 11:27:01 +0100
Subject: [PATCH 01/13] WIP test case

---
 .../test/impl/JoinRetainConditionsTest.groovy |  91 +++++++++++++++
 .../ABC-to-S.halex                            |  27 +++++
 .../ABC-to-S.halex.alignment.xml              | 106 ++++++++++++++++++
 .../ABC-to-S.halex.styles.sld                 |   3 +
 .../retain-join-conditions-retype/ABC.groovy  |  15 +++
 .../S-to-T.halex                              |  27 +++++
 .../S-to-T.halex.alignment.xml                |  67 +++++++++++
 .../S-to-T.halex.styles.sld                   |   3 +
 .../retain-join-conditions-retype/S.groovy    |   7 ++
 .../retain-join-conditions-retype/T.groovy    |   7 ++
 10 files changed, 353 insertions(+)
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/JoinRetainConditionsTest.groovy
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/ABC-to-S.halex
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/ABC-to-S.halex.alignment.xml
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/ABC-to-S.halex.styles.sld
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/ABC.groovy
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/S-to-T.halex
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/S-to-T.halex.alignment.xml
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/S-to-T.halex.styles.sld
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/S.groovy
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/T.groovy

diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/JoinRetainConditionsTest.groovy b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/JoinRetainConditionsTest.groovy
new file mode 100644
index 0000000000..89be9a0ac5
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/JoinRetainConditionsTest.groovy
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2024 wetransform GmbH
+ * 
+ * All rights reserved. This program and the accompanying materials are made
+ * available under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 3 of the License,
+ * or (at your option) any later version.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this distribution. If not, see <http://www.gnu.org/licenses/>.
+ * 
+ * Contributors:
+ *     wetransform GmbH <http://www.wetransform.to>
+ */
+
+package eu.esdihumboldt.hale.common.align.merge.test.impl
+
+import static org.junit.Assert.*
+
+import org.junit.Test
+
+import eu.esdihumboldt.hale.common.align.io.impl.JaxbAlignmentIO
+import eu.esdihumboldt.hale.common.align.merge.test.AbstractMergeCellMigratorTest
+import eu.esdihumboldt.hale.common.align.model.functions.join.JoinParameter
+
+/**
+ * Test for retaining conditions during Merge.
+ * 
+ * @author Simon Templer
+ */
+class JoinRetainConditionsTest extends AbstractMergeCellMigratorTest {
+
+	@Test
+	void testRetypeCondition() {
+		def toMigrate = this.class.getResource('/testcases/retain-join-conditions-retype/S-to-T.halex')
+		def cellId = 'SabcT'
+
+		def matching = this.class.getResource('/testcases/retain-join-conditions-retype/ABC-to-S.halex')
+
+		def migrated = merge(cellId, toMigrate, matching)
+
+		// do checks
+
+		// filter
+		assertEquals(1, migrated.size())
+		JaxbAlignmentIO.printCell(migrated[0], System.out)
+
+		/*
+		 assertNotNull(migrated[0].source)
+		 assertEquals(2, migrated[0].source.size())
+		 Collection<? extends Entity> source = migrated[0].source.values()
+		 ((Collection<Entity>) source).each { e ->
+		 def filter = e.definition.filter
+		 if (e.definition.definition.displayName == 'A1') {
+		 // expect filter to have been propagated to A1
+		 assertNotNull(filter)
+		 //assertEquals('a1 <> \'NIL\'', filter.filterTerm)
+		 assertEquals('NOT (a1 = \'NIL\')', filter.filterTerm)
+		 }
+		 else {
+		 // no filter should be present
+		 assertNull(filter)
+		 }
+		 }
+		 JoinParameter param = CellUtil.getFirstParameter(migrated[0], JoinFunction.PARAMETER_JOIN).as(JoinParameter)
+		 assertJoinOrder(param, ['A1', 'A2'])
+		 // there should be a condition on the join focus, also in the order
+		 assertNotNull(param.types[0].filter)
+		 // there should also be a filter in the condition
+		 def base = param.conditions.collect { it.baseProperty }.findAll { it.type.displayName == 'A1' }.toList()
+		 assertEquals(1, base.size())
+		 assertNotNull(base[0].filter)
+		 // there should be a message about the condition having been translated automatically
+		 def messages = getMigrationMessages(migrated[0])
+		 assertTrue(messages.any { msg ->
+		 msg.text.toLowerCase().contains('condition')
+		 })
+		 */
+	}
+
+	// helpers
+
+	void assertJoinOrder(JoinParameter param, List<String> expected) {
+		def names = []
+		param.types?.each { type ->
+			names << type.type.name.localPart
+		}
+
+		assertEquals(expected, names)
+	}
+}
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/ABC-to-S.halex b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/ABC-to-S.halex
new file mode 100644
index 0000000000..1ec3f6a8e7
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/ABC-to-S.halex
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<hale-project version="5.1.0.qualifier">
+    <name>ABC to S (matching)</name>
+    <author>Simon Templer</author>
+    <created>2018-01-10T16:17:32.757+01:00</created>
+    <modified>2024-02-05T11:12:38.984+01:00</modified>
+    <save-config action-id="project.save" provider-id="eu.esdihumboldt.hale.io.project.hale25.xml.writer">
+        <setting name="charset">UTF-8</setting>
+        <setting name="projectFiles.separate">false</setting>
+        <setting name="contentType">eu.esdihumboldt.hale.io.project.hale25.xml</setting>
+        <setting name="target">file:/home/simon/repos/hale/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/ABC-to-S.halex</setting>
+    </save-config>
+    <resource action-id="eu.esdihumboldt.hale.io.schema.read.source" provider-id="eu.esdihumboldt.hale.io.schemabuilder">
+        <setting name="charset">UTF-8</setting>
+        <setting name="resourceId">6534b899-7ef3-4ead-8e4f-9af3af16f030</setting>
+        <setting name="source">ABC.groovy</setting>
+        <setting name="contentType">eu.esdihumboldt.hale.io.schemabuilder</setting>
+    </resource>
+    <resource action-id="eu.esdihumboldt.hale.io.schema.read.target" provider-id="eu.esdihumboldt.hale.io.schemabuilder">
+        <setting name="charset">UTF-8</setting>
+        <setting name="resourceId">67afcd12-5dd4-4adc-ab52-042786a4b3af</setting>
+        <setting name="source">S.groovy</setting>
+        <setting name="contentType">eu.esdihumboldt.hale.io.schemabuilder</setting>
+    </resource>
+    <file name="alignment.xml" location="ABC-to-S.halex.alignment.xml"/>
+    <file name="styles.sld" location="ABC-to-S.halex.styles.sld"/>
+</hale-project>
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/ABC-to-S.halex.alignment.xml b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/ABC-to-S.halex.alignment.xml
new file mode 100644
index 0000000000..afdede245f
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/ABC-to-S.halex.alignment.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<alignment xmlns="http://www.esdi-humboldt.eu/hale/alignment">
+    <cell relation="eu.esdihumboldt.hale.align.join" id="C052fa5be-faf8-441e-9e55-bd4a6d271834" priority="normal">
+        <source name="types">
+            <class>
+                <type name="B" ns="ABC"/>
+            </class>
+        </source>
+        <source name="types">
+            <class>
+                <type name="C" ns="ABC"/>
+            </class>
+        </source>
+        <source name="types">
+            <class>
+                <type name="A" ns="ABC"/>
+            </class>
+        </source>
+        <target>
+            <class>
+                <type name="S" ns="S"/>
+            </class>
+        </target>
+        <complexParameter name="join">
+            <jp:join-parameter xmlns:jp="http://www.esdi-humboldt.eu/hale/join">
+                <class>
+                    <type name="A" ns="ABC"/>
+                </class>
+                <class>
+                    <type name="B" ns="ABC"/>
+                </class>
+                <class>
+                    <type name="C" ns="ABC"/>
+                </class>
+                <jp:condition>
+                    <property>
+                        <type name="A" ns="ABC"/>
+                        <child name="a" ns="ABC"/>
+                    </property>
+                    <property>
+                        <type name="B" ns="ABC"/>
+                        <child name="a" ns="ABC"/>
+                    </property>
+                </jp:condition>
+                <jp:condition>
+                    <property>
+                        <type name="B" ns="ABC"/>
+                        <child name="b" ns="ABC"/>
+                    </property>
+                    <property>
+                        <type name="C" ns="ABC"/>
+                        <child name="b" ns="ABC"/>
+                    </property>
+                </jp:condition>
+            </jp:join-parameter>
+        </complexParameter>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="C10d44c4c-8346-48ec-bff9-0b5f305f82c8" priority="normal">
+        <source>
+            <property>
+                <type name="A" ns="ABC"/>
+                <child name="a" ns="ABC"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="S" ns="S"/>
+                <child name="a" ns="S"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="C531ccec5-b548-49ce-8aac-e2b74c9b6b93" priority="normal">
+        <source>
+            <property>
+                <type name="B" ns="ABC"/>
+                <child name="b" ns="ABC"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="S" ns="S"/>
+                <child name="b" ns="S"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="C2636c3cf-fc30-418d-b22b-803c794c0fa4" priority="normal">
+        <source>
+            <property>
+                <type name="C" ns="ABC"/>
+                <child name="c" ns="ABC"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="S" ns="S"/>
+                <child name="c" ns="S"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+</alignment>
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/ABC-to-S.halex.styles.sld b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/ABC-to-S.halex.styles.sld
new file mode 100644
index 0000000000..ad34f540f6
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/ABC-to-S.halex.styles.sld
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?><sld:UserStyle xmlns="http://www.opengis.net/sld" xmlns:sld="http://www.opengis.net/sld" xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc">
+  <sld:Name>Default Styler</sld:Name>
+</sld:UserStyle>
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/ABC.groovy b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/ABC.groovy
new file mode 100644
index 0000000000..29f9fb2411
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/ABC.groovy
@@ -0,0 +1,15 @@
+schema('ABC') {
+	A {
+		a()
+	}
+	
+	B {
+		a()
+		b(cardinality: '?')
+	}
+	
+	C {
+		b()
+		c(cardinality: '?')
+	}
+}
\ No newline at end of file
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/S-to-T.halex b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/S-to-T.halex
new file mode 100644
index 0000000000..98d774624f
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/S-to-T.halex
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<hale-project version="5.1.0.qualifier">
+    <name>S to T (to be migrated)</name>
+    <author>Simon Templer</author>
+    <created>2018-01-10T16:26:35.275+01:00</created>
+    <modified>2024-02-05T11:15:17.691+01:00</modified>
+    <save-config action-id="project.save" provider-id="eu.esdihumboldt.hale.io.project.hale25.xml.writer">
+        <setting name="charset">UTF-8</setting>
+        <setting name="projectFiles.separate">false</setting>
+        <setting name="contentType">eu.esdihumboldt.hale.io.project.hale25.xml</setting>
+        <setting name="target">file:/home/simon/repos/hale/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/S-to-T.halex</setting>
+    </save-config>
+    <resource action-id="eu.esdihumboldt.hale.io.schema.read.source" provider-id="eu.esdihumboldt.hale.io.schemabuilder">
+        <setting name="charset">UTF-8</setting>
+        <setting name="resourceId">bafa16f0-75ed-44e3-a0ed-10e5410d044d</setting>
+        <setting name="source">S.groovy</setting>
+        <setting name="contentType">eu.esdihumboldt.hale.io.schemabuilder</setting>
+    </resource>
+    <resource action-id="eu.esdihumboldt.hale.io.schema.read.target" provider-id="eu.esdihumboldt.hale.io.schemabuilder">
+        <setting name="charset">UTF-8</setting>
+        <setting name="resourceId">e13b67b2-8b9a-45eb-b812-c8c675028b48</setting>
+        <setting name="source">T.groovy</setting>
+        <setting name="contentType">eu.esdihumboldt.hale.io.schemabuilder</setting>
+    </resource>
+    <file name="alignment.xml" location="S-to-T.halex.alignment.xml"/>
+    <file name="styles.sld" location="S-to-T.halex.styles.sld"/>
+</hale-project>
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/S-to-T.halex.alignment.xml b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/S-to-T.halex.alignment.xml
new file mode 100644
index 0000000000..a37cb59750
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/S-to-T.halex.alignment.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<alignment xmlns="http://www.esdi-humboldt.eu/hale/alignment">
+    <cell relation="eu.esdihumboldt.hale.align.retype" id="SabcT" priority="normal">
+        <source>
+            <class>
+                <type name="S" ns="S">
+                    <condition lang="ECQL">a &lt;&gt; '100' AND (b = '1000' OR b = '1001') AND c IS NOT NULL</condition>
+                </type>
+            </class>
+        </source>
+        <target>
+            <class>
+                <type name="T" ns="T"/>
+            </class>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="C5a797cd6-1a6b-4692-ad14-115fd819a068" priority="normal">
+        <source>
+            <property>
+                <type name="S" ns="S"/>
+                <child name="a" ns="S"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="T" ns="T"/>
+                <child name="a" ns="T"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="Cda0bda4c-63b9-4120-ad93-df726cbad7d6" priority="normal">
+        <source>
+            <property>
+                <type name="S" ns="S"/>
+                <child name="b" ns="S"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="T" ns="T"/>
+                <child name="b" ns="T"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="Ce600b8bf-fdd8-447b-aa31-074fd48b431e" priority="normal">
+        <source>
+            <property>
+                <type name="S" ns="S"/>
+                <child name="c" ns="S"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="T" ns="T"/>
+                <child name="c" ns="T"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+</alignment>
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/S-to-T.halex.styles.sld b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/S-to-T.halex.styles.sld
new file mode 100644
index 0000000000..ad34f540f6
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/S-to-T.halex.styles.sld
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?><sld:UserStyle xmlns="http://www.opengis.net/sld" xmlns:sld="http://www.opengis.net/sld" xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc">
+  <sld:Name>Default Styler</sld:Name>
+</sld:UserStyle>
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/S.groovy b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/S.groovy
new file mode 100644
index 0000000000..1b76c88047
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/S.groovy
@@ -0,0 +1,7 @@
+schema('S') {
+	S {
+		a()
+		b(cardinality: '?')
+		c(cardinality: '?')
+	}
+}
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/T.groovy b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/T.groovy
new file mode 100644
index 0000000000..f34690fedc
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/T.groovy
@@ -0,0 +1,7 @@
+schema('T') {
+	T {
+		a()
+		b(cardinality: '?')
+		c(cardinality: '?')
+	}
+}

From a692fe87a44b0123cf26774c408503d049f9b8d3 Mon Sep 17 00:00:00 2001
From: Simon Templer <simon@wetransform.to>
Date: Tue, 6 Feb 2024 16:53:19 +0100
Subject: [PATCH 02/13] WIP merge

---
 .../test/impl/JoinRetainConditionsTest.groovy | 107 ++++++++++-----
 .../merge/functions/JoinMergeMigrator.java    |   2 +-
 .../merge/impl/AbstractMergeCellMigrator.java | 122 +++++++++++++++++-
 .../align/merge/impl/AbstractMigration.groovy |  23 ++--
 .../merge/impl/DefaultSchemaMigration.groovy  |   3 +-
 .../align/merge/impl/MatchingMigration.groovy |  23 +++-
 .../align/instance/EntityAwareFilter.java     |   7 +-
 .../align/migrate/AlignmentMigration.java     |  17 ++-
 .../align/migrate/impl/UnmigratedCell.java    |   3 +-
 .../common/filter/AbstractGeotoolsFilter.java | 112 +++++++++++++++-
 .../internal/EntityReplacementVisitor.java    |  88 ++++++++++++-
 .../service/align/migrate/UserMigration.java  |   4 +-
 12 files changed, 443 insertions(+), 68 deletions(-)

diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/JoinRetainConditionsTest.groovy b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/JoinRetainConditionsTest.groovy
index 89be9a0ac5..48de315fa4 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/JoinRetainConditionsTest.groovy
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/JoinRetainConditionsTest.groovy
@@ -21,6 +21,9 @@ import org.junit.Test
 
 import eu.esdihumboldt.hale.common.align.io.impl.JaxbAlignmentIO
 import eu.esdihumboldt.hale.common.align.merge.test.AbstractMergeCellMigratorTest
+import eu.esdihumboldt.hale.common.align.model.CellUtil
+import eu.esdihumboldt.hale.common.align.model.Entity
+import eu.esdihumboldt.hale.common.align.model.functions.JoinFunction
 import eu.esdihumboldt.hale.common.align.model.functions.join.JoinParameter
 
 /**
@@ -45,37 +48,79 @@ class JoinRetainConditionsTest extends AbstractMergeCellMigratorTest {
 		assertEquals(1, migrated.size())
 		JaxbAlignmentIO.printCell(migrated[0], System.out)
 
-		/*
-		 assertNotNull(migrated[0].source)
-		 assertEquals(2, migrated[0].source.size())
-		 Collection<? extends Entity> source = migrated[0].source.values()
-		 ((Collection<Entity>) source).each { e ->
-		 def filter = e.definition.filter
-		 if (e.definition.definition.displayName == 'A1') {
-		 // expect filter to have been propagated to A1
-		 assertNotNull(filter)
-		 //assertEquals('a1 <> \'NIL\'', filter.filterTerm)
-		 assertEquals('NOT (a1 = \'NIL\')', filter.filterTerm)
-		 }
-		 else {
-		 // no filter should be present
-		 assertNull(filter)
-		 }
-		 }
-		 JoinParameter param = CellUtil.getFirstParameter(migrated[0], JoinFunction.PARAMETER_JOIN).as(JoinParameter)
-		 assertJoinOrder(param, ['A1', 'A2'])
-		 // there should be a condition on the join focus, also in the order
-		 assertNotNull(param.types[0].filter)
-		 // there should also be a filter in the condition
-		 def base = param.conditions.collect { it.baseProperty }.findAll { it.type.displayName == 'A1' }.toList()
-		 assertEquals(1, base.size())
-		 assertNotNull(base[0].filter)
-		 // there should be a message about the condition having been translated automatically
-		 def messages = getMigrationMessages(migrated[0])
-		 assertTrue(messages.any { msg ->
-		 msg.text.toLowerCase().contains('condition')
-		 })
-		 */
+		def expectedFilterA = 'NOT (a = \'100\')'
+		def expectedFilterB = '(b IN (\'1000\',\'1001\'))'
+		def expectedFilterC = 'NOT (c IS NULL)'
+
+		assertNotNull(migrated[0].source)
+		assertEquals(3, migrated[0].source.size())
+		Collection<? extends Entity> source = migrated[0].source.values()
+		((Collection<Entity>) source).each { e ->
+			def filter = e.definition.filter
+			if (e.definition.definition.displayName == 'A') {
+				// expect filter part to have been propagated to A
+				assertNotNull(filter)
+				assertEquals(expectedFilterA, filter.filterTerm)
+			}
+			else if (e.definition.definition.displayName == 'B') {
+				// expect filter part to have been propagated to A
+				assertNotNull(filter)
+				assertEquals(expectedFilterB, filter.filterTerm)
+			}
+			else if (e.definition.definition.displayName == 'C') {
+				// expect filter part to have been propagated to A
+				assertNotNull(filter)
+				assertEquals(expectedFilterC, filter.filterTerm)
+			}
+			else {
+				fail('Unexpected entity')
+			}
+		}
+		JoinParameter param = CellUtil.getFirstParameter(migrated[0], JoinFunction.PARAMETER_JOIN).as(JoinParameter)
+		assertJoinOrder(param, ['A', 'B', 'C'])
+
+		// there should be a condition on all join types, also in the order
+		def filter = param.types[0].filter
+		assertNotNull(filter)
+		assertEquals(expectedFilterA, filter.filterTerm)
+
+		filter = param.types[1].filter
+		assertNotNull(filter)
+		assertEquals(expectedFilterB, filter.filterTerm)
+
+		filter = param.types[2].filter
+		assertNotNull(filter)
+		assertEquals(expectedFilterC, filter.filterTerm)
+
+		// there should also be a filter in the condition for each type
+
+		// base properties
+		def base = param.conditions.collect { it.baseProperty }.findAll { it.type.displayName == 'A' }.toList()
+		assertEquals(1, base.size())
+		assertNotNull(base[0].filter)
+		assertEquals(expectedFilterA, base[0].filter.filterTerm)
+
+		base = param.conditions.collect { it.baseProperty }.findAll { it.type.displayName == 'B' }.toList()
+		assertEquals(1, base.size())
+		assertNotNull(base[0].filter)
+		assertEquals(expectedFilterB, base[0].filter.filterTerm)
+
+		// join properties
+		def join = param.conditions.collect { it.joinProperty }.findAll { it.type.displayName == 'B' }.toList()
+		assertEquals(1, join.size())
+		assertNotNull(join[0].filter)
+		assertEquals(expectedFilterB, join[0].filter.filterTerm)
+
+		join = param.conditions.collect { it.joinProperty }.findAll { it.type.displayName == 'C' }.toList()
+		assertEquals(1, join.size())
+		assertNotNull(join[0].filter)
+		assertEquals(expectedFilterC, join[0].filter.filterTerm)
+
+		// there should be a message about the condition having been translated automatically
+		def messages = getMigrationMessages(migrated[0])
+		assertTrue(messages.any { msg ->
+			msg.text.toLowerCase().contains('condition')
+		})
 	}
 
 	// helpers
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/functions/JoinMergeMigrator.java b/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/functions/JoinMergeMigrator.java
index 97825d437c..3eacaad6cf 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/functions/JoinMergeMigrator.java
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/functions/JoinMergeMigrator.java
@@ -190,7 +190,7 @@ private void addSources(MutableCell cell, EntityDefinition source, Cell match,
 				if (transferContext.test(entity.getDefinition())) {
 					// transfer filter and contexts if possible
 					EntityDefinition withContexts = AbstractMigration.translateContexts(source,
-							entity.getDefinition(), migration, log);
+							entity.getDefinition(), migration, null, log);
 					if (withContexts.getFilter() != null) {
 						context.addTypeFilter(withContexts.getType(), withContexts.getFilter());
 					}
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/AbstractMergeCellMigrator.java b/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/AbstractMergeCellMigrator.java
index b7f7692b8d..f4e9e8e6f6 100755
--- a/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/AbstractMergeCellMigrator.java
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/AbstractMergeCellMigrator.java
@@ -20,7 +20,9 @@
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Optional;
 import java.util.Set;
@@ -60,6 +62,7 @@
 import eu.esdihumboldt.hale.common.core.report.SimpleLog;
 import eu.esdihumboldt.hale.common.instance.model.Filter;
 import eu.esdihumboldt.hale.common.schema.model.PropertyDefinition;
+import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
 import eu.esdihumboldt.hale.common.schema.model.constraint.type.GeometryType;
 
 /**
@@ -155,8 +158,8 @@ else if (isDirectMatch(originalCell)) {
 						AlignmentMigration cellMigration = new AbstractMigration() {
 
 							@Override
-							protected Optional<EntityDefinition> findMatch(
-									EntityDefinition entity) {
+							protected Optional<EntityDefinition> findMatch(EntityDefinition entity,
+									TypeDefinition preferRoot) {
 								Entity target = CellUtil.getFirstEntity(originalCell.getTarget());
 								if (target != null) {
 									return Optional.ofNullable(target.getDefinition());
@@ -357,7 +360,7 @@ else if (newSource.size() == 1) {
 					Entity singleSource = CellUtil.getFirstEntity(newSource);
 					if (singleSource != null) {
 						EntityDefinition transferedSource = AbstractMigration.translateContexts(
-								original, singleSource.getDefinition(), migration, log);
+								original, singleSource.getDefinition(), migration, null, log);
 						ListMultimap<String, Entity> s = ArrayListMultimap.create();
 						s.put(newSource.keySet().iterator().next(),
 								AlignmentUtil.createEntity(transferedSource));
@@ -371,11 +374,19 @@ else if (newSource.size() == 1) {
 					// sources?
 
 					// XXX for now only special case handling to support
-					// XtraServer use case
+					// XtraServer use case - if not enabled, continue
 					if (applySourceContextsToJoinFocus(newCell, originalSource, migration, log)) {
 						return;
 					}
 
+					/*
+					 * Generic handling of source contexts for Join that tries
+					 * to split filters and apply parts to each source type.
+					 */
+					if (applySourceContextsToJoin(newCell, originalSource, migration, log)) {
+						return;
+					}
+
 					// no idea where to add contexts -> report
 					log.warn(
 							"Any conditions/contexts on the original source have been dropped because the new mapping has multiple sources and it is not clear where they should be attached: "
@@ -385,6 +396,107 @@ else if (newSource.size() == 1) {
 		}
 	}
 
+	/**
+	 * Apply source conditions to joined types.
+	 * 
+	 * @param newCell the new cell to update the sources
+	 * @param originalSource the original source
+	 * @param migration the alignment migration
+	 * @param log the operation log
+	 * @return if the method handled the context transfer
+	 */
+	private boolean applySourceContextsToJoin(MutableCell newCell, Entity originalSource,
+			AlignmentMigration migration, SimpleLog log) {
+		String function = newCell.getTransformationIdentifier();
+		switch (function) {
+		case GroovyJoin.ID:
+		case JoinFunction.ID:
+			break;
+		default:
+			return false;
+		}
+
+		JoinParameter joinConfig = CellUtil.getFirstParameter(newCell, JoinFunction.PARAMETER_JOIN)
+				.as(JoinParameter.class);
+
+		if (joinConfig == null || joinConfig.getTypes() == null
+				|| joinConfig.getTypes().isEmpty()) {
+			return false;
+		}
+
+		List<TypeDefinition> joinTypes = joinConfig.getTypes().stream()
+				.map(TypeEntityDefinition::getDefinition).toList();
+		Map<TypeDefinition, Filter> joinTypeFilters = new HashMap<TypeDefinition, Filter>();
+
+		// transfer context for each Join source type
+		newCell.setSource(ArrayListMultimap.create(Multimaps.transformValues(newCell.getSource(),
+				new com.google.common.base.Function<Entity, Entity>() {
+
+					@Override
+					public Entity apply(Entity input) {
+						TypeDefinition inputType = input.getDefinition().getType();
+						if (input.getDefinition().getPropertyPath().isEmpty()
+								&& joinTypes.contains(inputType)) {
+							EntityDefinition transferedSource = AbstractMigration.translateContexts(
+									originalSource.getDefinition(), input.getDefinition(),
+									migration, inputType, log);
+							joinTypeFilters.put(inputType, transferedSource.getFilter());
+							return AlignmentUtil.createEntity(transferedSource);
+						}
+						else {
+							return input;
+						}
+					}
+				})));
+
+		// fix filter in order and conditions
+		// XXX only works like this because a type currently can only be present
+		// once in the source
+		if (!joinTypeFilters.isEmpty()) {
+			// order
+			List<TypeEntityDefinition> types = new ArrayList<>();
+			for (int i = 0; i < joinConfig.getTypes().size(); i++) {
+				TypeEntityDefinition type = joinConfig.getTypes().get(i);
+				Filter filter = joinTypeFilters.get(type.getType());
+				if (filter != null) {
+					type = new TypeEntityDefinition(type.getDefinition(), type.getSchemaSpace(),
+							filter);
+				}
+				types.add(type);
+			}
+
+			// conditions
+			Set<JoinCondition> conditions = joinConfig.getConditions().stream().map(c -> {
+				Filter baseFilter = joinTypeFilters.get(c.baseProperty.getType());
+				Filter joinFilter = joinTypeFilters.get(c.joinProperty.getType());
+
+				if (baseFilter != null || joinFilter != null) {
+					return new JoinCondition(applyFilter(c.baseProperty, baseFilter),
+							applyFilter(c.joinProperty, joinFilter));
+				}
+				else {
+					return c;
+				}
+			}).collect(Collectors.toSet());
+
+			JoinParameter newConfig = new JoinParameter(types, conditions);
+
+			ListMultimap<String, ParameterValue> modParams = ArrayListMultimap
+					.create(newCell.getTransformationParameters());
+			List<ParameterValue> joinParams = modParams.get(JoinFunction.PARAMETER_JOIN);
+			if (!joinParams.isEmpty()) {
+				JoinParameter joinParam = joinParams.get(0).as(JoinParameter.class);
+				if (joinParam != null) {
+					joinParams.clear();
+					joinParams.add(new ParameterValue(Value.complex(newConfig)));
+				}
+			}
+			newCell.setTransformationParameters(modParams);
+		}
+
+		return true;
+	}
+
 	/**
 	 * Handle special case of applying source contexts to the entity that is the
 	 * Join focus.
@@ -431,7 +543,7 @@ public Entity apply(Entity input) {
 								&& input.getDefinition().getType().equals(focus.getType())) {
 							EntityDefinition transferedSource = AbstractMigration.translateContexts(
 									originalSource.getDefinition(), input.getDefinition(),
-									migration, log);
+									migration, focus.getType(), log);
 							focusFilter.set(transferedSource.getFilter());
 							return AlignmentUtil.createEntity(transferedSource);
 						}
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/AbstractMigration.groovy b/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/AbstractMigration.groovy
index 014a1706df..a79b5516ee 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/AbstractMigration.groovy
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/AbstractMigration.groovy
@@ -26,6 +26,7 @@ import eu.esdihumboldt.hale.common.core.report.SimpleLog
 import eu.esdihumboldt.hale.common.instance.extension.filter.FilterDefinitionManager
 import eu.esdihumboldt.hale.common.schema.SchemaSpaceID
 import eu.esdihumboldt.hale.common.schema.model.PropertyDefinition
+import eu.esdihumboldt.hale.common.schema.model.TypeDefinition
 import eu.esdihumboldt.hale.common.schema.model.constraint.type.GeometryType
 import groovy.transform.CompileStatic
 
@@ -40,18 +41,19 @@ abstract class AbstractMigration implements AlignmentMigration {
 	/**
 	 * Find a match for the given entity
 	 * @param entity the entity
+	 * @param preferRoot hint on which entity to prefer if there are multiple matches
 	 * @return the match if found
 	 */
-	protected abstract Optional<EntityDefinition> findMatch(EntityDefinition entity);
+	protected abstract Optional<EntityDefinition> findMatch(EntityDefinition entity, TypeDefinition preferRoot);
 
 	@Override
-	Optional<EntityDefinition> entityReplacement(EntityDefinition entity, SimpleLog log) {
+	Optional<EntityDefinition> entityReplacement(EntityDefinition entity, TypeDefinition preferRoot, SimpleLog log) {
 		EntityDefinition defaultEntity = AlignmentUtil.getAllDefaultEntity(entity)
-		Optional<EntityDefinition> matchedEntity = findMatch(defaultEntity)
+		Optional<EntityDefinition> matchedEntity = findMatch(defaultEntity, preferRoot)
 
 		// special case handling
 		if (!matchedEntity.isPresent()) {
-			matchedEntity = findParentMatch(defaultEntity)
+			matchedEntity = findParentMatch(defaultEntity, preferRoot)
 			if (matchedEntity.present) {
 				log.warn "Inaccurate match of $entity to ${matchedEntity.get()} via parent entity"
 			}
@@ -60,7 +62,7 @@ abstract class AbstractMigration implements AlignmentMigration {
 		if (matchedEntity.present) {
 			// entity contained contexts -> translate them if possible
 
-			matchedEntity = Optional.ofNullable(translateContexts(entity, matchedEntity.get(), this, log));
+			matchedEntity = Optional.ofNullable(translateContexts(entity, matchedEntity.get(), this, preferRoot, log));
 		}
 
 		if (!matchedEntity.isPresent()) {
@@ -71,7 +73,7 @@ abstract class AbstractMigration implements AlignmentMigration {
 	}
 
 	static EntityDefinition translateContexts(EntityDefinition original, EntityDefinition target,
-			AlignmentMigration migration, SimpleLog log) {
+			AlignmentMigration migration, TypeDefinition preferRoot, SimpleLog log) {
 		def defaultEntity = AlignmentUtil.getAllDefaultEntity(original)
 
 		if (original.filter) {
@@ -92,7 +94,7 @@ abstract class AbstractMigration implements AlignmentMigration {
 				if (!sameEntity(original, target)) {
 					// replacements in filter if possible
 					if (filter instanceof EntityAwareFilter) {
-						def migrated = ((EntityAwareFilter) filter).migrateFilter(AlignmentUtil.getTypeEntity(original), migration, log)
+						def migrated = ((EntityAwareFilter) filter).migrateFilter(AlignmentUtil.getTypeEntity(original), migration, preferRoot, log)
 						if (migrated.present) {
 							filter = migrated.get()
 							//TODO mark automatically migrated?
@@ -206,10 +208,10 @@ abstract class AbstractMigration implements AlignmentMigration {
 		}
 	}
 
-	protected Optional<EntityDefinition> findParentMatch(EntityDefinition entity) {
+	protected Optional<EntityDefinition> findParentMatch(EntityDefinition entity, TypeDefinition preferRoot) {
 		//XXX only allow parent matches for specific cases right now
 		if (!(entity.definition instanceof PropertyDefinition) ||
-		!((PropertyDefinition) entity.definition).propertyType.getConstraint(GeometryType).isGeometry()) {
+				!((PropertyDefinition) entity.definition).propertyType.getConstraint(GeometryType).isGeometry()) {
 			// not a geometry
 			return Optional.empty()
 		}
@@ -217,7 +219,7 @@ abstract class AbstractMigration implements AlignmentMigration {
 		while (entity != null) {
 			entity = AlignmentUtil.getParent(entity)
 
-			def matchedEntity = findMatch(entity);
+			def matchedEntity = findMatch(entity, preferRoot);
 			if (matchedEntity.present) {
 				return matchedEntity
 			}
@@ -225,5 +227,4 @@ abstract class AbstractMigration implements AlignmentMigration {
 
 		return Optional.empty()
 	}
-
 }
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/DefaultSchemaMigration.groovy b/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/DefaultSchemaMigration.groovy
index aa7031ca00..0eff252a04 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/DefaultSchemaMigration.groovy
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/DefaultSchemaMigration.groovy
@@ -44,7 +44,7 @@ class DefaultSchemaMigration implements AlignmentMigration {
 	}
 
 	@Override
-	public Optional<EntityDefinition> entityReplacement(EntityDefinition entity, SimpleLog log) {
+	public Optional<EntityDefinition> entityReplacement(EntityDefinition entity, TypeDefinition preferRoot, SimpleLog log) {
 
 		// default behavior - try to find entity in new schema, based on names w/o namespace
 
@@ -184,5 +184,4 @@ class DefaultSchemaMigration implements AlignmentMigration {
 
 		candidate
 	}
-
 }
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/MatchingMigration.groovy b/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/MatchingMigration.groovy
index 5075af47e1..4847f9a3c0 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/MatchingMigration.groovy
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/MatchingMigration.groovy
@@ -22,6 +22,7 @@ import eu.esdihumboldt.hale.common.align.model.Cell
 import eu.esdihumboldt.hale.common.align.model.EntityDefinition
 import eu.esdihumboldt.hale.common.headless.impl.ProjectTransformationEnvironment;
 import eu.esdihumboldt.hale.common.schema.SchemaSpaceID
+import eu.esdihumboldt.hale.common.schema.model.TypeDefinition
 import groovy.transform.CompileStatic;;;
 
 /**
@@ -41,12 +42,30 @@ class MatchingMigration extends AbstractMigration {
 		this.reverse = reverse
 	}
 
-	protected Optional<EntityDefinition> findMatch(EntityDefinition entity) {
+	@Override
+	protected Optional<EntityDefinition> findMatch(EntityDefinition entity, TypeDefinition preferRoot) {
 		findMatches(entity).flatMap({ list ->
-			list ? Optional.ofNullable(((List<EntityDefinition>)list)[0]) : Optional.empty()
+			findPreferredCandidate((List<EntityDefinition>)list, preferRoot)
 		} as Function)
 	}
 
+	private Optional<EntityDefinition> findPreferredCandidate(Collection<EntityDefinition> entities, TypeDefinition preferRoot) {
+		if (entities.empty) {
+			return Optional.empty()
+		}
+
+		/*
+		 * Note: preferRoot is used here to for example pick a specific type from a Join cell's sources 
+		 */
+		def firstPreferred = entities.find { EntityDefinition it -> it.type == preferRoot }
+		if (firstPreferred) {
+			Optional.of(firstPreferred)
+		}
+		else {
+			Optional.of(entities.iterator().next())
+		}
+	}
+
 	public Optional<List<EntityDefinition>> findMatches(EntityDefinition entity) {
 		if (reverse) {
 			// match to target
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/instance/EntityAwareFilter.java b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/instance/EntityAwareFilter.java
index 43c3e3376a..ceea97bc43 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/instance/EntityAwareFilter.java
+++ b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/instance/EntityAwareFilter.java
@@ -22,6 +22,7 @@
 import eu.esdihumboldt.hale.common.align.model.EntityDefinition;
 import eu.esdihumboldt.hale.common.core.report.SimpleLog;
 import eu.esdihumboldt.hale.common.instance.model.Filter;
+import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
 
 /**
  * Extended filter interface for filters aware of referenced entities.
@@ -42,7 +43,7 @@ public interface EntityAwareFilter extends Filter {
 
 	/**
 	 * States if the filter supports migration via
-	 * {@link #migrateFilter(EntityDefinition, AlignmentMigration, SimpleLog)}
+	 * {@link #migrateFilter(EntityDefinition, AlignmentMigration, TypeDefinition, SimpleLog)}
 	 * 
 	 * @return <code>true</code> if migration is supported, <code>false</code>
 	 *         otherwise
@@ -55,10 +56,12 @@ public interface EntityAwareFilter extends Filter {
 	 * 
 	 * @param context the entity context
 	 * @param migration the alignment migration
+	 * @param preferRoot hint on which entity to prefer if there are multiple
+	 *            matches
 	 * @param log the operation log
 	 * @return the migrated filter, if migration is possible
 	 */
 	Optional<Filter> migrateFilter(EntityDefinition context, AlignmentMigration migration,
-			SimpleLog log);
+			TypeDefinition preferRoot, SimpleLog log);
 
 }
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/migrate/AlignmentMigration.java b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/migrate/AlignmentMigration.java
index c65ef04431..ff37f622b4 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/migrate/AlignmentMigration.java
+++ b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/migrate/AlignmentMigration.java
@@ -19,6 +19,7 @@
 
 import eu.esdihumboldt.hale.common.align.model.EntityDefinition;
 import eu.esdihumboldt.hale.common.core.report.SimpleLog;
+import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
 
 /**
  * Interface describing an alignment migration.
@@ -34,6 +35,20 @@ public interface AlignmentMigration {
 	 * @param log the migration process log (may be cell specific)
 	 * @return the replacement entity, if the entity should be replaced
 	 */
-	Optional<EntityDefinition> entityReplacement(EntityDefinition entity, SimpleLog log);
+	default Optional<EntityDefinition> entityReplacement(EntityDefinition entity, SimpleLog log) {
+		return entityReplacement(entity, null, log);
+	}
+
+	/**
+	 * Yields a replacement for an entity existing in a given alignment.
+	 * 
+	 * @param entity the entity to replace
+	 * @param preferRoot hint on which entity to prefer if there are multiple
+	 *            matches
+	 * @param log the migration process log (may be cell specific)
+	 * @return the replacement entity, if the entity should be replaced
+	 */
+	Optional<EntityDefinition> entityReplacement(EntityDefinition entity, TypeDefinition preferRoot,
+			SimpleLog log);
 
 }
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/migrate/impl/UnmigratedCell.java b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/migrate/impl/UnmigratedCell.java
index 371ab0d75c..5bd1f0a3c0 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/migrate/impl/UnmigratedCell.java
+++ b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/migrate/impl/UnmigratedCell.java
@@ -29,6 +29,7 @@
 import eu.esdihumboldt.hale.common.align.model.MutableCell;
 import eu.esdihumboldt.hale.common.align.model.impl.MutableCellDecorator;
 import eu.esdihumboldt.hale.common.core.report.SimpleLog;
+import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
 
 /**
  * Decorator for a {@link MutableCell} that allows to do lazy migration.
@@ -82,7 +83,7 @@ public MutableCell migrate(Map<EntityDefinition, EntityDefinition> additionalMap
 
 			@Override
 			public Optional<EntityDefinition> entityReplacement(EntityDefinition entity,
-					SimpleLog log) {
+					TypeDefinition preferRoot, SimpleLog log) {
 				return Optional.ofNullable(joinedMappings.get(entity));
 			}
 
diff --git a/common/plugins/eu.esdihumboldt.hale.common.filter/src/eu/esdihumboldt/hale/common/filter/AbstractGeotoolsFilter.java b/common/plugins/eu.esdihumboldt.hale.common.filter/src/eu/esdihumboldt/hale/common/filter/AbstractGeotoolsFilter.java
index bd3880fdc4..4eb3df6f62 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.filter/src/eu/esdihumboldt/hale/common/filter/AbstractGeotoolsFilter.java
+++ b/common/plugins/eu.esdihumboldt.hale.common.filter/src/eu/esdihumboldt/hale/common/filter/AbstractGeotoolsFilter.java
@@ -16,14 +16,20 @@
 
 package eu.esdihumboldt.hale.common.filter;
 
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Optional;
 import java.util.stream.Collectors;
 
 import javax.xml.namespace.QName;
 
+import org.geotools.factory.CommonFactoryFinder;
 import org.geotools.filter.FilterAttributeExtractor;
 import org.geotools.filter.text.cql2.CQLException;
+import org.geotools.util.factory.GeoTools;
+import org.opengis.filter.And;
 import org.opengis.filter.Filter;
 import org.opengis.filter.expression.PropertyName;
 
@@ -39,6 +45,7 @@
 import eu.esdihumboldt.hale.common.filter.internal.EntityReplacementVisitor;
 import eu.esdihumboldt.hale.common.instance.helper.PropertyResolver;
 import eu.esdihumboldt.hale.common.instance.model.Instance;
+import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
 import eu.esdihumboldt.util.groovy.paths.Path;
 
 /**
@@ -179,15 +186,84 @@ public List<Optional<EntityDefinition>> getReferencedEntities(EntityDefinition c
 
 	@Override
 	public Optional<eu.esdihumboldt.hale.common.instance.model.Filter> migrateFilter(
-			EntityDefinition context, AlignmentMigration migration, SimpleLog log) {
+			EntityDefinition context, AlignmentMigration migration, TypeDefinition preferRoot,
+			SimpleLog log) {
+		// split filter (AND operands)
+		List<Filter> andParts = splitAnd(internFilter);
+
+		// migrate each filter part
+		List<Filter> acceptedParts = new ArrayList<>();
+		for (Filter part : andParts) {
+			EntityReplacementVisitor visitor = new EntityReplacementVisitor(migration,
+					name -> resolveProperty(name, context, log), preferRoot, log);
+			Object extraData = null;
+			Filter copy = (Filter) part.accept(visitor, extraData);
+
+			/*
+			 * Determine if part is relevant. Only accept filter parts that are
+			 * not exclusively updated with other types than `preferRoot`. (This
+			 * is used to handle the different types from a Join individually)
+			 * 
+			 * TODO is usage of preferRoot OK or should we have an additional
+			 * parameter to control this behavior?
+			 * 
+			 * Inform about parts that are dropped)
+			 */
+			TypeDefinition focusType = preferRoot;
+			String messagePrefix = (focusType == null) ? "" : focusType.getDisplayName() + ": ";
+			if (visitor.isAllMismatches(focusType)) {
+				// drop if there were no successful replacements at all
+				if (andParts.size() == 1) {
+					try {
+						log.warn(
+								"{0}The filter \"{1}\" was removed because no matches for the respective properties were found",
+								messagePrefix, toFilterTerm(part));
+					} catch (CQLException e) {
+						log.error(
+								"{0}The filter was removed because no matches for the respective properties were found; error converting filter to string",
+								messagePrefix, e);
+					}
+				}
+				else {
+					try {
+						log.warn(
+								"{0}The filter operand \"{1}\" part of the filter''s AND condition was removed because no matches for the respective properties were found",
+								messagePrefix, toFilterTerm(part));
+					} catch (CQLException e) {
+						log.error(
+								"{0}A filter operand part of the filter's AND condition was removed because no matches for the respective properties were found; error converting filter part to string",
+								messagePrefix, e);
+					}
+				}
+			}
+			else {
+				acceptedParts.add(copy);
+
+				// log if there are replacements that don't match the focus type
+				if (focusType != null) {
+					List<EntityDefinition> otherReplacements = visitor.getReplacements().stream()
+							.filter(entity -> !focusType.equals(entity.getType())).toList();
+					if (!otherReplacements.isEmpty()) {
+						try {
+							log.warn(
+									"{0}The filter operand \"{1}\" part of the filter''s AND condition contains references related to other types than {2}",
+									messagePrefix, toFilterTerm(part), focusType.getDisplayName());
+						} catch (CQLException e) {
+							log.error(
+									"{0}A filter operand part of the filter's AND condition contains references related to other types than {1}; error converting filter part to string",
+									messagePrefix, focusType.getDisplayName(), e);
+						}
+					}
+				}
+			}
+		}
 
-		EntityReplacementVisitor visitor = new EntityReplacementVisitor(migration,
-				name -> resolveProperty(name, context, log), log);
-		Object extraData = null;
-		Filter copy = (Filter) internFilter.accept(visitor, extraData);
+		// combine accepted filter parts
+		Filter combined = CommonFactoryFinder.getFilterFactory2(GeoTools.getDefaultHints())
+				.and(acceptedParts);
 
 		try {
-			String filterString = toFilterTerm(copy);
+			String filterString = toFilterTerm(combined);
 			return Optional.of(buildFilter(filterString));
 		} catch (CQLException e) {
 			log.error("Filter could not be automatically migrated", e);
@@ -195,6 +271,30 @@ public Optional<eu.esdihumboldt.hale.common.instance.model.Filter> migrateFilter
 		}
 	}
 
+	/**
+	 * Split a filter into separate AND conditions.
+	 * 
+	 * @param filter the filter to split
+	 * @return the split filters as list
+	 */
+	private List<Filter> splitAnd(Filter filter) {
+		List<Filter> result = new ArrayList<>();
+
+		Deque<Filter> toCheck = new LinkedList<>();
+		toCheck.add(filter);
+		while (!toCheck.isEmpty()) {
+			Filter f = toCheck.poll();
+			if (f instanceof And) {
+				toCheck.addAll(((And) f).getChildren());
+			}
+			else {
+				result.add(f);
+			}
+		}
+
+		return result;
+	}
+
 	/**
 	 * Resolve a property name based on the given context.
 	 * 
diff --git a/common/plugins/eu.esdihumboldt.hale.common.filter/src/eu/esdihumboldt/hale/common/filter/internal/EntityReplacementVisitor.java b/common/plugins/eu.esdihumboldt.hale.common.filter/src/eu/esdihumboldt/hale/common/filter/internal/EntityReplacementVisitor.java
index 6e3c5cf736..adff175940 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.filter/src/eu/esdihumboldt/hale/common/filter/internal/EntityReplacementVisitor.java
+++ b/common/plugins/eu.esdihumboldt.hale.common.filter/src/eu/esdihumboldt/hale/common/filter/internal/EntityReplacementVisitor.java
@@ -15,7 +15,10 @@
 
 package eu.esdihumboldt.hale.common.filter.internal;
 
+import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.Optional;
+import java.util.Set;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
@@ -26,6 +29,7 @@
 import eu.esdihumboldt.hale.common.align.model.AlignmentUtil;
 import eu.esdihumboldt.hale.common.align.model.EntityDefinition;
 import eu.esdihumboldt.hale.common.core.report.SimpleLog;
+import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
 
 /**
  * Filter visitor that replaces entities in filters with their
@@ -37,19 +41,33 @@ public class EntityReplacementVisitor extends DuplicatingFilterVisitor {
 
 	private final AlignmentMigration migration;
 	private final Function<PropertyName, Optional<EntityDefinition>> resolveProperty;
+	private final TypeDefinition preferRoot;
 	private final SimpleLog log;
 
+	// counter for total attempted replacements (where the original could be
+	// resolved)
+	private int total = 0;
+	// counter for successful replacements
+	private int matched = 0;
+
+	private final Set<EntityDefinition> mismatches = new LinkedHashSet<>();
+	private final Set<EntityDefinition> replacements = new LinkedHashSet<>();
+
 	/**
 	 * Create an entity replacement visitor.
 	 * 
 	 * @param migration the alignment migration
 	 * @param resolveProperty the function resolving a property to an entity
+	 * @param preferRoot hint on which entity to prefer if there are multiple
+	 *            matches
 	 * @param log the operation log
 	 */
 	public EntityReplacementVisitor(AlignmentMigration migration,
-			Function<PropertyName, Optional<EntityDefinition>> resolveProperty, SimpleLog log) {
+			Function<PropertyName, Optional<EntityDefinition>> resolveProperty,
+			TypeDefinition preferRoot, SimpleLog log) {
 		this.migration = migration;
 		this.resolveProperty = resolveProperty;
+		this.preferRoot = preferRoot;
 		this.log = log;
 	}
 
@@ -58,10 +76,21 @@ public Object visit(PropertyName expression, Object extraData) {
 		Optional<EntityDefinition> resolved = resolveProperty.apply(expression)
 				.map(p -> AlignmentUtil.getAllDefaultEntity(p));
 		if (resolved.isPresent()) {
-			Optional<EntityDefinition> replace = migration.entityReplacement(resolved.get(), log);
-			if (replace.isPresent() && !resolved.get().equals(replace.get())) {
-				return getFactory(extraData).property(toPropertyName(replace.get()),
-						expression.getNamespaceContext());
+			total++;
+
+			Optional<EntityDefinition> replace = migration.entityReplacement(resolved.get(),
+					preferRoot, log);
+			if (replace.isPresent()) {
+				matched++;
+				replacements.add(replace.get());
+
+				if (!resolved.get().equals(replace.get())) {
+					return getFactory(extraData).property(toPropertyName(replace.get()),
+							expression.getNamespaceContext());
+				}
+			}
+			else {
+				mismatches.add(resolved.get());
 			}
 		}
 		else {
@@ -81,4 +110,53 @@ private String toPropertyName(EntityDefinition entityDefinition) {
 		return name;
 	}
 
+	/**
+	 * @return if none the attempted replacements matched.
+	 */
+	public boolean isAllMismatches() {
+		return total > 0 && matched == 0;
+	}
+
+	/**
+	 * @return if there were any failed attempted replacements
+	 */
+	public boolean hasMismatches() {
+		return !mismatches.isEmpty();
+	}
+
+	/**
+	 * @return the set of entities where no replacement was found
+	 */
+	public Set<EntityDefinition> getMismatches() {
+		return Collections.unmodifiableSet(mismatches);
+	}
+
+	/**
+	 * @return the set of entities that served as replacement
+	 */
+	public Set<EntityDefinition> getReplacements() {
+		return Collections.unmodifiableSet(replacements);
+	}
+
+	/**
+	 * Determine if related to an expected parent type all replacement attempts
+	 * were mismatches or matching entities with a different parent type.
+	 * 
+	 * @param expectedParent the expected parent type
+	 * @return
+	 */
+	public boolean isAllMismatches(TypeDefinition expectedParent) {
+		if (expectedParent == null) {
+			return isAllMismatches();
+		}
+
+		if (total > 0) {
+			return !replacements.stream()
+					.anyMatch(entity -> expectedParent.equals(entity.getType()));
+		}
+		else {
+			return false;
+		}
+	}
+
 }
diff --git a/ui/plugins/eu.esdihumboldt.hale.ui/src/eu/esdihumboldt/hale/ui/service/align/migrate/UserMigration.java b/ui/plugins/eu.esdihumboldt.hale.ui/src/eu/esdihumboldt/hale/ui/service/align/migrate/UserMigration.java
index d04fbbcd68..1f42a8bbcf 100644
--- a/ui/plugins/eu.esdihumboldt.hale.ui/src/eu/esdihumboldt/hale/ui/service/align/migrate/UserMigration.java
+++ b/ui/plugins/eu.esdihumboldt.hale.ui/src/eu/esdihumboldt/hale/ui/service/align/migrate/UserMigration.java
@@ -25,6 +25,7 @@
 import eu.esdihumboldt.hale.common.align.model.impl.TypeEntityDefinition;
 import eu.esdihumboldt.hale.common.core.report.SimpleLog;
 import eu.esdihumboldt.hale.common.schema.SchemaSpaceID;
+import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
 import eu.esdihumboldt.hale.ui.service.align.resolver.UserFallbackEntityResolver;
 import eu.esdihumboldt.hale.ui.service.align.resolver.internal.EntityCandidates;
 
@@ -53,7 +54,8 @@ public UserMigration(SchemaSpaceID schemaSpace) {
 	}
 
 	@Override
-	public Optional<EntityDefinition> entityReplacement(EntityDefinition entity, SimpleLog log) {
+	public Optional<EntityDefinition> entityReplacement(EntityDefinition entity,
+			TypeDefinition preferRoot, SimpleLog log) {
 
 		// use functionality from entity resolver
 		if (entity instanceof TypeEntityDefinition) {

From b4d532b54e6ca49bba088296dcefc615dad91e02 Mon Sep 17 00:00:00 2001
From: Simon Templer <simon@wetransform.to>
Date: Tue, 6 Feb 2024 20:29:17 +0100
Subject: [PATCH 03/13] WIP 2

---
 .../impl/DefaultMergeCellMigratorTest.groovy  |  3 +-
 .../test/impl/JoinRetainConditionsTest.groovy | 56 +++++++++++++++++++
 .../retain-condition-join/B-to-C.halex        |  4 +-
 .../B-to-C.halex.alignment.xml                |  6 +-
 .../B-to-C.halex.styles.sld                   |  3 -
 .../align/merge/impl/AbstractMigration.groovy |  5 ++
 .../common/filter/AbstractGeotoolsFilter.java |  4 ++
 7 files changed, 71 insertions(+), 10 deletions(-)

diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/DefaultMergeCellMigratorTest.groovy b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/DefaultMergeCellMigratorTest.groovy
index e4f4ac5a1d..02f0c4d4e8 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/DefaultMergeCellMigratorTest.groovy
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/DefaultMergeCellMigratorTest.groovy
@@ -583,7 +583,8 @@ class DefaultMergeCellMigratorTest extends AbstractMergeCellMigratorTest {
 			def filter = e.definition.filter
 			if (e.definition.definition.name.localPart == 'A5') {
 				assertNotNull(filter)
-				assertEquals('ba = \'test\' AND NOT (bb = \'test\')', filter.filterTerm)
+				// assertEquals('ba = \'test\' AND NOT (bb = \'test\')', filter.filterTerm)
+				assertEquals('ba = \'test\' and bb <> \'test\'', filter.filterTerm)
 			}
 			else {
 				assertNull(filter)
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/JoinRetainConditionsTest.groovy b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/JoinRetainConditionsTest.groovy
index 48de315fa4..35077a2390 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/JoinRetainConditionsTest.groovy
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/JoinRetainConditionsTest.groovy
@@ -123,6 +123,62 @@ class JoinRetainConditionsTest extends AbstractMergeCellMigratorTest {
 		})
 	}
 
+	@Test
+	void testJoinCondition() {
+		def toMigrate = this.class.getResource('/testcases/retain-condition-join/B-to-C.halex')
+		def cellId = 'B1toC1'
+
+		def matching = this.class.getResource('/testcases/retain-condition-join/A-to-B.halex')
+
+		def migrated = merge(cellId, toMigrate, matching)
+
+		// do checks
+
+		// filter
+		assertEquals(1, migrated.size())
+		JaxbAlignmentIO.printCell(migrated[0], System.out)
+
+		assertNotNull(migrated[0].source)
+		assertEquals(2, migrated[0].source.size())
+		Collection<? extends Entity> source = migrated[0].source.values()
+		((Collection<Entity>) source).each { e ->
+			def filter = e.definition.filter
+			if (e.definition.definition.displayName == 'A1') {
+				// expect filter to have been propagated to A1
+				assertNotNull(filter)
+				//assertEquals('a1 <> \'NIL\'', filter.filterTerm)
+				assertEquals('NOT (a1 = \'NIL\')', filter.filterTerm)
+			}
+			else {
+				assertEquals('A2', e.definition.definition.displayName)
+
+				// there should be no filter
+				assertNull(filter)
+			}
+		}
+
+		JoinParameter param = CellUtil.getFirstParameter(migrated[0], JoinFunction.PARAMETER_JOIN).as(JoinParameter)
+		assertJoinOrder(param, ['A1', 'A2'])
+
+		// there should be a condition on the join focus, also in the order
+		assertNotNull(param.types[0].filter)
+
+		// there should also be a filter in the condition
+		def base = param.conditions.collect { it.baseProperty }.findAll { it.type.displayName == 'A1' }.toList()
+		assertEquals(1, base.size())
+		assertNotNull(base[0].filter)
+
+		// there should be a message about the condition having been translated automatically
+		def messages = getMigrationMessages(migrated[0])
+		assertTrue(messages.any { msg ->
+			msg.text.toLowerCase().contains('condition')
+		})
+		// there should be a message about the filter being removed from A2
+		assertTrue(messages.any { msg ->
+			msg.text.startsWith('A2') && msg.text.toLowerCase().contains('was removed')
+		})
+	}
+
 	// helpers
 
 	void assertJoinOrder(JoinParameter param, List<String> expected) {
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-condition-join/B-to-C.halex b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-condition-join/B-to-C.halex
index e42552830d..ff4af6c277 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-condition-join/B-to-C.halex
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-condition-join/B-to-C.halex
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<hale-project version="3.5.0.qualifier">
+<hale-project version="5.1.0.qualifier">
     <name>B to C (to be migrated)</name>
     <author>Simon Templer</author>
     <created>2018-01-10T16:26:35.275+01:00</created>
-    <modified>2018-07-17T14:47:20.404+02:00</modified>
+    <modified>2024-02-06T20:10:09.235+01:00</modified>
     <save-config action-id="project.save" provider-id="eu.esdihumboldt.hale.io.project.hale25.xml.writer">
         <setting name="charset">UTF-8</setting>
         <setting name="projectFiles.separate">false</setting>
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-condition-join/B-to-C.halex.alignment.xml b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-condition-join/B-to-C.halex.alignment.xml
index 8a9f9d4248..17d2d9d3fd 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-condition-join/B-to-C.halex.alignment.xml
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-condition-join/B-to-C.halex.alignment.xml
@@ -118,10 +118,8 @@
     <cell relation="eu.esdihumboldt.hale.align.rename" id="B6b7toC4c4" priority="normal">
         <source>
             <property>
-                <type name="B6" ns="B">
-                    <condition lang="ECQL">b6 &lt;&gt; 10</condition>
-                </type>
-                <child name="b3" ns="B"/>
+                <type name="B6" ns="B"/>
+                <child name="b6" ns="B"/>
             </property>
         </source>
         <target>
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-condition-join/B-to-C.halex.styles.sld b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-condition-join/B-to-C.halex.styles.sld
index e80edb03f6..ad34f540f6 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-condition-join/B-to-C.halex.styles.sld
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-condition-join/B-to-C.halex.styles.sld
@@ -1,6 +1,3 @@
 <?xml version="1.0" encoding="UTF-8"?><sld:UserStyle xmlns="http://www.opengis.net/sld" xmlns:sld="http://www.opengis.net/sld" xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc">
   <sld:Name>Default Styler</sld:Name>
-  <sld:FeatureTypeStyle>
-    <sld:Name>name</sld:Name>
-  </sld:FeatureTypeStyle>
 </sld:UserStyle>
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/AbstractMigration.groovy b/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/AbstractMigration.groovy
index a79b5516ee..9e0305bc78 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/AbstractMigration.groovy
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/AbstractMigration.groovy
@@ -99,6 +99,11 @@ abstract class AbstractMigration implements AlignmentMigration {
 							filter = migrated.get()
 							//TODO mark automatically migrated?
 						}
+						else {
+							// drop filter (should have been documented by migrateFilter call above)
+							//TODO discern between filter dropped vs. error when migrating filter? (see AbstractGeotoolsFilter#migrateFilter)
+							filter = null
+						}
 					}
 
 					// mark unsafe if entity is not the same
diff --git a/common/plugins/eu.esdihumboldt.hale.common.filter/src/eu/esdihumboldt/hale/common/filter/AbstractGeotoolsFilter.java b/common/plugins/eu.esdihumboldt.hale.common.filter/src/eu/esdihumboldt/hale/common/filter/AbstractGeotoolsFilter.java
index 4eb3df6f62..57be597a9e 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.filter/src/eu/esdihumboldt/hale/common/filter/AbstractGeotoolsFilter.java
+++ b/common/plugins/eu.esdihumboldt.hale.common.filter/src/eu/esdihumboldt/hale/common/filter/AbstractGeotoolsFilter.java
@@ -258,6 +258,10 @@ public Optional<eu.esdihumboldt.hale.common.instance.model.Filter> migrateFilter
 			}
 		}
 
+		if (acceptedParts.isEmpty()) {
+			return Optional.empty();
+		}
+
 		// combine accepted filter parts
 		Filter combined = CommonFactoryFinder.getFilterFactory2(GeoTools.getDefaultHints())
 				.and(acceptedParts);

From 1106a19b1615788b4aa101488ab5d6d0d6ce70d0 Mon Sep 17 00:00:00 2001
From: Simon Templer <simon@wetransform.to>
Date: Wed, 7 Feb 2024 09:36:31 +0100
Subject: [PATCH 04/13] WIP test fixes

---
 .../impl/DefaultMergeCellMigratorTest.groovy    | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/DefaultMergeCellMigratorTest.groovy b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/DefaultMergeCellMigratorTest.groovy
index 02f0c4d4e8..4794bd0c6f 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/DefaultMergeCellMigratorTest.groovy
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/DefaultMergeCellMigratorTest.groovy
@@ -446,6 +446,16 @@ class DefaultMergeCellMigratorTest extends AbstractMergeCellMigratorTest {
 		assertEquals(expectedFilter, filter.filterTerm)
 	}
 
+	@CompileStatic(TypeCheckingMode.SKIP)
+	private void filterCheckNull(Cell migrated) {
+		JaxbAlignmentIO.printCell(migrated, System.out)
+
+		// the condition should be present on the source
+		def source = CellUtil.getFirstEntity(migrated.source).definition
+		def filter = source.propertyPath.empty ? source.filter : source.propertyPath[0].condition?.filter
+		assertNull(filter)
+	}
+
 	@Test
 	void testTypeFilter1() {
 		def toMigrate = this.class.getResource('/testcases/type-filter/B-to-C.halex')
@@ -481,13 +491,18 @@ class DefaultMergeCellMigratorTest extends AbstractMergeCellMigratorTest {
 
 		// filter
 		assertEquals(1, migrated.size())
-		filterCheck(migrated[0], "bb = 'test'") // the filter should be retained
+		// filter is now dropped (because bb is not mapped for B2)
+		filterCheckNull(migrated[0])
+		// filterCheck(migrated[0], "bb = 'test'") // the filter should be retained
 
 		// there should be a message about the condition
 		def messages = getMigrationMessages(migrated[0])
 		assertTrue(messages.any { msg ->
 			msg.text.toLowerCase().contains('condition')
 		})
+		assertTrue(messages.any { msg ->
+			msg.text.toLowerCase().contains('removed because no matches')
+		})
 	}
 
 	@Test

From d5574d814a2d075ca5e68a5ac96d48bd68406888 Mon Sep 17 00:00:00 2001
From: Simon Templer <simon@wetransform.to>
Date: Tue, 12 Mar 2024 15:05:22 +0100
Subject: [PATCH 05/13] fix: don't close PrintStream when using printCell

...to avoid closing System.out which is used in most cases as target for
printing a Cell in tests, which lead to incomplete output from tests.
---
 .../META-INF/MANIFEST.MF                      |  1 +
 .../common/align/io/impl/JaxbAlignmentIO.java | 19 ++++++++++++++++++-
 2 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/common/plugins/eu.esdihumboldt.hale.common.align/META-INF/MANIFEST.MF b/common/plugins/eu.esdihumboldt.hale.common.align/META-INF/MANIFEST.MF
index 3adb7e08d2..eec7c07d4a 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align/META-INF/MANIFEST.MF
+++ b/common/plugins/eu.esdihumboldt.hale.common.align/META-INF/MANIFEST.MF
@@ -51,6 +51,7 @@ Import-Package: com.google.common.base;version="1.6.0",
  eu.esdihumboldt.util.io,
  javax.annotation;version="[1.2.0,1.2.0]",
  net.jcip.annotations,
+ org.apache.commons.io.output,
  org.apache.commons.lang;version="2.4.0",
  org.locationtech.jts.geom,
  org.slf4j;version="1.5.11",
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/io/impl/JaxbAlignmentIO.java b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/io/impl/JaxbAlignmentIO.java
index bed39c7e0e..d2f1386118 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/io/impl/JaxbAlignmentIO.java
+++ b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/io/impl/JaxbAlignmentIO.java
@@ -19,8 +19,11 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.io.PrintStream;
 import java.net.URI;
 
+import org.apache.commons.io.output.CloseShieldOutputStream;
+
 import eu.esdihumboldt.hale.common.align.io.EntityResolver;
 import eu.esdihumboldt.hale.common.align.io.impl.internal.AlignmentToJaxb;
 import eu.esdihumboldt.hale.common.align.io.impl.internal.JaxbToAlignment;
@@ -171,7 +174,21 @@ public static void save(AlignmentType alignment, IOReporter reporter, OutputStre
 	}
 
 	/**
-	 * Print a cell to an output stream (intended for tests/debugging).
+	 * Print a cell to a {@link PrintStream} (intended for tests/debugging). The
+	 * stream is prevented from being closed, which is intended to prevent for
+	 * instance System.out from being closed.
+	 * 
+	 * @param cell the cell to print
+	 * @param out the output stream
+	 * @throws Exception if an error occurs trying to print the cell
+	 */
+	public static void printCell(MutableCell cell, PrintStream out) throws Exception {
+		printCell(cell, CloseShieldOutputStream.wrap(out));
+	}
+
+	/**
+	 * Print a cell to an output stream (intended for tests/debugging). The
+	 * stream is closed when the cell was written.
 	 * 
 	 * @param cell the cell to print
 	 * @param out the output stream

From 4d1b37a598613cf53ae619b2d43dc400623d1626 Mon Sep 17 00:00:00 2001
From: Simon Templer <simon@wetransform.to>
Date: Tue, 12 Mar 2024 15:32:05 +0100
Subject: [PATCH 06/13] WIP test for property parent condition

---
 .../test/impl/JoinRetainConditionsTest.groovy | 75 +++++++++++++++++++
 .../S-to-T.halex                              |  4 +-
 .../S-to-T.halex.alignment.xml                | 10 ++-
 3 files changed, 83 insertions(+), 6 deletions(-)

diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/JoinRetainConditionsTest.groovy b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/JoinRetainConditionsTest.groovy
index 35077a2390..f3fc666644 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/JoinRetainConditionsTest.groovy
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/JoinRetainConditionsTest.groovy
@@ -123,6 +123,81 @@ class JoinRetainConditionsTest extends AbstractMergeCellMigratorTest {
 		})
 	}
 
+	/**
+	 * Test if conditions on the type are properly migrated for property relations.
+	 */
+	@Test
+	void testRetypeConditionRenameTypeFiltered() {
+		def toMigrate = this.class.getResource('/testcases/retain-join-conditions-retype/S-to-T.halex')
+		def cellId = 'aRename'
+
+		def matching = this.class.getResource('/testcases/retain-join-conditions-retype/ABC-to-S.halex')
+
+		def migrated = merge(cellId, toMigrate, matching)
+
+		// do checks
+
+		// filter
+		assertEquals(1, migrated.size())
+		JaxbAlignmentIO.printCell(migrated[0], System.out)
+
+		def expectedFilterA = 'NOT (a = \'100\')'
+
+		assertNotNull(migrated[0].source)
+		assertEquals(1, migrated[0].source.size())
+		Collection<? extends Entity> source = migrated[0].source.values()
+		((Collection<Entity>) source).each { e ->
+			def filter = e.definition.filter
+			if (e.definition.type.displayName == 'A') {
+				// expect filter part to have been propagated to A
+				assertNotNull(filter)
+				assertEquals(expectedFilterA, filter.filterTerm)
+			}
+			else {
+				fail('Unexpected entity')
+			}
+		}
+
+		// there should be a message about the condition having been translated automatically
+		def messages = getMigrationMessages(migrated[0])
+		assertTrue(messages.any { msg ->
+			msg.text.toLowerCase().contains('condition')
+		})
+	}
+
+	/**
+	 * Test that a property relation that previously had no type filter still has no type filter after migration.
+	 */
+	@Test
+	void testRetypeConditionRenameTypeInherited() {
+		def toMigrate = this.class.getResource('/testcases/retain-join-conditions-retype/S-to-T.halex')
+		def cellId = 'bRename'
+
+		def matching = this.class.getResource('/testcases/retain-join-conditions-retype/ABC-to-S.halex')
+
+		def migrated = merge(cellId, toMigrate, matching)
+
+		// do checks
+
+		// filter
+		assertEquals(1, migrated.size())
+		JaxbAlignmentIO.printCell(migrated[0], System.out)
+
+		assertNotNull(migrated[0].source)
+		assertEquals(1, migrated[0].source.size())
+		Collection<? extends Entity> source = migrated[0].source.values()
+		((Collection<Entity>) source).each { e ->
+			def filter = e.definition.filter
+			if (e.definition.type.displayName == 'B') {
+				// there should be no filter
+				assertNull(filter)
+			}
+			else {
+				fail('Unexpected entity')
+			}
+		}
+	}
+
 	@Test
 	void testJoinCondition() {
 		def toMigrate = this.class.getResource('/testcases/retain-condition-join/B-to-C.halex')
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/S-to-T.halex b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/S-to-T.halex
index 98d774624f..1e62ad98f6 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/S-to-T.halex
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/S-to-T.halex
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<hale-project version="5.1.0.qualifier">
+<hale-project version="5.2.0.qualifier">
     <name>S to T (to be migrated)</name>
     <author>Simon Templer</author>
     <created>2018-01-10T16:26:35.275+01:00</created>
-    <modified>2024-02-05T11:15:17.691+01:00</modified>
+    <modified>2024-03-12T13:27:44.936+01:00</modified>
     <save-config action-id="project.save" provider-id="eu.esdihumboldt.hale.io.project.hale25.xml.writer">
         <setting name="charset">UTF-8</setting>
         <setting name="projectFiles.separate">false</setting>
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/S-to-T.halex.alignment.xml b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/S-to-T.halex.alignment.xml
index a37cb59750..ab1ed4de7c 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/S-to-T.halex.alignment.xml
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype/S-to-T.halex.alignment.xml
@@ -16,10 +16,12 @@
         <parameter value="false" name="ignoreNamespaces"/>
         <parameter value="false" name="structuralRename"/>
     </cell>
-    <cell relation="eu.esdihumboldt.hale.align.rename" id="C5a797cd6-1a6b-4692-ad14-115fd819a068" priority="normal">
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="aRename" priority="normal">
         <source>
             <property>
-                <type name="S" ns="S"/>
+                <type name="S" ns="S">
+                    <condition lang="ECQL">a &lt;&gt; '100' AND (b = '1000' OR b = '1001') AND c IS NOT NULL</condition>
+                </type>
                 <child name="a" ns="S"/>
             </property>
         </source>
@@ -32,7 +34,7 @@
         <parameter value="false" name="ignoreNamespaces"/>
         <parameter value="false" name="structuralRename"/>
     </cell>
-    <cell relation="eu.esdihumboldt.hale.align.rename" id="Cda0bda4c-63b9-4120-ad93-df726cbad7d6" priority="normal">
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="bRename" priority="normal">
         <source>
             <property>
                 <type name="S" ns="S"/>
@@ -48,7 +50,7 @@
         <parameter value="false" name="ignoreNamespaces"/>
         <parameter value="false" name="structuralRename"/>
     </cell>
-    <cell relation="eu.esdihumboldt.hale.align.rename" id="Ce600b8bf-fdd8-447b-aa31-074fd48b431e" priority="normal">
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="cRename" priority="normal">
         <source>
             <property>
                 <type name="S" ns="S"/>

From e5625820911e500d960192099d2d1db14b52ee4f Mon Sep 17 00:00:00 2001
From: Simon Templer <simon@wetransform.to>
Date: Tue, 12 Mar 2024 16:18:03 +0100
Subject: [PATCH 07/13] build(deps): update equinox-test update site

---
 platform/hale-platform.target | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/platform/hale-platform.target b/platform/hale-platform.target
index dc448faab3..287d3591f2 100644
--- a/platform/hale-platform.target
+++ b/platform/hale-platform.target
@@ -43,10 +43,6 @@
 <repository location="http://build-artifacts.wetransform.to/p2/mirror/gef-legacy-releases/"/>
 <unit id="org.eclipse.gef.all.feature.group" version="3.11.0.201606061308"/>
 </location>
-	<location includeAllPlatforms="false" includeConfigurePhase="false" includeMode="slicer" includeSource="true" type="InstallableUnit">
-		<repository location="http://build-artifacts.wetransform.to/p2/equinox-test/build_4"/>
-		<unit id="de.fhg.igd.equinox.test.feature.feature.group" version="1.2.0.202203220819"/>
-	</location>
 	<location includeAllPlatforms="false" includeConfigurePhase="false" includeMode="slicer" includeSource="true" type="InstallableUnit">
 		<repository location="http://build-artifacts.wetransform.to/p2/offline-resources/current"/>
 		<unit id="to.wetransform.offlineresources.feature.feature.group" version="2024.3.8.bnd-W1hqew"/>
@@ -55,5 +51,9 @@
 		<repository location="https://gitlab.wetransform.to/hale/hale-build-support/raw/0342a0f0c4f57a1ec4109f1fbe6e6fecd7dc722b/updatesites/platform"/>
 		<unit id="eu.esdihumboldt.hale.platform.feature.group" version="5.0.0.i20240220"/>
 	</location>
+	<location includeAllPlatforms="false" includeConfigurePhase="false" includeMode="slicer" includeSource="true" type="InstallableUnit">
+		<repository location="http://build-artifacts.wetransform.to/p2/equinox-test/1.2.0.20240312"/>
+		<unit id="de.fhg.igd.equinox.test.feature.feature.group" version="1.2.0.202403121501"/>
+	</location>
 </locations>
 </target>
\ No newline at end of file

From c8ff2c8b5d37234412d556b39e1c7e651e0655cf Mon Sep 17 00:00:00 2001
From: Simon Templer <simon@wetransform.to>
Date: Tue, 12 Mar 2024 17:08:45 +0100
Subject: [PATCH 08/13] WIP another test

---
 .../test/impl/JoinRetainConditionsTest.groovy |  43 +++++++
 .../ABC-to-S.halex                            |  27 +++++
 .../ABC-to-S.halex.alignment.xml              | 106 ++++++++++++++++++
 .../ABC-to-S.halex.styles.sld                 |   3 +
 .../retain-join-conditions-retype2/ABC.groovy |  15 +++
 .../S-to-T.halex                              |  27 +++++
 .../S-to-T.halex.alignment.xml                |  69 ++++++++++++
 .../S-to-T.halex.styles.sld                   |   3 +
 .../retain-join-conditions-retype2/S.groovy   |   7 ++
 .../retain-join-conditions-retype2/T.groovy   |   7 ++
 10 files changed, 307 insertions(+)
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/ABC-to-S.halex
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/ABC-to-S.halex.alignment.xml
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/ABC-to-S.halex.styles.sld
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/ABC.groovy
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/S-to-T.halex
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/S-to-T.halex.alignment.xml
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/S-to-T.halex.styles.sld
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/S.groovy
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/T.groovy

diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/JoinRetainConditionsTest.groovy b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/JoinRetainConditionsTest.groovy
index f3fc666644..efb6e2ab7d 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/JoinRetainConditionsTest.groovy
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/JoinRetainConditionsTest.groovy
@@ -165,6 +165,49 @@ class JoinRetainConditionsTest extends AbstractMergeCellMigratorTest {
 		})
 	}
 
+	/**
+	 * Test if conditions on the type are properly migrated for property relations.
+	 * Migrating conditions involves replacing property names in the filter. 
+	 */
+	@Test
+	void testRetypeConditionRenameTypeFilteredTranslated() {
+		def toMigrate = this.class.getResource('/testcases/retain-join-conditions-retype2/S-to-T.halex')
+		def cellId = 'aRename'
+
+		def matching = this.class.getResource('/testcases/retain-join-conditions-retype2/ABC-to-S.halex')
+
+		def migrated = merge(cellId, toMigrate, matching)
+
+		// do checks
+
+		// filter
+		assertEquals(1, migrated.size())
+		JaxbAlignmentIO.printCell(migrated[0], System.out)
+
+		def expectedFilterA = 'NOT (a = \'100\')'
+
+		assertNotNull(migrated[0].source)
+		assertEquals(1, migrated[0].source.size())
+		Collection<? extends Entity> source = migrated[0].source.values()
+		((Collection<Entity>) source).each { e ->
+			def filter = e.definition.filter
+			if (e.definition.type.displayName == 'A') {
+				// expect filter part to have been propagated to A
+				assertNotNull(filter)
+				assertEquals(expectedFilterA, filter.filterTerm)
+			}
+			else {
+				fail('Unexpected entity')
+			}
+		}
+
+		// there should be a message about the condition having been translated automatically
+		def messages = getMigrationMessages(migrated[0])
+		assertTrue(messages.any { msg ->
+			msg.text.toLowerCase().contains('condition')
+		})
+	}
+
 	/**
 	 * Test that a property relation that previously had no type filter still has no type filter after migration.
 	 */
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/ABC-to-S.halex b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/ABC-to-S.halex
new file mode 100644
index 0000000000..ea371d8f71
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/ABC-to-S.halex
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<hale-project version="5.1.0.qualifier">
+    <name>ABC to S (matching)</name>
+    <author>Simon Templer</author>
+    <created>2018-01-10T16:17:32.757+01:00</created>
+    <modified>2024-02-05T11:12:38.984+01:00</modified>
+    <save-config action-id="project.save" provider-id="eu.esdihumboldt.hale.io.project.hale25.xml.writer">
+        <setting name="charset">UTF-8</setting>
+        <setting name="projectFiles.separate">false</setting>
+        <setting name="contentType">eu.esdihumboldt.hale.io.project.hale25.xml</setting>
+        <setting name="target">file:/home/simon/repos/hale/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/ABC-to-S.halex</setting>
+    </save-config>
+    <resource action-id="eu.esdihumboldt.hale.io.schema.read.source" provider-id="eu.esdihumboldt.hale.io.schemabuilder">
+        <setting name="charset">UTF-8</setting>
+        <setting name="resourceId">6534b899-7ef3-4ead-8e4f-9af3af16f030</setting>
+        <setting name="source">ABC.groovy</setting>
+        <setting name="contentType">eu.esdihumboldt.hale.io.schemabuilder</setting>
+    </resource>
+    <resource action-id="eu.esdihumboldt.hale.io.schema.read.target" provider-id="eu.esdihumboldt.hale.io.schemabuilder">
+        <setting name="charset">UTF-8</setting>
+        <setting name="resourceId">67afcd12-5dd4-4adc-ab52-042786a4b3af</setting>
+        <setting name="source">S.groovy</setting>
+        <setting name="contentType">eu.esdihumboldt.hale.io.schemabuilder</setting>
+    </resource>
+    <file name="alignment.xml" location="ABC-to-S.halex.alignment.xml"/>
+    <file name="styles.sld" location="ABC-to-S.halex.styles.sld"/>
+</hale-project>
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/ABC-to-S.halex.alignment.xml b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/ABC-to-S.halex.alignment.xml
new file mode 100644
index 0000000000..1e8f3bc303
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/ABC-to-S.halex.alignment.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<alignment xmlns="http://www.esdi-humboldt.eu/hale/alignment">
+    <cell relation="eu.esdihumboldt.hale.align.join" id="C052fa5be-faf8-441e-9e55-bd4a6d271834" priority="normal">
+        <source name="types">
+            <class>
+                <type name="B" ns="ABC"/>
+            </class>
+        </source>
+        <source name="types">
+            <class>
+                <type name="C" ns="ABC"/>
+            </class>
+        </source>
+        <source name="types">
+            <class>
+                <type name="A" ns="ABC"/>
+            </class>
+        </source>
+        <target>
+            <class>
+                <type name="S" ns="S"/>
+            </class>
+        </target>
+        <complexParameter name="join">
+            <jp:join-parameter xmlns:jp="http://www.esdi-humboldt.eu/hale/join">
+                <class>
+                    <type name="A" ns="ABC"/>
+                </class>
+                <class>
+                    <type name="B" ns="ABC"/>
+                </class>
+                <class>
+                    <type name="C" ns="ABC"/>
+                </class>
+                <jp:condition>
+                    <property>
+                        <type name="A" ns="ABC"/>
+                        <child name="a" ns="ABC"/>
+                    </property>
+                    <property>
+                        <type name="B" ns="ABC"/>
+                        <child name="a" ns="ABC"/>
+                    </property>
+                </jp:condition>
+                <jp:condition>
+                    <property>
+                        <type name="B" ns="ABC"/>
+                        <child name="b" ns="ABC"/>
+                    </property>
+                    <property>
+                        <type name="C" ns="ABC"/>
+                        <child name="b" ns="ABC"/>
+                    </property>
+                </jp:condition>
+            </jp:join-parameter>
+        </complexParameter>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="C10d44c4c-8346-48ec-bff9-0b5f305f82c8" priority="normal">
+        <source>
+            <property>
+                <type name="A" ns="ABC"/>
+                <child name="a" ns="ABC"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="S" ns="S"/>
+                <child name="sourceA" ns="S"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="C531ccec5-b548-49ce-8aac-e2b74c9b6b93" priority="normal">
+        <source>
+            <property>
+                <type name="B" ns="ABC"/>
+                <child name="b" ns="ABC"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="S" ns="S"/>
+                <child name="sourceB" ns="S"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="C2636c3cf-fc30-418d-b22b-803c794c0fa4" priority="normal">
+        <source>
+            <property>
+                <type name="C" ns="ABC"/>
+                <child name="c" ns="ABC"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="S" ns="S"/>
+                <child name="sourceC" ns="S"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+</alignment>
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/ABC-to-S.halex.styles.sld b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/ABC-to-S.halex.styles.sld
new file mode 100644
index 0000000000..ad34f540f6
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/ABC-to-S.halex.styles.sld
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?><sld:UserStyle xmlns="http://www.opengis.net/sld" xmlns:sld="http://www.opengis.net/sld" xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc">
+  <sld:Name>Default Styler</sld:Name>
+</sld:UserStyle>
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/ABC.groovy b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/ABC.groovy
new file mode 100644
index 0000000000..29f9fb2411
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/ABC.groovy
@@ -0,0 +1,15 @@
+schema('ABC') {
+	A {
+		a()
+	}
+	
+	B {
+		a()
+		b(cardinality: '?')
+	}
+	
+	C {
+		b()
+		c(cardinality: '?')
+	}
+}
\ No newline at end of file
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/S-to-T.halex b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/S-to-T.halex
new file mode 100644
index 0000000000..ead9673df5
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/S-to-T.halex
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<hale-project version="5.2.0.qualifier">
+    <name>S to T (to be migrated)</name>
+    <author>Simon Templer</author>
+    <created>2018-01-10T16:26:35.275+01:00</created>
+    <modified>2024-03-12T13:27:44.936+01:00</modified>
+    <save-config action-id="project.save" provider-id="eu.esdihumboldt.hale.io.project.hale25.xml.writer">
+        <setting name="charset">UTF-8</setting>
+        <setting name="projectFiles.separate">false</setting>
+        <setting name="contentType">eu.esdihumboldt.hale.io.project.hale25.xml</setting>
+        <setting name="target">file:/home/simon/repos/hale/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/S-to-T.halex</setting>
+    </save-config>
+    <resource action-id="eu.esdihumboldt.hale.io.schema.read.source" provider-id="eu.esdihumboldt.hale.io.schemabuilder">
+        <setting name="charset">UTF-8</setting>
+        <setting name="resourceId">bafa16f0-75ed-44e3-a0ed-10e5410d044d</setting>
+        <setting name="source">S.groovy</setting>
+        <setting name="contentType">eu.esdihumboldt.hale.io.schemabuilder</setting>
+    </resource>
+    <resource action-id="eu.esdihumboldt.hale.io.schema.read.target" provider-id="eu.esdihumboldt.hale.io.schemabuilder">
+        <setting name="charset">UTF-8</setting>
+        <setting name="resourceId">e13b67b2-8b9a-45eb-b812-c8c675028b48</setting>
+        <setting name="source">T.groovy</setting>
+        <setting name="contentType">eu.esdihumboldt.hale.io.schemabuilder</setting>
+    </resource>
+    <file name="alignment.xml" location="S-to-T.halex.alignment.xml"/>
+    <file name="styles.sld" location="S-to-T.halex.styles.sld"/>
+</hale-project>
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/S-to-T.halex.alignment.xml b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/S-to-T.halex.alignment.xml
new file mode 100644
index 0000000000..b999738459
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/S-to-T.halex.alignment.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<alignment xmlns="http://www.esdi-humboldt.eu/hale/alignment">
+    <cell relation="eu.esdihumboldt.hale.align.retype" id="SabcT" priority="normal">
+        <source>
+            <class>
+                <type name="S" ns="S">
+                    <condition lang="ECQL">sourceA &lt;&gt; '100' AND (sourceB = '1000' OR sourceB = '1001') AND sourceC IS NOT NULL</condition>
+                </type>
+            </class>
+        </source>
+        <target>
+            <class>
+                <type name="T" ns="T"/>
+            </class>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="aRename" priority="normal">
+        <source>
+            <property>
+                <type name="S" ns="S">
+                    <condition lang="ECQL">sourceA &lt;&gt; '100' AND (sourceB = '1000' OR sourceB = '1001') AND sourceC IS NOT NULL</condition>
+                </type>
+                <child name="sourceA" ns="S"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="T" ns="T"/>
+                <child name="targetA" ns="T"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="bRename" priority="normal">
+        <source>
+            <property>
+                <type name="S" ns="S"/>
+                <child name="sourceB" ns="S"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="T" ns="T"/>
+                <child name="targetB" ns="T"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="cRename" priority="normal">
+        <source>
+            <property>
+                <type name="S" ns="S"/>
+                <child name="sourceC" ns="S"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="T" ns="T"/>
+                <child name="targetC" ns="T"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+</alignment>
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/S-to-T.halex.styles.sld b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/S-to-T.halex.styles.sld
new file mode 100644
index 0000000000..ad34f540f6
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/S-to-T.halex.styles.sld
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?><sld:UserStyle xmlns="http://www.opengis.net/sld" xmlns:sld="http://www.opengis.net/sld" xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc">
+  <sld:Name>Default Styler</sld:Name>
+</sld:UserStyle>
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/S.groovy b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/S.groovy
new file mode 100644
index 0000000000..65cdddccbc
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/S.groovy
@@ -0,0 +1,7 @@
+schema('S') {
+	S {
+		sourceA()
+		sourceB(cardinality: '?')
+		sourceC(cardinality: '?')
+	}
+}
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/T.groovy b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/T.groovy
new file mode 100644
index 0000000000..2bbca8b4ba
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/retain-join-conditions-retype2/T.groovy
@@ -0,0 +1,7 @@
+schema('T') {
+	T {
+		targetA()
+		targetB(cardinality: '?')
+		targetC(cardinality: '?')
+	}
+}

From 6b37a3deeb0375770e09ca50f0bea006944713c8 Mon Sep 17 00:00:00 2001
From: Simon Templer <simon@wetransform.to>
Date: Tue, 12 Mar 2024 17:09:04 +0100
Subject: [PATCH 09/13] WIP testing property filter translation

---
 .../align/merge/impl/AbstractMigration.groovy       | 13 ++++++++++++-
 .../hale/common/filter/AbstractGeotoolsFilter.java  |  8 ++++++++
 2 files changed, 20 insertions(+), 1 deletion(-)

diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/AbstractMigration.groovy b/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/AbstractMigration.groovy
index 9e0305bc78..da09cb1065 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/AbstractMigration.groovy
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/AbstractMigration.groovy
@@ -22,6 +22,7 @@ import eu.esdihumboldt.hale.common.align.model.ChildContext
 import eu.esdihumboldt.hale.common.align.model.Condition
 import eu.esdihumboldt.hale.common.align.model.EntityDefinition
 import eu.esdihumboldt.hale.common.align.model.impl.PropertyEntityDefinition
+import eu.esdihumboldt.hale.common.align.model.impl.TypeEntityDefinition
 import eu.esdihumboldt.hale.common.core.report.SimpleLog
 import eu.esdihumboldt.hale.common.instance.extension.filter.FilterDefinitionManager
 import eu.esdihumboldt.hale.common.schema.SchemaSpaceID
@@ -62,6 +63,8 @@ abstract class AbstractMigration implements AlignmentMigration {
 		if (matchedEntity.present) {
 			// entity contained contexts -> translate them if possible
 
+			//XXX target was determined (matchedEntity), do we need to know about how the match was found, e.g. if it was a join?
+
 			matchedEntity = Optional.ofNullable(translateContexts(entity, matchedEntity.get(), this, preferRoot, log));
 		}
 
@@ -94,7 +97,15 @@ abstract class AbstractMigration implements AlignmentMigration {
 				if (!sameEntity(original, target)) {
 					// replacements in filter if possible
 					if (filter instanceof EntityAwareFilter) {
-						def migrated = ((EntityAwareFilter) filter).migrateFilter(AlignmentUtil.getTypeEntity(original), migration, preferRoot, log)
+						// def migrated = ((EntityAwareFilter) filter).migrateFilter(AlignmentUtil.getTypeEntity(original), migration, preferRoot, log)
+
+						if (preferRoot == null) {
+							TypeEntityDefinition originalType = AlignmentUtil.getTypeEntity(original)
+							//FIXME if match for originalType is a Join and then set preferRoot to target type?
+						}
+
+						//FIXME for testing, replace preferRoot by target
+						def migrated = ((EntityAwareFilter) filter).migrateFilter(AlignmentUtil.getTypeEntity(original), migration, target.type, log)
 						if (migrated.present) {
 							filter = migrated.get()
 							//TODO mark automatically migrated?
diff --git a/common/plugins/eu.esdihumboldt.hale.common.filter/src/eu/esdihumboldt/hale/common/filter/AbstractGeotoolsFilter.java b/common/plugins/eu.esdihumboldt.hale.common.filter/src/eu/esdihumboldt/hale/common/filter/AbstractGeotoolsFilter.java
index 57be597a9e..fad0964202 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.filter/src/eu/esdihumboldt/hale/common/filter/AbstractGeotoolsFilter.java
+++ b/common/plugins/eu.esdihumboldt.hale.common.filter/src/eu/esdihumboldt/hale/common/filter/AbstractGeotoolsFilter.java
@@ -188,7 +188,15 @@ public List<Optional<EntityDefinition>> getReferencedEntities(EntityDefinition c
 	public Optional<eu.esdihumboldt.hale.common.instance.model.Filter> migrateFilter(
 			EntityDefinition context, AlignmentMigration migration, TypeDefinition preferRoot,
 			SimpleLog log) {
+		// TODO pass in target entity or target type, so we can use that
+		// information to determine, if filter conditions actually apply
+		// XXX can preferRoot be used or do we need to differ due to Join?
+
 		// split filter (AND operands)
+		// TODO split based on different operand based on if there is a join or
+		// not
+		// FIXME what about properties related to a Join -> we want to drop the
+		// AND parts - how do we know the context is a join?
 		List<Filter> andParts = splitAnd(internFilter);
 
 		// migrate each filter part

From eb02a86d0763364087f1c06d43bdaf39ce640d5b Mon Sep 17 00:00:00 2001
From: Simon Templer <simon@wetransform.to>
Date: Wed, 13 Mar 2024 10:19:12 +0100
Subject: [PATCH 10/13] WIP filter splitting based on join information,
 restrict property filter migration to parent type

---
 .../align/merge/functions/JoinContext.java    |  5 +-
 .../merge/functions/JoinMergeMigrator.java    |  9 ++-
 .../merge/impl/AbstractMergeCellMigrator.java | 29 ++++---
 .../align/merge/impl/AbstractMigration.groovy | 36 ++++-----
 .../align/merge/impl/MatchingMigration.groovy | 43 +++++++++--
 .../align/instance/EntityAwareFilter.java     |  9 ++-
 .../align/migrate/AlignmentMigration.java     |  4 +-
 .../common/align/migrate/EntityMatch.groovy   | 54 +++++++++++++
 .../migrate/impl/DefaultCellMigrator.java     |  5 +-
 .../align/migrate/impl/UnmigratedCell.java    |  6 +-
 .../functions/FormattedStringMigrator.java    |  6 +-
 .../model/functions/join/JoinMigrator.java    | 10 ++-
 .../model/functions/merge/MergeMigrator.java  |  4 +-
 .../META-INF/MANIFEST.MF                      |  3 +-
 .../common/filter/AbstractGeotoolsFilter.java | 75 ++++++++++++-------
 .../internal/EntityReplacementVisitor.java    | 10 ++-
 .../service/align/migrate/UserMigration.java  |  7 +-
 17 files changed, 223 insertions(+), 92 deletions(-)
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/migrate/EntityMatch.groovy

diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/functions/JoinContext.java b/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/functions/JoinContext.java
index a199bea7e9..385bd7b64c 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/functions/JoinContext.java
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/functions/JoinContext.java
@@ -33,6 +33,7 @@
 import eu.esdihumboldt.cst.functions.groovy.GroovyJoin;
 import eu.esdihumboldt.hale.common.align.merge.MergeUtil;
 import eu.esdihumboldt.hale.common.align.migrate.AlignmentMigration;
+import eu.esdihumboldt.hale.common.align.migrate.EntityMatch;
 import eu.esdihumboldt.hale.common.align.model.AlignmentUtil;
 import eu.esdihumboldt.hale.common.align.model.Cell;
 import eu.esdihumboldt.hale.common.align.model.CellUtil;
@@ -245,12 +246,12 @@ private PropertyEntityDefinition processOriginalConditionProperty(
 		});
 		if (!isStripped) {
 			return (PropertyEntityDefinition) migration.entityReplacement(property, log)
-					.orElse(property);
+					.map(EntityMatch::getMatch).orElse(property);
 		}
 		else {
 			EntityDefinition stripped = AlignmentUtil.getAllDefaultEntity(property);
 			return (PropertyEntityDefinition) migration.entityReplacement(stripped, log)
-					.orElse(stripped);
+					.map(EntityMatch::getMatch).orElse(stripped);
 		}
 	}
 
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/functions/JoinMergeMigrator.java b/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/functions/JoinMergeMigrator.java
index 3eacaad6cf..64b0e17124 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/functions/JoinMergeMigrator.java
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/functions/JoinMergeMigrator.java
@@ -29,6 +29,7 @@
 import eu.esdihumboldt.hale.common.align.merge.impl.AbstractMergeCellMigrator;
 import eu.esdihumboldt.hale.common.align.merge.impl.AbstractMigration;
 import eu.esdihumboldt.hale.common.align.migrate.AlignmentMigration;
+import eu.esdihumboldt.hale.common.align.migrate.EntityMatch;
 import eu.esdihumboldt.hale.common.align.model.AlignmentUtil;
 import eu.esdihumboldt.hale.common.align.model.Cell;
 import eu.esdihumboldt.hale.common.align.model.CellUtil;
@@ -188,9 +189,13 @@ private void addSources(MutableCell cell, EntityDefinition source, Cell match,
 				Entity entity = entry.getValue();
 
 				if (transferContext.test(entity.getDefinition())) {
+					// XXX should the match be marked as from a Join? Unclear in
+					// this context
+					EntityMatch e = EntityMatch.of(entity.getDefinition());
+
 					// transfer filter and contexts if possible
-					EntityDefinition withContexts = AbstractMigration.translateContexts(source,
-							entity.getDefinition(), migration, null, log);
+					EntityDefinition withContexts = AbstractMigration
+							.translateContexts(source, e, migration, null, log).getMatch();
 					if (withContexts.getFilter() != null) {
 						context.addTypeFilter(withContexts.getType(), withContexts.getFilter());
 					}
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/AbstractMergeCellMigrator.java b/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/AbstractMergeCellMigrator.java
index f4e9e8e6f6..23cdc486ad 100755
--- a/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/AbstractMergeCellMigrator.java
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/AbstractMergeCellMigrator.java
@@ -41,6 +41,7 @@
 import eu.esdihumboldt.hale.common.align.merge.MergeUtil;
 import eu.esdihumboldt.hale.common.align.migrate.AlignmentMigration;
 import eu.esdihumboldt.hale.common.align.migrate.CellMigrator;
+import eu.esdihumboldt.hale.common.align.migrate.EntityMatch;
 import eu.esdihumboldt.hale.common.align.migrate.MigrationOptions;
 import eu.esdihumboldt.hale.common.align.migrate.impl.DefaultCellMigrator;
 import eu.esdihumboldt.hale.common.align.migrate.impl.MigrationOptionsImpl;
@@ -158,11 +159,12 @@ else if (isDirectMatch(originalCell)) {
 						AlignmentMigration cellMigration = new AbstractMigration() {
 
 							@Override
-							protected Optional<EntityDefinition> findMatch(EntityDefinition entity,
+							protected Optional<EntityMatch> findMatch(EntityDefinition entity,
 									TypeDefinition preferRoot) {
 								Entity target = CellUtil.getFirstEntity(originalCell.getTarget());
 								if (target != null) {
-									return Optional.ofNullable(target.getDefinition());
+									return Optional
+											.ofNullable(EntityMatch.of(target.getDefinition()));
 								}
 								return Optional.empty();
 							}
@@ -360,7 +362,8 @@ else if (newSource.size() == 1) {
 					Entity singleSource = CellUtil.getFirstEntity(newSource);
 					if (singleSource != null) {
 						EntityDefinition transferedSource = AbstractMigration.translateContexts(
-								original, singleSource.getDefinition(), migration, null, log);
+								original, EntityMatch.of(singleSource.getDefinition()), migration,
+								null, log).getMatch();
 						ListMultimap<String, Entity> s = ArrayListMultimap.create();
 						s.put(newSource.keySet().iterator().next(),
 								AlignmentUtil.createEntity(transferedSource));
@@ -437,9 +440,12 @@ public Entity apply(Entity input) {
 						TypeDefinition inputType = input.getDefinition().getType();
 						if (input.getDefinition().getPropertyPath().isEmpty()
 								&& joinTypes.contains(inputType)) {
-							EntityDefinition transferedSource = AbstractMigration.translateContexts(
-									originalSource.getDefinition(), input.getDefinition(),
-									migration, inputType, log);
+							EntityDefinition transferedSource = AbstractMigration
+									.translateContexts(originalSource.getDefinition(),
+											new EntityMatch(input.getDefinition(),
+													joinTypes.size() > 0, true),
+											migration, inputType, log)
+									.getMatch();
 							joinTypeFilters.put(inputType, transferedSource.getFilter());
 							return AlignmentUtil.createEntity(transferedSource);
 						}
@@ -533,6 +539,8 @@ private boolean applySourceContextsToJoinFocus(MutableCell newCell, Entity origi
 		TypeEntityDefinition focus = joinConfig.getTypes().iterator().next();
 		AtomicReference<Filter> focusFilter = new AtomicReference<>();
 
+		boolean multipleSources = newCell.getSource().size() > 0;
+
 		// transfer context
 		newCell.setSource(ArrayListMultimap.create(Multimaps.transformValues(newCell.getSource(),
 				new com.google.common.base.Function<Entity, Entity>() {
@@ -541,9 +549,12 @@ private boolean applySourceContextsToJoinFocus(MutableCell newCell, Entity origi
 					public Entity apply(Entity input) {
 						if (input.getDefinition().getPropertyPath().isEmpty()
 								&& input.getDefinition().getType().equals(focus.getType())) {
-							EntityDefinition transferedSource = AbstractMigration.translateContexts(
-									originalSource.getDefinition(), input.getDefinition(),
-									migration, focus.getType(), log);
+							EntityDefinition transferedSource = AbstractMigration
+									.translateContexts(originalSource.getDefinition(),
+											new EntityMatch(input.getDefinition(), multipleSources,
+													true),
+											migration, focus.getType(), log)
+									.getMatch();
 							focusFilter.set(transferedSource.getFilter());
 							return AlignmentUtil.createEntity(transferedSource);
 						}
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/AbstractMigration.groovy b/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/AbstractMigration.groovy
index da09cb1065..20c114995c 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/AbstractMigration.groovy
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/AbstractMigration.groovy
@@ -17,12 +17,12 @@ package eu.esdihumboldt.hale.common.align.merge.impl
 
 import eu.esdihumboldt.hale.common.align.instance.EntityAwareFilter
 import eu.esdihumboldt.hale.common.align.migrate.AlignmentMigration
+import eu.esdihumboldt.hale.common.align.migrate.EntityMatch
 import eu.esdihumboldt.hale.common.align.model.AlignmentUtil
 import eu.esdihumboldt.hale.common.align.model.ChildContext
 import eu.esdihumboldt.hale.common.align.model.Condition
 import eu.esdihumboldt.hale.common.align.model.EntityDefinition
 import eu.esdihumboldt.hale.common.align.model.impl.PropertyEntityDefinition
-import eu.esdihumboldt.hale.common.align.model.impl.TypeEntityDefinition
 import eu.esdihumboldt.hale.common.core.report.SimpleLog
 import eu.esdihumboldt.hale.common.instance.extension.filter.FilterDefinitionManager
 import eu.esdihumboldt.hale.common.schema.SchemaSpaceID
@@ -45,12 +45,12 @@ abstract class AbstractMigration implements AlignmentMigration {
 	 * @param preferRoot hint on which entity to prefer if there are multiple matches
 	 * @return the match if found
 	 */
-	protected abstract Optional<EntityDefinition> findMatch(EntityDefinition entity, TypeDefinition preferRoot);
+	protected abstract Optional<EntityMatch> findMatch(EntityDefinition entity, TypeDefinition preferRoot);
 
 	@Override
-	Optional<EntityDefinition> entityReplacement(EntityDefinition entity, TypeDefinition preferRoot, SimpleLog log) {
+	Optional<EntityMatch> entityReplacement(EntityDefinition entity, TypeDefinition preferRoot, SimpleLog log) {
 		EntityDefinition defaultEntity = AlignmentUtil.getAllDefaultEntity(entity)
-		Optional<EntityDefinition> matchedEntity = findMatch(defaultEntity, preferRoot)
+		Optional<EntityMatch> matchedEntity = findMatch(defaultEntity, preferRoot)
 
 		// special case handling
 		if (!matchedEntity.isPresent()) {
@@ -62,9 +62,6 @@ abstract class AbstractMigration implements AlignmentMigration {
 
 		if (matchedEntity.present) {
 			// entity contained contexts -> translate them if possible
-
-			//XXX target was determined (matchedEntity), do we need to know about how the match was found, e.g. if it was a join?
-
 			matchedEntity = Optional.ofNullable(translateContexts(entity, matchedEntity.get(), this, preferRoot, log));
 		}
 
@@ -75,14 +72,15 @@ abstract class AbstractMigration implements AlignmentMigration {
 		return matchedEntity
 	}
 
-	static EntityDefinition translateContexts(EntityDefinition original, EntityDefinition target,
+	static EntityMatch translateContexts(EntityDefinition original, EntityMatch target,
 			AlignmentMigration migration, TypeDefinition preferRoot, SimpleLog log) {
 		def defaultEntity = AlignmentUtil.getAllDefaultEntity(original)
+		def targetEntity = target.getMatch()
 
 		if (original.filter) {
 
 			// what about if match has filter?
-			if (target.filter && original.filter != target.filter) {
+			if (targetEntity.filter && original.filter != targetEntity.filter) {
 				def filterString = FilterDefinitionManager.instance.asString(original.filter)
 				def msg = "Filter condition applied to the original source type has been dropped because a filter already existed for the entity it was replaced with. Please check if you need to change the condition to match both original conditions."
 				if (filterString) {
@@ -94,18 +92,16 @@ abstract class AbstractMigration implements AlignmentMigration {
 				// apply filter to entity
 				def filter = original.filter
 
-				if (!sameEntity(original, target)) {
+				if (!sameEntity(original, targetEntity)) {
 					// replacements in filter if possible
 					if (filter instanceof EntityAwareFilter) {
-						// def migrated = ((EntityAwareFilter) filter).migrateFilter(AlignmentUtil.getTypeEntity(original), migration, preferRoot, log)
-
 						if (preferRoot == null) {
-							TypeEntityDefinition originalType = AlignmentUtil.getTypeEntity(original)
-							//FIXME if match for originalType is a Join and then set preferRoot to target type?
+							// set preferRoot to target type
+							// to restrict conditions to the type they may reference
+							preferRoot = targetEntity.type
 						}
 
-						//FIXME for testing, replace preferRoot by target
-						def migrated = ((EntityAwareFilter) filter).migrateFilter(AlignmentUtil.getTypeEntity(original), migration, target.type, log)
+						def migrated = ((EntityAwareFilter) filter).migrateFilter(AlignmentUtil.getTypeEntity(original), target, migration, preferRoot, log)
 						if (migrated.present) {
 							filter = migrated.get()
 							//TODO mark automatically migrated?
@@ -122,17 +118,17 @@ abstract class AbstractMigration implements AlignmentMigration {
 				}
 
 				// add filter to match
-				target = AlignmentUtil.createEntity(target.type, target.propertyPath,
+				targetEntity = AlignmentUtil.createEntity(targetEntity.type, targetEntity.propertyPath,
 						SchemaSpaceID.SOURCE, filter)
 			}
 		}
 
 		if (original.propertyPath && original != defaultEntity) {
 			// likely a context was present
-			target = applyContexts(target, original, log)
+			targetEntity = applyContexts(targetEntity, original, log)
 		}
 
-		return target
+		return target.withMatch(targetEntity)
 	}
 
 	/**
@@ -224,7 +220,7 @@ abstract class AbstractMigration implements AlignmentMigration {
 		}
 	}
 
-	protected Optional<EntityDefinition> findParentMatch(EntityDefinition entity, TypeDefinition preferRoot) {
+	protected Optional<EntityMatch> findParentMatch(EntityDefinition entity, TypeDefinition preferRoot) {
 		//XXX only allow parent matches for specific cases right now
 		if (!(entity.definition instanceof PropertyDefinition) ||
 				!((PropertyDefinition) entity.definition).propertyType.getConstraint(GeometryType).isGeometry()) {
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/MatchingMigration.groovy b/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/MatchingMigration.groovy
index 4847f9a3c0..d63bae6174 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/MatchingMigration.groovy
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge/src/eu/esdihumboldt/hale/common/align/merge/impl/MatchingMigration.groovy
@@ -17,6 +17,7 @@ package eu.esdihumboldt.hale.common.align.merge.impl
 
 import java.util.function.Function
 
+import eu.esdihumboldt.hale.common.align.migrate.EntityMatch
 import eu.esdihumboldt.hale.common.align.model.AlignmentUtil
 import eu.esdihumboldt.hale.common.align.model.Cell
 import eu.esdihumboldt.hale.common.align.model.EntityDefinition
@@ -43,26 +44,52 @@ class MatchingMigration extends AbstractMigration {
 	}
 
 	@Override
-	protected Optional<EntityDefinition> findMatch(EntityDefinition entity, TypeDefinition preferRoot) {
+	protected Optional<EntityMatch> findMatch(EntityDefinition entity, TypeDefinition preferRoot) {
 		findMatches(entity).flatMap({ list ->
-			findPreferredCandidate((List<EntityDefinition>)list, preferRoot)
+			findPreferredCandidate((List<EntityDefinition>)list, entity, preferRoot)
 		} as Function)
 	}
 
-	private Optional<EntityDefinition> findPreferredCandidate(Collection<EntityDefinition> entities, TypeDefinition preferRoot) {
+	private Optional<EntityMatch> findPreferredCandidate(Collection<EntityDefinition> entities, EntityDefinition original, TypeDefinition preferRoot) {
 		if (entities.empty) {
 			return Optional.empty()
 		}
 
+		boolean multiple = entities.size() > 1
+		boolean join = false
+
+		if (original.propertyPath.isEmpty()) {
+			// this is about a type entity
+
+			/*
+			 * Currently, because findMatches only considers cases where exactly one cell is present,
+			 * we can assume that if there are multiple candidates that there is a join for the match.
+			 */
+			join = multiple
+		}
+		else {
+			// this is about a property entity
+
+			/*
+			 * For a property we need to determine if the respective type match is part of a join
+			 * and use that information. 
+			 */
+			def typeEntity = AlignmentUtil.getTypeEntity(original)
+			def typeMatch = findMatch(typeEntity, preferRoot)
+			if (typeMatch.isPresent()) {
+				join = typeMatch.get().matchPartOfJoin
+			}
+		}
+
+
 		/*
 		 * Note: preferRoot is used here to for example pick a specific type from a Join cell's sources 
 		 */
 		def firstPreferred = entities.find { EntityDefinition it -> it.type == preferRoot }
-		if (firstPreferred) {
-			Optional.of(firstPreferred)
-		}
-		else {
-			Optional.of(entities.iterator().next())
+		def match = firstPreferred ? Optional.of(firstPreferred) : Optional.of(entities.iterator().next())
+
+		match.map {
+			new EntityMatch(it, multiple, join)
 		}
 	}
 
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/instance/EntityAwareFilter.java b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/instance/EntityAwareFilter.java
index ceea97bc43..17c0f56284 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/instance/EntityAwareFilter.java
+++ b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/instance/EntityAwareFilter.java
@@ -19,6 +19,7 @@
 import java.util.Optional;
 
 import eu.esdihumboldt.hale.common.align.migrate.AlignmentMigration;
+import eu.esdihumboldt.hale.common.align.migrate.EntityMatch;
 import eu.esdihumboldt.hale.common.align.model.EntityDefinition;
 import eu.esdihumboldt.hale.common.core.report.SimpleLog;
 import eu.esdihumboldt.hale.common.instance.model.Filter;
@@ -43,7 +44,7 @@ public interface EntityAwareFilter extends Filter {
 
 	/**
 	 * States if the filter supports migration via
-	 * {@link #migrateFilter(EntityDefinition, AlignmentMigration, TypeDefinition, SimpleLog)}
+	 * {@link #migrateFilter(EntityDefinition, EntityMatch, AlignmentMigration, TypeDefinition, SimpleLog)}
 	 * 
 	 * @return <code>true</code> if migration is supported, <code>false</code>
 	 *         otherwise
@@ -55,13 +56,15 @@ public interface EntityAwareFilter extends Filter {
 	 * migration.
 	 * 
 	 * @param context the entity context
+	 * @param targetMatch the match representing the entity the filter is
+	 *            applied to
 	 * @param migration the alignment migration
 	 * @param preferRoot hint on which entity to prefer if there are multiple
 	 *            matches
 	 * @param log the operation log
 	 * @return the migrated filter, if migration is possible
 	 */
-	Optional<Filter> migrateFilter(EntityDefinition context, AlignmentMigration migration,
-			TypeDefinition preferRoot, SimpleLog log);
+	Optional<Filter> migrateFilter(EntityDefinition context, EntityMatch targetMatch,
+			AlignmentMigration migration, TypeDefinition preferRoot, SimpleLog log);
 
 }
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/migrate/AlignmentMigration.java b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/migrate/AlignmentMigration.java
index ff37f622b4..52eee484bc 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/migrate/AlignmentMigration.java
+++ b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/migrate/AlignmentMigration.java
@@ -35,7 +35,7 @@ public interface AlignmentMigration {
 	 * @param log the migration process log (may be cell specific)
 	 * @return the replacement entity, if the entity should be replaced
 	 */
-	default Optional<EntityDefinition> entityReplacement(EntityDefinition entity, SimpleLog log) {
+	default Optional<EntityMatch> entityReplacement(EntityDefinition entity, SimpleLog log) {
 		return entityReplacement(entity, null, log);
 	}
 
@@ -48,7 +48,7 @@ default Optional<EntityDefinition> entityReplacement(EntityDefinition entity, Si
 	 * @param log the migration process log (may be cell specific)
 	 * @return the replacement entity, if the entity should be replaced
 	 */
-	Optional<EntityDefinition> entityReplacement(EntityDefinition entity, TypeDefinition preferRoot,
+	Optional<EntityMatch> entityReplacement(EntityDefinition entity, TypeDefinition preferRoot,
 			SimpleLog log);
 
 }
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/migrate/EntityMatch.groovy b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/migrate/EntityMatch.groovy
new file mode 100644
index 0000000000..e9d222601a
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/migrate/EntityMatch.groovy
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2024 wetransform GmbH
+ * 
+ * All rights reserved. This program and the accompanying materials are made
+ * available under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 3 of the License,
+ * or (at your option) any later version.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this distribution. If not, see <http://www.gnu.org/licenses/>.
+ * 
+ * Contributors:
+ *     wetransform GmbH <http://www.wetransform.to>
+ */
+
+package eu.esdihumboldt.hale.common.align.migrate
+
+import eu.esdihumboldt.hale.common.align.model.EntityDefinition
+import groovy.transform.CompileStatic
+import groovy.transform.Immutable
+
+/**
+ * Type representing an entity match in an alignment migration with additional information.
+ * 
+ * @author Simon Templer
+ */
+@CompileStatic
+@Immutable(knownImmutableClasses = [EntityDefinition.class])
+class EntityMatch {
+	EntityDefinition match
+	boolean multipleCandidates
+	boolean matchPartOfJoin
+
+	/**
+	 * Create a copy of the match replacing the matched entity.
+	 * 
+	 * @param newMatch the entity to use as new match
+	 * @return the copy of the match with the replaced entity
+	 */
+	EntityMatch withMatch(EntityDefinition newMatch) {
+		return new EntityMatch(newMatch, multipleCandidates, matchPartOfJoin)
+	}
+
+	/**
+	 * Create an entity match using the provided entity with default values assuming
+	 * there is only this candidate and there is no join involved.
+	 * 
+	 * @param match the entity to use for the match
+	 * @return the entity match using the provided entity
+	 */
+	static EntityMatch of(EntityDefinition match) {
+		return new EntityMatch(match, false, false)
+	}
+}
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/migrate/impl/DefaultCellMigrator.java b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/migrate/impl/DefaultCellMigrator.java
index 2cee521a51..ea95417315 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/migrate/impl/DefaultCellMigrator.java
+++ b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/migrate/impl/DefaultCellMigrator.java
@@ -25,6 +25,7 @@
 
 import eu.esdihumboldt.hale.common.align.migrate.AlignmentMigration;
 import eu.esdihumboldt.hale.common.align.migrate.CellMigrator;
+import eu.esdihumboldt.hale.common.align.migrate.EntityMatch;
 import eu.esdihumboldt.hale.common.align.migrate.MigrationOptions;
 import eu.esdihumboldt.hale.common.align.model.Cell;
 import eu.esdihumboldt.hale.common.align.model.Entity;
@@ -60,9 +61,9 @@ public MutableCell updateCell(final Cell originalCell, final AlignmentMigration
 			public Entity transformEntry(String key, Entity value) {
 				EntityDefinition org = value.getDefinition();
 
-				Optional<EntityDefinition> replace = migration.entityReplacement(org, cellLog);
+				Optional<EntityMatch> replace = migration.entityReplacement(org, cellLog);
 
-				EntityDefinition entity = replace.orElse(org);
+				EntityDefinition entity = replace.map(m -> m.getMatch()).orElse(org);
 				// FIXME what about null replacements / removals?
 
 				if (!Objects.equal(entity, org)) {
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/migrate/impl/UnmigratedCell.java b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/migrate/impl/UnmigratedCell.java
index 5bd1f0a3c0..2284ef999a 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/migrate/impl/UnmigratedCell.java
+++ b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/migrate/impl/UnmigratedCell.java
@@ -24,6 +24,7 @@
 import eu.esdihumboldt.hale.common.align.migrate.AlignmentMigration;
 import eu.esdihumboldt.hale.common.align.migrate.AlignmentMigrationNameLookupSupport;
 import eu.esdihumboldt.hale.common.align.migrate.CellMigrator;
+import eu.esdihumboldt.hale.common.align.migrate.EntityMatch;
 import eu.esdihumboldt.hale.common.align.migrate.MigrationOptions;
 import eu.esdihumboldt.hale.common.align.model.EntityDefinition;
 import eu.esdihumboldt.hale.common.align.model.MutableCell;
@@ -82,9 +83,10 @@ public MutableCell migrate(Map<EntityDefinition, EntityDefinition> additionalMap
 		AlignmentMigration migration = new AlignmentMigrationNameLookupSupport() {
 
 			@Override
-			public Optional<EntityDefinition> entityReplacement(EntityDefinition entity,
+			public Optional<EntityMatch> entityReplacement(EntityDefinition entity,
 					TypeDefinition preferRoot, SimpleLog log) {
-				return Optional.ofNullable(joinedMappings.get(entity));
+				return Optional
+						.ofNullable(new EntityMatch(joinedMappings.get(entity), false, false));
 			}
 
 			@Override
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/model/functions/FormattedStringMigrator.java b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/model/functions/FormattedStringMigrator.java
index cf151635eb..b618cb21e9 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/model/functions/FormattedStringMigrator.java
+++ b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/model/functions/FormattedStringMigrator.java
@@ -28,11 +28,11 @@
 import com.google.common.collect.ListMultimap;
 
 import eu.esdihumboldt.hale.common.align.migrate.AlignmentMigration;
+import eu.esdihumboldt.hale.common.align.migrate.EntityMatch;
 import eu.esdihumboldt.hale.common.align.migrate.MigrationOptions;
 import eu.esdihumboldt.hale.common.align.migrate.impl.DefaultCellMigrator;
 import eu.esdihumboldt.hale.common.align.model.Cell;
 import eu.esdihumboldt.hale.common.align.model.Entity;
-import eu.esdihumboldt.hale.common.align.model.EntityDefinition;
 import eu.esdihumboldt.hale.common.align.model.MutableCell;
 import eu.esdihumboldt.hale.common.align.model.ParameterValue;
 import eu.esdihumboldt.hale.common.align.model.annotations.messages.CellLog;
@@ -85,8 +85,8 @@ private String convertPattern(String pattern, ListMultimap<String, ? extends Ent
 
 		Map<String, Object> replacements = new HashMap<>();
 		for (PropertyEntityDefinition var : oldVars) {
-			Optional<EntityDefinition> replacement = migration.entityReplacement(var, log);
-			replacement.ifPresent(repl -> {
+			Optional<EntityMatch> replacement = migration.entityReplacement(var, log);
+			replacement.map(m -> m.getMatch()).ifPresent(repl -> {
 				String newName = repl.getDefinition().getName().getLocalPart();
 				// XXX there might be name conflicts - check for those or use
 				// long names?
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/model/functions/join/JoinMigrator.java b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/model/functions/join/JoinMigrator.java
index 5d1cc10562..2e66689f40 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/model/functions/join/JoinMigrator.java
+++ b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/model/functions/join/JoinMigrator.java
@@ -23,6 +23,7 @@
 import com.google.common.collect.ListMultimap;
 
 import eu.esdihumboldt.hale.common.align.migrate.AlignmentMigration;
+import eu.esdihumboldt.hale.common.align.migrate.EntityMatch;
 import eu.esdihumboldt.hale.common.align.migrate.MigrationOptions;
 import eu.esdihumboldt.hale.common.align.migrate.impl.DefaultCellMigrator;
 import eu.esdihumboldt.hale.common.align.model.Cell;
@@ -80,14 +81,17 @@ private JoinParameter convertJoinParameter(JoinParameter joinParam,
 			AlignmentMigration migration, MigrationOptions options, SimpleLog log) {
 
 		List<TypeEntityDefinition> types = joinParam.getTypes().stream().map(type -> {
-			return (TypeEntityDefinition) migration.entityReplacement(type, log).orElse(type);
+			return (TypeEntityDefinition) migration.entityReplacement(type, log)
+					.map(EntityMatch::getMatch).orElse(type);
 		}).collect(Collectors.toList());
 
 		Set<JoinCondition> conditions = joinParam.getConditions().stream().map(condition -> {
 			PropertyEntityDefinition baseProperty = (PropertyEntityDefinition) migration
-					.entityReplacement(condition.baseProperty, log).orElse(condition.baseProperty);
+					.entityReplacement(condition.baseProperty, log).map(EntityMatch::getMatch)
+					.orElse(condition.baseProperty);
 			PropertyEntityDefinition joinProperty = (PropertyEntityDefinition) migration
-					.entityReplacement(condition.joinProperty, log).orElse(condition.joinProperty);
+					.entityReplacement(condition.joinProperty, log).map(EntityMatch::getMatch)
+					.orElse(condition.joinProperty);
 			JoinCondition result = new JoinCondition(baseProperty, joinProperty);
 			return result;
 		}).collect(Collectors.toSet());
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/model/functions/merge/MergeMigrator.java b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/model/functions/merge/MergeMigrator.java
index 706dfbe129..6157ebcbf0 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/model/functions/merge/MergeMigrator.java
+++ b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/model/functions/merge/MergeMigrator.java
@@ -27,6 +27,7 @@
 
 import eu.esdihumboldt.hale.common.align.migrate.AlignmentMigration;
 import eu.esdihumboldt.hale.common.align.migrate.AlignmentMigrationNameLookupSupport;
+import eu.esdihumboldt.hale.common.align.migrate.EntityMatch;
 import eu.esdihumboldt.hale.common.align.migrate.MigrationOptions;
 import eu.esdihumboldt.hale.common.align.migrate.impl.DefaultCellMigrator;
 import eu.esdihumboldt.hale.common.align.model.Cell;
@@ -106,7 +107,8 @@ private ParameterValue convertProperty(ParameterValue value, AlignmentMigration
 			return value;
 		}
 
-		Optional<EntityDefinition> replacement = migration.entityReplacement(entity, log);
+		Optional<EntityDefinition> replacement = migration.entityReplacement(entity, log)
+				.map(EntityMatch::getMatch);
 		if (replacement.isPresent()) {
 			return convertProperty(value, replacement.get(), log);
 		}
diff --git a/common/plugins/eu.esdihumboldt.hale.common.filter/META-INF/MANIFEST.MF b/common/plugins/eu.esdihumboldt.hale.common.filter/META-INF/MANIFEST.MF
index def593b6ad..9e3e50a3fa 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.filter/META-INF/MANIFEST.MF
+++ b/common/plugins/eu.esdihumboldt.hale.common.filter/META-INF/MANIFEST.MF
@@ -23,6 +23,7 @@ Export-Package: eu.esdihumboldt.hale.common.filter,
  eu.esdihumboldt.hale.common.filter.definition
 Require-Bundle: org.opengis;bundle-version="29.1.0",
  eu.esdihumboldt.util.groovy,
- org.geotools;bundle-version="29.1.0"
+ org.geotools;bundle-version="29.1.0",
+ groovy;bundle-version="2.5.19"
 Bundle-Vendor: data harmonisation panel
 Automatic-Module-Name: eu.esdihumboldt.hale.common.filter
diff --git a/common/plugins/eu.esdihumboldt.hale.common.filter/src/eu/esdihumboldt/hale/common/filter/AbstractGeotoolsFilter.java b/common/plugins/eu.esdihumboldt.hale.common.filter/src/eu/esdihumboldt/hale/common/filter/AbstractGeotoolsFilter.java
index fad0964202..c8c1e8d37d 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.filter/src/eu/esdihumboldt/hale/common/filter/AbstractGeotoolsFilter.java
+++ b/common/plugins/eu.esdihumboldt.hale.common.filter/src/eu/esdihumboldt/hale/common/filter/AbstractGeotoolsFilter.java
@@ -31,6 +31,7 @@
 import org.geotools.util.factory.GeoTools;
 import org.opengis.filter.And;
 import org.opengis.filter.Filter;
+import org.opengis.filter.Or;
 import org.opengis.filter.expression.PropertyName;
 
 import de.fhg.igd.slf4jplus.ALogger;
@@ -40,6 +41,7 @@
 import eu.esdihumboldt.hale.common.align.groovy.accessor.internal.EntityAccessorUtil;
 import eu.esdihumboldt.hale.common.align.instance.EntityAwareFilter;
 import eu.esdihumboldt.hale.common.align.migrate.AlignmentMigration;
+import eu.esdihumboldt.hale.common.align.migrate.EntityMatch;
 import eu.esdihumboldt.hale.common.align.model.EntityDefinition;
 import eu.esdihumboldt.hale.common.core.report.SimpleLog;
 import eu.esdihumboldt.hale.common.filter.internal.EntityReplacementVisitor;
@@ -59,6 +61,10 @@
 public abstract class AbstractGeotoolsFilter
 		implements eu.esdihumboldt.hale.common.instance.model.Filter, EntityAwareFilter {
 
+	private static enum SplitType {
+		AND, OR
+	}
+
 	private static final ALogger log = ALoggerFactory.getLogger(AbstractGeotoolsFilter.class);
 
 	private final String filterTerm;
@@ -186,22 +192,18 @@ public List<Optional<EntityDefinition>> getReferencedEntities(EntityDefinition c
 
 	@Override
 	public Optional<eu.esdihumboldt.hale.common.instance.model.Filter> migrateFilter(
-			EntityDefinition context, AlignmentMigration migration, TypeDefinition preferRoot,
-			SimpleLog log) {
-		// TODO pass in target entity or target type, so we can use that
-		// information to determine, if filter conditions actually apply
-		// XXX can preferRoot be used or do we need to differ due to Join?
+			EntityDefinition context, EntityMatch targetMatch, AlignmentMigration migration,
+			TypeDefinition preferRoot, SimpleLog log) {
+		// determine how to split filter
+		boolean join = targetMatch.isMatchPartOfJoin();
+		SplitType splitType = join ? SplitType.AND : SplitType.OR;
 
-		// split filter (AND operands)
-		// TODO split based on different operand based on if there is a join or
-		// not
-		// FIXME what about properties related to a Join -> we want to drop the
-		// AND parts - how do we know the context is a join?
-		List<Filter> andParts = splitAnd(internFilter);
+		// split filter
+		List<Filter> filterParts = splitFilter(internFilter, splitType);
 
 		// migrate each filter part
 		List<Filter> acceptedParts = new ArrayList<>();
-		for (Filter part : andParts) {
+		for (Filter part : filterParts) {
 			EntityReplacementVisitor visitor = new EntityReplacementVisitor(migration,
 					name -> resolveProperty(name, context, log), preferRoot, log);
 			Object extraData = null;
@@ -210,18 +212,19 @@ public Optional<eu.esdihumboldt.hale.common.instance.model.Filter> migrateFilter
 			/*
 			 * Determine if part is relevant. Only accept filter parts that are
 			 * not exclusively updated with other types than `preferRoot`. (This
-			 * is used to handle the different types from a Join individually)
+			 * is used to handle the different types from a Join individually,
+			 * also for properties that are mapped in the same context)
 			 * 
 			 * TODO is usage of preferRoot OK or should we have an additional
 			 * parameter to control this behavior?
 			 * 
-			 * Inform about parts that are dropped)
+			 * Inform about parts that are dropped
 			 */
 			TypeDefinition focusType = preferRoot;
 			String messagePrefix = (focusType == null) ? "" : focusType.getDisplayName() + ": ";
 			if (visitor.isAllMismatches(focusType)) {
 				// drop if there were no successful replacements at all
-				if (andParts.size() == 1) {
+				if (filterParts.size() == 1) {
 					try {
 						log.warn(
 								"{0}The filter \"{1}\" was removed because no matches for the respective properties were found",
@@ -235,12 +238,12 @@ public Optional<eu.esdihumboldt.hale.common.instance.model.Filter> migrateFilter
 				else {
 					try {
 						log.warn(
-								"{0}The filter operand \"{1}\" part of the filter''s AND condition was removed because no matches for the respective properties were found",
-								messagePrefix, toFilterTerm(part));
+								"{0}The filter operand \"{1}\" part of the filter''s {2} condition was removed because no matches for the respective properties were found",
+								messagePrefix, toFilterTerm(part), splitType);
 					} catch (CQLException e) {
 						log.error(
-								"{0}A filter operand part of the filter's AND condition was removed because no matches for the respective properties were found; error converting filter part to string",
-								messagePrefix, e);
+								"{0}A filter operand part of the filter's {1} condition was removed because no matches for the respective properties were found; error converting filter part to string",
+								messagePrefix, splitType, e);
 					}
 				}
 			}
@@ -254,12 +257,13 @@ public Optional<eu.esdihumboldt.hale.common.instance.model.Filter> migrateFilter
 					if (!otherReplacements.isEmpty()) {
 						try {
 							log.warn(
-									"{0}The filter operand \"{1}\" part of the filter''s AND condition contains references related to other types than {2}",
-									messagePrefix, toFilterTerm(part), focusType.getDisplayName());
+									"{0}The filter operand \"{1}\" part of the filter''s {3} condition contains references related to other types than {2}",
+									messagePrefix, toFilterTerm(part), focusType.getDisplayName(),
+									splitType);
 						} catch (CQLException e) {
 							log.error(
-									"{0}A filter operand part of the filter's AND condition contains references related to other types than {1}; error converting filter part to string",
-									messagePrefix, focusType.getDisplayName(), e);
+									"{0}A filter operand part of the filter's {2} condition contains references related to other types than {1}; error converting filter part to string",
+									messagePrefix, focusType.getDisplayName(), splitType, e);
 						}
 					}
 				}
@@ -271,8 +275,19 @@ public Optional<eu.esdihumboldt.hale.common.instance.model.Filter> migrateFilter
 		}
 
 		// combine accepted filter parts
-		Filter combined = CommonFactoryFinder.getFilterFactory2(GeoTools.getDefaultHints())
-				.and(acceptedParts);
+		Filter combined;
+		switch (splitType) {
+		case AND:
+			combined = CommonFactoryFinder.getFilterFactory2(GeoTools.getDefaultHints())
+					.and(acceptedParts);
+			break;
+		case OR:
+			combined = CommonFactoryFinder.getFilterFactory2(GeoTools.getDefaultHints())
+					.or(acceptedParts);
+			break;
+		default:
+			throw new IllegalStateException("Unsupported filter split type " + splitType);
+		}
 
 		try {
 			String filterString = toFilterTerm(combined);
@@ -284,21 +299,25 @@ public Optional<eu.esdihumboldt.hale.common.instance.model.Filter> migrateFilter
 	}
 
 	/**
-	 * Split a filter into separate AND conditions.
+	 * Split a filter into separate AND or OR conditions.
 	 * 
 	 * @param filter the filter to split
+	 * @param splitType on which operation to split
 	 * @return the split filters as list
 	 */
-	private List<Filter> splitAnd(Filter filter) {
+	private List<Filter> splitFilter(Filter filter, SplitType splitType) {
 		List<Filter> result = new ArrayList<>();
 
 		Deque<Filter> toCheck = new LinkedList<>();
 		toCheck.add(filter);
 		while (!toCheck.isEmpty()) {
 			Filter f = toCheck.poll();
-			if (f instanceof And) {
+			if (SplitType.AND.equals(splitType) && f instanceof And) {
 				toCheck.addAll(((And) f).getChildren());
 			}
+			else if (SplitType.OR.equals(splitType) && f instanceof Or) {
+				toCheck.addAll(((Or) f).getChildren());
+			}
 			else {
 				result.add(f);
 			}
diff --git a/common/plugins/eu.esdihumboldt.hale.common.filter/src/eu/esdihumboldt/hale/common/filter/internal/EntityReplacementVisitor.java b/common/plugins/eu.esdihumboldt.hale.common.filter/src/eu/esdihumboldt/hale/common/filter/internal/EntityReplacementVisitor.java
index adff175940..18f99cbd94 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.filter/src/eu/esdihumboldt/hale/common/filter/internal/EntityReplacementVisitor.java
+++ b/common/plugins/eu.esdihumboldt.hale.common.filter/src/eu/esdihumboldt/hale/common/filter/internal/EntityReplacementVisitor.java
@@ -26,6 +26,7 @@
 import org.opengis.filter.expression.PropertyName;
 
 import eu.esdihumboldt.hale.common.align.migrate.AlignmentMigration;
+import eu.esdihumboldt.hale.common.align.migrate.EntityMatch;
 import eu.esdihumboldt.hale.common.align.model.AlignmentUtil;
 import eu.esdihumboldt.hale.common.align.model.EntityDefinition;
 import eu.esdihumboldt.hale.common.core.report.SimpleLog;
@@ -78,8 +79,8 @@ public Object visit(PropertyName expression, Object extraData) {
 		if (resolved.isPresent()) {
 			total++;
 
-			Optional<EntityDefinition> replace = migration.entityReplacement(resolved.get(),
-					preferRoot, log);
+			Optional<EntityDefinition> replace = migration
+					.entityReplacement(resolved.get(), preferRoot, log).map(EntityMatch::getMatch);
 			if (replace.isPresent()) {
 				matched++;
 				replacements.add(replace.get());
@@ -143,7 +144,10 @@ public Set<EntityDefinition> getReplacements() {
 	 * were mismatches or matching entities with a different parent type.
 	 * 
 	 * @param expectedParent the expected parent type
-	 * @return
+	 * @return <code>true</code> if related to the given expected parent type
+	 *         all replacement attempts were mismatches or matching entities
+	 *         with a different parent type, <code>false</code> if there were
+	 *         successful matches with the given parent type
 	 */
 	public boolean isAllMismatches(TypeDefinition expectedParent) {
 		if (expectedParent == null) {
diff --git a/ui/plugins/eu.esdihumboldt.hale.ui/src/eu/esdihumboldt/hale/ui/service/align/migrate/UserMigration.java b/ui/plugins/eu.esdihumboldt.hale.ui/src/eu/esdihumboldt/hale/ui/service/align/migrate/UserMigration.java
index 1f42a8bbcf..f6f84f37b6 100644
--- a/ui/plugins/eu.esdihumboldt.hale.ui/src/eu/esdihumboldt/hale/ui/service/align/migrate/UserMigration.java
+++ b/ui/plugins/eu.esdihumboldt.hale.ui/src/eu/esdihumboldt/hale/ui/service/align/migrate/UserMigration.java
@@ -18,6 +18,7 @@
 import java.util.Optional;
 
 import eu.esdihumboldt.hale.common.align.migrate.AlignmentMigration;
+import eu.esdihumboldt.hale.common.align.migrate.EntityMatch;
 import eu.esdihumboldt.hale.common.align.model.EntityDefinition;
 import eu.esdihumboldt.hale.common.align.model.Property;
 import eu.esdihumboldt.hale.common.align.model.Type;
@@ -54,7 +55,7 @@ public UserMigration(SchemaSpaceID schemaSpace) {
 	}
 
 	@Override
-	public Optional<EntityDefinition> entityReplacement(EntityDefinition entity,
+	public Optional<EntityMatch> entityReplacement(EntityDefinition entity,
 			TypeDefinition preferRoot, SimpleLog log) {
 
 		// use functionality from entity resolver
@@ -62,14 +63,14 @@ public Optional<EntityDefinition> entityReplacement(EntityDefinition entity,
 			EntityDefinition candidate = entity;
 			Type type = UserFallbackEntityResolver.resolveType((TypeEntityDefinition) entity,
 					candidate, schemaSpace);
-			return Optional.ofNullable(type).map(e -> e.getDefinition());
+			return Optional.ofNullable(type).map(e -> EntityMatch.of(e.getDefinition()));
 		}
 		else if (entity instanceof PropertyEntityDefinition) {
 			EntityDefinition candidate = entity;
 			candidate = EntityCandidates.find((PropertyEntityDefinition) entity);
 			Property property = UserFallbackEntityResolver
 					.resolveProperty((PropertyEntityDefinition) entity, candidate, schemaSpace);
-			return Optional.ofNullable(property).map(e -> e.getDefinition());
+			return Optional.ofNullable(property).map(e -> EntityMatch.of(e.getDefinition()));
 		}
 		else {
 			log.error("Unrecognised entity type: " + entity.getClass());

From 90ba5bf56140586716956e056761746a614c55de Mon Sep 17 00:00:00 2001
From: Simon Templer <simon@wetransform.to>
Date: Wed, 13 Mar 2024 11:16:10 +0100
Subject: [PATCH 11/13] WIP fix tests

---
 .../impl/DefaultMergeCellMigratorTest.groovy  | 171 ++++++-
 .../type-filter-props-mapped/A-to-B.halex     |  27 ++
 .../A-to-B.halex.alignment.xml                | 422 ++++++++++++++++++
 .../A-to-B.halex.styles.sld                   |   3 +
 .../type-filter-props-mapped/A.groovy         |  36 ++
 .../type-filter-props-mapped/B-to-C.halex     |  27 ++
 .../B-to-C.halex.alignment.xml                | 190 ++++++++
 .../B-to-C.halex.styles.sld                   |   3 +
 .../type-filter-props-mapped/B.groovy         |  43 ++
 .../type-filter-props-mapped/C.groovy         |  43 ++
 10 files changed, 955 insertions(+), 10 deletions(-)
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/A-to-B.halex
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/A-to-B.halex.alignment.xml
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/A-to-B.halex.styles.sld
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/A.groovy
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/B-to-C.halex
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/B-to-C.halex.alignment.xml
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/B-to-C.halex.styles.sld
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/B.groovy
 create mode 100644 common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/C.groovy

diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/DefaultMergeCellMigratorTest.groovy b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/DefaultMergeCellMigratorTest.groovy
index 4794bd0c6f..9416cd71bd 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/DefaultMergeCellMigratorTest.groovy
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/src/eu/esdihumboldt/hale/common/align/merge/test/impl/DefaultMergeCellMigratorTest.groovy
@@ -446,6 +446,16 @@ class DefaultMergeCellMigratorTest extends AbstractMergeCellMigratorTest {
 		assertEquals(expectedFilter, filter.filterTerm)
 	}
 
+	@CompileStatic(TypeCheckingMode.SKIP)
+	private void typeFilterCheck(Cell migrated, String expectedFilter) {
+		JaxbAlignmentIO.printCell(migrated, System.out)
+
+		// the condition should be present on the source
+		def source = CellUtil.getFirstEntity(migrated.source).definition
+		assertNotNull(source.filter)
+		assertEquals(expectedFilter, source.filter.filterTerm)
+	}
+
 	@CompileStatic(TypeCheckingMode.SKIP)
 	private void filterCheckNull(Cell migrated) {
 		JaxbAlignmentIO.printCell(migrated, System.out)
@@ -493,7 +503,6 @@ class DefaultMergeCellMigratorTest extends AbstractMergeCellMigratorTest {
 		assertEquals(1, migrated.size())
 		// filter is now dropped (because bb is not mapped for B2)
 		filterCheckNull(migrated[0])
-		// filterCheck(migrated[0], "bb = 'test'") // the filter should be retained
 
 		// there should be a message about the condition
 		def messages = getMigrationMessages(migrated[0])
@@ -506,7 +515,28 @@ class DefaultMergeCellMigratorTest extends AbstractMergeCellMigratorTest {
 	}
 
 	@Test
-	@CompileStatic(TypeCheckingMode.SKIP)
+	void testTypeFilter2Present() {
+		def toMigrate = this.class.getResource('/testcases/type-filter-props-mapped/B-to-C.halex')
+		def cellId = 'B2-C2' // Retype
+
+		def matching = this.class.getResource('/testcases/type-filter-props-mapped/A-to-B.halex')
+
+		def migrated = merge(cellId, toMigrate, matching)
+
+		// do checks
+
+		// filter
+		assertEquals(1, migrated.size())
+		filterCheck(migrated[0], "ab = 'test'")
+
+		// there should be a message about the condition
+		def messages = getMigrationMessages(migrated[0])
+		assertTrue(messages.any { msg ->
+			msg.text.toLowerCase().contains('condition')
+		})
+	}
+
+	@Test
 	void testTypeFilter2Property() {
 		def toMigrate = this.class.getResource('/testcases/type-filter/B-to-C.halex')
 		def cellId = 'B2C2a' // Rename
@@ -517,11 +547,34 @@ class DefaultMergeCellMigratorTest extends AbstractMergeCellMigratorTest {
 
 		// do checks
 
-		// type filter should be retained
-		def source = CellUtil.getFirstEntity(migrated[0].source).definition
-		def filter = source.filter
-		assertNotNull(filter)
-		assertEquals("bb = 'test'", filter.filterTerm)
+		// filter is now dropped (because bb is not mapped for B2)
+		assertEquals(1, migrated.size())
+		filterCheckNull(migrated[0])
+
+		// there should be a message about the condition
+		def messages = getMigrationMessages(migrated[0])
+		assertTrue(messages.any { msg ->
+			msg.text.toLowerCase().contains('condition')
+		})
+		assertTrue(messages.any { msg ->
+			msg.text.toLowerCase().contains('removed because no matches')
+		})
+	}
+
+	@Test
+	void testTypeFilter2PropertyPresent() {
+		def toMigrate = this.class.getResource('/testcases/type-filter-props-mapped/B-to-C.halex')
+		def cellId = 'B2C2a' // Rename
+
+		def matching = this.class.getResource('/testcases/type-filter-props-mapped/A-to-B.halex')
+
+		def migrated = merge(cellId, toMigrate, matching)
+
+		// do checks
+
+		// filter
+		assertEquals(1, migrated.size())
+		typeFilterCheck(migrated[0], "ab = 'test'")
 
 		// there should be a message about the condition
 		def messages = getMigrationMessages(migrated[0])
@@ -541,9 +594,34 @@ class DefaultMergeCellMigratorTest extends AbstractMergeCellMigratorTest {
 
 		// do checks
 
+		// filter is now dropped (because bc is not mapped for B3)
+		assertEquals(1, migrated.size())
+		filterCheckNull(migrated[0])
+
+		// there should be a message about the condition
+		def messages = getMigrationMessages(migrated[0])
+		assertTrue(messages.any { msg ->
+			msg.text.toLowerCase().contains('condition')
+		})
+		assertTrue(messages.any { msg ->
+			msg.text.toLowerCase().contains('removed because no matches')
+		})
+	}
+
+	@Test
+	void testTypeFilter3Present() {
+		def toMigrate = this.class.getResource('/testcases/type-filter-props-mapped/B-to-C.halex')
+		def cellId = 'B3-C3' // Merge
+
+		def matching = this.class.getResource('/testcases/type-filter-props-mapped/A-to-B.halex')
+
+		def migrated = merge(cellId, toMigrate, matching)
+
+		// do checks
+
 		// filter
 		assertEquals(1, migrated.size())
-		filterCheck(migrated[0], "bc = 'test'")
+		filterCheck(migrated[0], "ac = 'test'")
 
 		// there should be a message about the condition
 		def messages = getMigrationMessages(migrated[0])
@@ -587,6 +665,46 @@ class DefaultMergeCellMigratorTest extends AbstractMergeCellMigratorTest {
 
 		// do checks
 
+		// filter
+		assertEquals(1, migrated.size())
+		JaxbAlignmentIO.printCell(migrated[0], System.out)
+		// expect filters to be present on A5 source
+		assertNotNull(migrated[0].source)
+		assertEquals(2, migrated[0].source.size())
+		Collection<? extends Entity> source = migrated[0].source.values()
+		((Collection<Entity>) source).each { e ->
+			def filter = e.definition.filter
+			if (e.definition.definition.name.localPart == 'A5') {
+				// filter is now dropped (because ba and bb are not mapped for B5)
+				assertNull(filter)
+			}
+			else {
+				assertNull(filter)
+			}
+		}
+
+		// there should be a message about the conditions being dropped
+		def messages = getMigrationMessages(migrated[0])
+		assertTrue(messages.any { msg ->
+			msg.text.toLowerCase().contains('condition')
+		})
+		assertTrue(messages.any { msg ->
+			msg.text.toLowerCase().contains('removed because no matches')
+		})
+	}
+
+	@CompileStatic(TypeCheckingMode.SKIP)
+	@Test
+	void testTypeFilter5Present() {
+		def toMigrate = this.class.getResource('/testcases/type-filter-props-mapped/B-to-C.halex')
+		def cellId = 'Join' // Join
+
+		def matching = this.class.getResource('/testcases/type-filter-props-mapped/A-to-B.halex')
+
+		def migrated = merge(cellId, toMigrate, matching)
+
+		// do checks
+
 		// filter
 		assertEquals(1, migrated.size())
 		JaxbAlignmentIO.printCell(migrated[0], System.out)
@@ -598,8 +716,8 @@ class DefaultMergeCellMigratorTest extends AbstractMergeCellMigratorTest {
 			def filter = e.definition.filter
 			if (e.definition.definition.name.localPart == 'A5') {
 				assertNotNull(filter)
-				// assertEquals('ba = \'test\' AND NOT (bb = \'test\')', filter.filterTerm)
-				assertEquals('ba = \'test\' and bb <> \'test\'', filter.filterTerm)
+				assertEquals('(aa = \'test\' AND NOT (ab = \'test\'))', filter.filterTerm)
+				// assertEquals('aa = \'test\' and ab <> \'test\'', filter.filterTerm)
 			}
 			else {
 				assertNull(filter)
@@ -624,6 +742,39 @@ class DefaultMergeCellMigratorTest extends AbstractMergeCellMigratorTest {
 
 		// do checks
 
+		// filter
+		assertEquals(1, migrated.size())
+		JaxbAlignmentIO.printCell(migrated[0], System.out)
+		// expect filters to be present on both sources
+		assertNotNull(migrated[0].source)
+		assertEquals(2, migrated[0].source.size())
+		Collection<? extends Entity> source = migrated[0].source.values()
+		((Collection<Entity>) source).each { e ->
+			def filter = e.definition.filter
+			assertNull(filter)
+		}
+
+		// there should be a message about the conditions being dropped
+		def messages = getMigrationMessages(migrated[0])
+		assertTrue(messages.any { msg ->
+			msg.text.toLowerCase().contains('condition')
+		})
+		assertTrue(messages.any { msg ->
+			msg.text.toLowerCase().contains('removed because no matches')
+		})
+	}
+
+	@Test
+	void testTypeFilter6Present() {
+		def toMigrate = this.class.getResource('/testcases/type-filter-props-mapped/B-to-C.halex')
+		def cellId = 'GJoin' // Groovy Join
+
+		def matching = this.class.getResource('/testcases/type-filter-props-mapped/A-to-B.halex')
+
+		def migrated = merge(cellId, toMigrate, matching)
+
+		// do checks
+
 		// filter
 		assertEquals(1, migrated.size())
 		JaxbAlignmentIO.printCell(migrated[0], System.out)
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/A-to-B.halex b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/A-to-B.halex
new file mode 100644
index 0000000000..600daa4e90
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/A-to-B.halex
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<hale-project version="5.2.0.qualifier">
+    <name>A to B (matching)</name>
+    <author>Simon Templer</author>
+    <created>2018-01-10T16:17:32.757+01:00</created>
+    <modified>2024-03-13T10:56:32.007+01:00</modified>
+    <save-config action-id="project.save" provider-id="eu.esdihumboldt.hale.io.project.hale25.xml.writer">
+        <setting name="charset">UTF-8</setting>
+        <setting name="projectFiles.separate">false</setting>
+        <setting name="contentType">eu.esdihumboldt.hale.io.project.hale25.xml</setting>
+        <setting name="target">file:/home/simon/repos/hale/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/A-to-B.halex</setting>
+    </save-config>
+    <resource action-id="eu.esdihumboldt.hale.io.schema.read.source" provider-id="eu.esdihumboldt.hale.io.schemabuilder">
+        <setting name="charset">UTF-8</setting>
+        <setting name="resourceId">6534b899-7ef3-4ead-8e4f-9af3af16f030</setting>
+        <setting name="source">A.groovy</setting>
+        <setting name="contentType">eu.esdihumboldt.hale.io.schemabuilder</setting>
+    </resource>
+    <resource action-id="eu.esdihumboldt.hale.io.schema.read.target" provider-id="eu.esdihumboldt.hale.io.schemabuilder">
+        <setting name="charset">UTF-8</setting>
+        <setting name="resourceId">67afcd12-5dd4-4adc-ab52-042786a4b3af</setting>
+        <setting name="source">B.groovy</setting>
+        <setting name="contentType">eu.esdihumboldt.hale.io.schemabuilder</setting>
+    </resource>
+    <file name="alignment.xml" location="A-to-B.halex.alignment.xml"/>
+    <file name="styles.sld" location="A-to-B.halex.styles.sld"/>
+</hale-project>
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/A-to-B.halex.alignment.xml b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/A-to-B.halex.alignment.xml
new file mode 100644
index 0000000000..386c1e1f5b
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/A-to-B.halex.alignment.xml
@@ -0,0 +1,422 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<alignment xmlns="http://www.esdi-humboldt.eu/hale/alignment">
+    <cell relation="eu.esdihumboldt.hale.align.retype" id="Cf7686d9a-bb20-4379-af80-0e1c97973d12" priority="normal">
+        <source>
+            <class>
+                <type name="A1" ns="A"/>
+            </class>
+        </source>
+        <target>
+            <class>
+                <type name="B1" ns="B"/>
+            </class>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="Cfbb81893-6860-4c32-9819-11e3036a212e" priority="normal">
+        <source>
+            <property>
+                <type name="A1" ns="A"/>
+                <child name="aa" ns="A"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="B1" ns="B"/>
+                <child name="ba" ns="B"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+        <documentation>Notes from match</documentation>
+    </cell>
+    <cell relation="eu.esdihumboldt.cst.functions.groovy" id="Caed2942c-b8ee-40c8-ac31-e58a433d5e2a" priority="normal">
+        <source name="var">
+            <property>
+                <type name="A1" ns="A"/>
+                <child name="ab" ns="A"/>
+            </property>
+        </source>
+        <target name="result">
+            <property>
+                <type name="B1" ns="B"/>
+                <child name="bb" ns="B"/>
+            </property>
+        </target>
+        <complexParameter name="script">
+            <core:text xmlns:core="http://www.esdi-humboldt.eu/hale/core" xml:space="preserve">ab + 'ist doof'</core:text>
+        </complexParameter>
+        <parameter value="false" name="variablesAsInstances"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="C4ee1954f-e373-4dde-b54e-9e13b5865984" priority="normal">
+        <source>
+            <property>
+                <type name="A1" ns="A"/>
+                <child name="ac" ns="A"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="B1" ns="B"/>
+                <child name="bc" ns="B"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="C65d966ef-02b9-4867-924b-699bda8cd50f" priority="normal">
+        <source>
+            <property>
+                <type name="A1" ns="A"/>
+                <child name="ad" ns="A">
+                    <condition lang="CQL">value = 'green lantern'</condition>
+                </child>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="B1" ns="B"/>
+                <child name="bd" ns="B"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.cst.functions.groovy.retype" id="C4b534860-3b4f-4a21-a629-eaae9f5723b8" priority="normal">
+        <source>
+            <class>
+                <type name="A2" ns="A"/>
+            </class>
+        </source>
+        <target>
+            <class>
+                <type name="B2" ns="B"/>
+            </class>
+        </target>
+        <complexParameter name="script">
+            <core:text xmlns:core="http://www.esdi-humboldt.eu/hale/core" xml:space="preserve">
+_target {
+}
+</core:text>
+        </complexParameter>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="C5e180246-7d8f-487b-bb16-98d113c363f3" priority="normal">
+        <source>
+            <property>
+                <type name="A2" ns="A"/>
+                <child name="aa" ns="A"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="B2" ns="B"/>
+                <child name="ba" ns="B"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="Ca9b31be4-01ca-4d2d-b8d1-170320ff487e" priority="normal">
+        <source>
+            <property>
+                <type name="A2" ns="A"/>
+                <child name="ab" ns="A"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="B2" ns="B"/>
+                <child name="bb" ns="B"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="Cfea966bc-0b98-45fd-aea1-b48dc86e32df" priority="normal">
+        <source>
+            <property>
+                <type name="A2" ns="A"/>
+                <child name="ac" ns="A"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="B2" ns="B"/>
+                <child name="bc" ns="B"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="Ce4961fc4-f0c4-4d4f-be47-11cd2bf564a6" priority="normal">
+        <source>
+            <property>
+                <type name="A2" ns="A"/>
+                <child name="ad" ns="A"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="B2" ns="B"/>
+                <child name="bd" ns="B"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.cst.functions.groovy.merge" id="C9d1e6f1f-d6e7-4a3f-b1e9-10e8ebf848db" priority="normal">
+        <source>
+            <class>
+                <type name="A3" ns="A"/>
+            </class>
+        </source>
+        <target>
+            <class>
+                <type name="B3" ns="B"/>
+            </class>
+        </target>
+        <parameter value="{A}ac" name="additional_property"/>
+        <parameter value="false" name="auto_detect"/>
+        <parameter value="{A}aa" name="property"/>
+        <complexParameter name="script">
+            <core:text xmlns:core="http://www.esdi-humboldt.eu/hale/core" xml:space="preserve">
+_target {
+}
+</core:text>
+        </complexParameter>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="C15932119-ea58-41ab-9710-c1c35b89659a" priority="normal">
+        <source>
+            <property>
+                <type name="A3" ns="A"/>
+                <child name="aa" ns="A"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="B3" ns="B"/>
+                <child name="ba" ns="B"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="C24e030f2-3d15-48f6-8d95-adf44d05fcca" priority="normal">
+        <source>
+            <property>
+                <type name="A3" ns="A"/>
+                <child name="ab" ns="A"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="B3" ns="B"/>
+                <child name="bb" ns="B"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="Cad8e6c27-7fc0-47dc-81ea-d93aaa21d11a" priority="normal">
+        <source>
+            <property>
+                <type name="A3" ns="A"/>
+                <child name="ac" ns="A"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="B3" ns="B"/>
+                <child name="bc" ns="B"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="C0c3c5c99-5f32-4181-a945-1a0e126413b2" priority="normal">
+        <source>
+            <property>
+                <type name="A3" ns="A"/>
+                <child name="ad" ns="A"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="B3" ns="B"/>
+                <child name="bd" ns="B"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.cst.functions.groovy.create" id="C152e75fb-dfee-4fc8-be6b-bbe815be4efa" priority="normal">
+        <target>
+            <class>
+                <type name="B4" ns="B"/>
+            </class>
+        </target>
+        <complexParameter name="script">
+            <core:text xmlns:core="http://www.esdi-humboldt.eu/hale/core" xml:space="preserve">
+_target {
+}
+</core:text>
+        </complexParameter>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.retype" id="C0b890e00-fea3-4172-a3ba-b36873e98b64" priority="normal">
+        <source>
+            <class>
+                <type name="A5" ns="A"/>
+            </class>
+        </source>
+        <target>
+            <class>
+                <type name="B5" ns="B"/>
+            </class>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="C870abb59-cc8f-4578-bb17-98b71478e0aa" priority="normal">
+        <source>
+            <property>
+                <type name="A5" ns="A"/>
+                <child name="aa" ns="A"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="B5" ns="B"/>
+                <child name="ba" ns="B"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="C743ed0c2-3aa8-4161-aaa1-7a8c10b9a7a5" priority="normal">
+        <source>
+            <property>
+                <type name="A5" ns="A"/>
+                <child name="ab" ns="A"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="B5" ns="B"/>
+                <child name="bb" ns="B"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="Cfa4549d1-b263-48dc-bc36-abf4bd0bf9f8" priority="normal">
+        <source>
+            <property>
+                <type name="A5" ns="A"/>
+                <child name="ac" ns="A"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="B5" ns="B"/>
+                <child name="bc" ns="B"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="Cbfd0ace3-6c89-47c0-9d55-ac2560a28d34" priority="normal">
+        <source>
+            <property>
+                <type name="A5" ns="A"/>
+                <child name="ad" ns="A"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="B5" ns="B"/>
+                <child name="bd" ns="B"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.retype" id="C673337ac-a66b-4918-b924-d1518314d09e" priority="normal">
+        <source>
+            <class>
+                <type name="A6" ns="A"/>
+            </class>
+        </source>
+        <target>
+            <class>
+                <type name="B6" ns="B"/>
+            </class>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="C6502f0c5-95d6-4aa4-b522-2d3f87975e75" priority="normal">
+        <source>
+            <property>
+                <type name="A6" ns="A"/>
+                <child name="aa" ns="A"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="B6" ns="B"/>
+                <child name="ba" ns="B"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="C724efad0-65ab-4b17-9106-9929f11f3c8c" priority="normal">
+        <source>
+            <property>
+                <type name="A6" ns="A"/>
+                <child name="ab" ns="A"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="B6" ns="B"/>
+                <child name="bb" ns="B"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="C33296870-ac93-4224-95ea-6c7b9fd27fb6" priority="normal">
+        <source>
+            <property>
+                <type name="A6" ns="A"/>
+                <child name="ac" ns="A"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="B6" ns="B"/>
+                <child name="bc" ns="B"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="C1fb210bb-4d69-4119-a382-48414a7dd106" priority="normal">
+        <source>
+            <property>
+                <type name="A6" ns="A"/>
+                <child name="ad" ns="A"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="B6" ns="B"/>
+                <child name="bd" ns="B"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+</alignment>
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/A-to-B.halex.styles.sld b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/A-to-B.halex.styles.sld
new file mode 100644
index 0000000000..ad34f540f6
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/A-to-B.halex.styles.sld
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?><sld:UserStyle xmlns="http://www.opengis.net/sld" xmlns:sld="http://www.opengis.net/sld" xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc">
+  <sld:Name>Default Styler</sld:Name>
+</sld:UserStyle>
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/A.groovy b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/A.groovy
new file mode 100644
index 0000000000..ed44fadde7
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/A.groovy
@@ -0,0 +1,36 @@
+schema('A') {
+	A1 {
+		aa()
+		ab()
+		ac()
+		ad()
+	}
+	
+	A2 {
+		aa()
+		ab()
+		ac()
+		ad()
+	}
+	
+	A3 {
+		aa()
+		ab()
+		ac()
+		ad()
+	}
+	
+	A5 {
+		aa()
+		ab()
+		ac()
+		ad()
+	}
+	
+	A6 {
+		aa()
+		ab()
+		ac()
+		ad()
+	}
+}
\ No newline at end of file
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/B-to-C.halex b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/B-to-C.halex
new file mode 100644
index 0000000000..b64bd20f0f
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/B-to-C.halex
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<hale-project version="5.2.0.qualifier">
+    <name>B to C (to be migrated)</name>
+    <author>Simon Templer</author>
+    <created>2018-01-10T16:26:35.275+01:00</created>
+    <modified>2024-03-13T10:57:03.028+01:00</modified>
+    <save-config action-id="project.save" provider-id="eu.esdihumboldt.hale.io.project.hale25.xml.writer">
+        <setting name="charset">UTF-8</setting>
+        <setting name="projectFiles.separate">false</setting>
+        <setting name="contentType">eu.esdihumboldt.hale.io.project.hale25.xml</setting>
+        <setting name="target">file:/home/simon/repos/hale/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/B-to-C.halex</setting>
+    </save-config>
+    <resource action-id="eu.esdihumboldt.hale.io.schema.read.source" provider-id="eu.esdihumboldt.hale.io.schemabuilder">
+        <setting name="charset">UTF-8</setting>
+        <setting name="resourceId">bafa16f0-75ed-44e3-a0ed-10e5410d044d</setting>
+        <setting name="source">B.groovy</setting>
+        <setting name="contentType">eu.esdihumboldt.hale.io.schemabuilder</setting>
+    </resource>
+    <resource action-id="eu.esdihumboldt.hale.io.schema.read.target" provider-id="eu.esdihumboldt.hale.io.schemabuilder">
+        <setting name="charset">UTF-8</setting>
+        <setting name="resourceId">e13b67b2-8b9a-45eb-b812-c8c675028b48</setting>
+        <setting name="source">C.groovy</setting>
+        <setting name="contentType">eu.esdihumboldt.hale.io.schemabuilder</setting>
+    </resource>
+    <file name="alignment.xml" location="B-to-C.halex.alignment.xml"/>
+    <file name="styles.sld" location="B-to-C.halex.styles.sld"/>
+</hale-project>
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/B-to-C.halex.alignment.xml b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/B-to-C.halex.alignment.xml
new file mode 100644
index 0000000000..75a706d161
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/B-to-C.halex.alignment.xml
@@ -0,0 +1,190 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<alignment xmlns="http://www.esdi-humboldt.eu/hale/alignment">
+    <cell relation="eu.esdihumboldt.cst.functions.groovy.retype" id="B1-C1" priority="normal">
+        <source>
+            <class>
+                <type name="B1" ns="B">
+                    <condition lang="CQL">ba = 'test'</condition>
+                </type>
+            </class>
+        </source>
+        <target>
+            <class>
+                <type name="C1" ns="C"/>
+            </class>
+        </target>
+        <complexParameter name="script">
+            <core:text xmlns:core="http://www.esdi-humboldt.eu/hale/core" xml:space="preserve">
+_target {
+}
+</core:text>
+        </complexParameter>
+        <documentation>B1 to C1</documentation>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.retype" id="B2-C2" priority="normal">
+        <source>
+            <class>
+                <type name="B2" ns="B">
+                    <condition lang="CQL">bb = 'test'</condition>
+                </type>
+            </class>
+        </source>
+        <target>
+            <class>
+                <type name="C2" ns="C"/>
+            </class>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+        <documentation>B2 to C2</documentation>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.rename" id="B2C2a" priority="normal">
+        <source>
+            <property>
+                <type name="B2" ns="B">
+                    <condition lang="CQL">bb = 'test'</condition>
+                </type>
+                <child name="ba" ns="B"/>
+            </property>
+        </source>
+        <target>
+            <property>
+                <type name="C2" ns="C"/>
+                <child name="ca" ns="C"/>
+            </property>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.merge" id="B3-C3" priority="normal">
+        <source>
+            <class>
+                <type name="B3" ns="B">
+                    <condition lang="CQL">bc = 'test'</condition>
+                </type>
+            </class>
+        </source>
+        <target>
+            <class>
+                <type name="C3" ns="C"/>
+            </class>
+        </target>
+        <parameter value="false" name="auto_detect"/>
+        <parameter value="{B}bc" name="property"/>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.retype" id="B4-C4" priority="normal">
+        <source>
+            <class>
+                <type name="B4" ns="B">
+                    <condition lang="CQL">bd = 'test'</condition>
+                </type>
+            </class>
+        </source>
+        <target>
+            <class>
+                <type name="C4" ns="C"/>
+            </class>
+        </target>
+        <parameter value="false" name="ignoreNamespaces"/>
+        <parameter value="false" name="structuralRename"/>
+        <documentation>B4 to C4</documentation>
+    </cell>
+    <cell relation="eu.esdihumboldt.cst.functions.groovy.join" id="GJoin" priority="normal">
+        <source name="types">
+            <class>
+                <type name="B5" ns="B">
+                    <condition lang="CQL">ba = 'test' and bb &lt;&gt; 'test'</condition>
+                </type>
+            </class>
+        </source>
+        <source name="types">
+            <class>
+                <type name="B6" ns="B">
+                    <condition lang="CQL">bc = 'test' and bd &lt;&gt; 'test'</condition>
+                </type>
+            </class>
+        </source>
+        <target>
+            <class>
+                <type name="C5" ns="C"/>
+            </class>
+        </target>
+        <complexParameter name="join">
+            <jp:join-parameter xmlns:jp="http://www.esdi-humboldt.eu/hale/join">
+                <class>
+                    <type name="B5" ns="B">
+                        <condition lang="CQL">ba = 'test' and bb &lt;&gt; 'test'</condition>
+                    </type>
+                </class>
+                <class>
+                    <type name="B6" ns="B">
+                        <condition lang="CQL">bc = 'test' and bd &lt;&gt; 'test'</condition>
+                    </type>
+                </class>
+                <jp:condition>
+                    <property>
+                        <type name="B5" ns="B">
+                            <condition lang="CQL">ba = 'test' and bb &lt;&gt; 'test'</condition>
+                        </type>
+                        <child name="ba" ns="B"/>
+                    </property>
+                    <property>
+                        <type name="B6" ns="B">
+                            <condition lang="CQL">bc = 'test' and bd &lt;&gt; 'test'</condition>
+                        </type>
+                        <child name="ba" ns="B"/>
+                    </property>
+                </jp:condition>
+            </jp:join-parameter>
+        </complexParameter>
+        <complexParameter name="script">
+            <core:text xmlns:core="http://www.esdi-humboldt.eu/hale/core" xml:space="preserve">
+_target {
+}
+</core:text>
+        </complexParameter>
+    </cell>
+    <cell relation="eu.esdihumboldt.hale.align.join" id="Join" priority="normal">
+        <source name="types">
+            <class>
+                <type name="B5" ns="B">
+                    <condition lang="CQL">ba = 'test' and bb &lt;&gt; 'test'</condition>
+                </type>
+            </class>
+        </source>
+        <source name="types">
+            <class>
+                <type name="B6" ns="B"/>
+            </class>
+        </source>
+        <target>
+            <class>
+                <type name="C6" ns="C"/>
+            </class>
+        </target>
+        <complexParameter name="join">
+            <jp:join-parameter xmlns:jp="http://www.esdi-humboldt.eu/hale/join">
+                <class>
+                    <type name="B5" ns="B">
+                        <condition lang="CQL">ba = 'test' and bb &lt;&gt; 'test'</condition>
+                    </type>
+                </class>
+                <class>
+                    <type name="B6" ns="B"/>
+                </class>
+                <jp:condition>
+                    <property>
+                        <type name="B5" ns="B">
+                            <condition lang="CQL">ba = 'test' and bb &lt;&gt; 'test'</condition>
+                        </type>
+                        <child name="bb" ns="B"/>
+                    </property>
+                    <property>
+                        <type name="B6" ns="B"/>
+                        <child name="bb" ns="B"/>
+                    </property>
+                </jp:condition>
+            </jp:join-parameter>
+        </complexParameter>
+    </cell>
+</alignment>
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/B-to-C.halex.styles.sld b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/B-to-C.halex.styles.sld
new file mode 100644
index 0000000000..ad34f540f6
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/B-to-C.halex.styles.sld
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?><sld:UserStyle xmlns="http://www.opengis.net/sld" xmlns:sld="http://www.opengis.net/sld" xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc">
+  <sld:Name>Default Styler</sld:Name>
+</sld:UserStyle>
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/B.groovy b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/B.groovy
new file mode 100644
index 0000000000..eac2f7358d
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/B.groovy
@@ -0,0 +1,43 @@
+schema('B') {
+	B1 {
+		ba()
+		bb()
+		bc()
+		bd()
+	}
+	
+	B2 {
+		ba()
+		bb()
+		bc()
+		bd()
+	}
+	
+	B3 {
+		ba()
+		bb()
+		bc()
+		bd()
+	}
+	
+	B4 {
+		ba()
+		bb()
+		bc()
+		bd()
+	}
+	
+	B5 {
+		ba()
+		bb()
+		bc()
+		bd()
+	}
+	
+	B6 {
+		ba()
+		bb()
+		bc()
+		bd()
+	}
+}
\ No newline at end of file
diff --git a/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/C.groovy b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/C.groovy
new file mode 100644
index 0000000000..529a699424
--- /dev/null
+++ b/common/plugins/eu.esdihumboldt.hale.common.align.merge.test/testcases/type-filter-props-mapped/C.groovy
@@ -0,0 +1,43 @@
+schema('C') {
+	C1 {
+		ca()
+		cb()
+		cc()
+		cd()
+	}
+	
+	C2 {
+		ca()
+		cb()
+		cc()
+		cd()
+	}
+	
+	C3 {
+		ca()
+		cb()
+		cc()
+		cd()
+	}
+	
+	C4 {
+		ca()
+		cb()
+		cc()
+		cd()
+	}
+	
+	C5 {
+		ca()
+		cb()
+		cc()
+		cd()
+	}
+	
+	C6 {
+		ca()
+		cb()
+		cc()
+		cd()
+	}
+}
\ No newline at end of file

From 0e0ccd88579234fd74951350b5d4473514858757 Mon Sep 17 00:00:00 2001
From: Simon Templer <simon@wetransform.to>
Date: Wed, 13 Mar 2024 11:52:48 +0100
Subject: [PATCH 12/13] fixup! build(deps): update equinox-test update site

---
 platform/hale-platform.target | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/platform/hale-platform.target b/platform/hale-platform.target
index 287d3591f2..ef157877aa 100644
--- a/platform/hale-platform.target
+++ b/platform/hale-platform.target
@@ -52,8 +52,8 @@
 		<unit id="eu.esdihumboldt.hale.platform.feature.group" version="5.0.0.i20240220"/>
 	</location>
 	<location includeAllPlatforms="false" includeConfigurePhase="false" includeMode="slicer" includeSource="true" type="InstallableUnit">
-		<repository location="http://build-artifacts.wetransform.to/p2/equinox-test/1.2.0.20240312"/>
-		<unit id="de.fhg.igd.equinox.test.feature.feature.group" version="1.2.0.202403121501"/>
+		<repository location="http://build-artifacts.wetransform.to/p2/equinox-test/96b3ede3cee11a98a1290edc330e421bb6ae2c86"/>
+		<unit id="de.fhg.igd.equinox.test.feature.feature.group" version="1.2.0.202403131049"/>
 	</location>
 </locations>
 </target>
\ No newline at end of file

From ac5662e7e6e9ed2b1f3b436b40d1a05e45e04d97 Mon Sep 17 00:00:00 2001
From: Simon Templer <simon@wetransform.to>
Date: Wed, 13 Mar 2024 15:36:48 +0100
Subject: [PATCH 13/13] fix: allow filtering for joins

...when selecting a source type in the alignment view.

Before no results would be shown because the internal check expects all
types to match while in the view only one can be specified.
---
 .../align/model/impl/DefaultAlignment.java    | 29 +++++++++++++++++--
 1 file changed, 26 insertions(+), 3 deletions(-)

diff --git a/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/model/impl/DefaultAlignment.java b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/model/impl/DefaultAlignment.java
index 1c04a450be..d41590778f 100644
--- a/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/model/impl/DefaultAlignment.java
+++ b/common/plugins/eu.esdihumboldt.hale.common.align/src/eu/esdihumboldt/hale/common/align/model/impl/DefaultAlignment.java
@@ -419,9 +419,32 @@ public Collection<? extends Cell> getTypeCells(Cell queryCell) {
 					.getDefinition().getType();
 			if (target == null || DefinitionUtil.isSuperType(target, typeCellTarget)) {
 				// target matches
-				if (sources.isEmpty() || matchesSources(typeCell.getSource(), sources)) {
-					// source matches, too
-					result.add(typeCell);
+
+				if (sources.size() == 1) {
+					/*
+					 * Special case handling when there is exactly one type,
+					 * that in this case also allows to find a Join that only
+					 * uses one of the types.
+					 * 
+					 * Unclear if this is intended, but the else case does not
+					 * yield any results in such cases, which could be due to
+					 * the implementation of `matchesSources` in that case being
+					 * focused on property relations.
+					 */
+					TypeEntityDefinition type = sources.iterator().next();
+					if (sources.isEmpty() || matchesSources(type,
+							typeCell.getSource().values().stream()
+									.map(e -> AlignmentUtil.getTypeEntity(e.getDefinition()))
+									.toList())) {
+						// source matches, too
+						result.add(typeCell);
+					}
+				}
+				else {
+					if (sources.isEmpty() || matchesSources(typeCell.getSource(), sources)) {
+						// source matches, too
+						result.add(typeCell);
+					}
 				}
 			}
 		}