diff --git a/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/main/java/org/xwiki/model/internal/reference/AbstractReferenceEntityReferenceResolver.java b/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/main/java/org/xwiki/model/internal/reference/AbstractReferenceEntityReferenceResolver.java index c62aa22c9052..1e7d5f235926 100644 --- a/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/main/java/org/xwiki/model/internal/reference/AbstractReferenceEntityReferenceResolver.java +++ b/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/main/java/org/xwiki/model/internal/reference/AbstractReferenceEntityReferenceResolver.java @@ -90,15 +90,16 @@ private EntityReference normalizeReference(EntityReference referenceToResolve, O EntityReference reference = normalizedReference; while (reference != null) { List types = reference.getType().getAllowedParents(); - if (reference.getParent() != null && !types.isEmpty() && !types.contains(reference.getParent().getType())) { + if (reference.getParent() != null + && isParentTypeAndAllowedTypeNotMatching(types, reference.getParentType())) { // The parent reference isn't the allowed parent: insert an allowed reference EntityReference newReference = resolveDefaultReference(types.get(0), parameters).appendParent(reference.getParent()); normalizedReference = normalizedReference.replaceParent(reference.getParent(), newReference); reference = newReference; - } else if (reference.getParent() == null && !types.isEmpty()) { + } else if (reference.getParent() == null && reference.getParentType() != null) { // The top reference isn't the allowed top level reference, add a parent reference - EntityReference newReference = resolveDefaultReference(types.get(0), parameters); + EntityReference newReference = resolveDefaultReference(reference.getParentType(), parameters); normalizedReference = normalizedReference.appendParent(newReference); reference = newReference; } else if (reference.getParent() != null && types.isEmpty()) { @@ -112,4 +113,9 @@ private EntityReference normalizeReference(EntityReference referenceToResolve, O return normalizedReference; } + + private boolean isParentTypeAndAllowedTypeNotMatching(List allowedTypes, EntityType parentType) + { + return !allowedTypes.isEmpty() && parentType != null && !allowedTypes.contains(parentType); + } } diff --git a/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/main/java/org/xwiki/model/reference/EntityReference.java b/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/main/java/org/xwiki/model/reference/EntityReference.java index 31e77b80c70c..f38eae6cf382 100644 --- a/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/main/java/org/xwiki/model/reference/EntityReference.java +++ b/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/main/java/org/xwiki/model/reference/EntityReference.java @@ -43,6 +43,13 @@ */ public class EntityReference implements Serializable, Cloneable, Comparable { + /** + * See {@link #getParentType()}. + * @since 17.0.0RC1 + */ + @Unstable + public static final String PARENT_TYPE_PARAMETER = "parentType"; + /** * Used to provide a nice and readable pretty name for the {@link #toString()} method. */ @@ -118,7 +125,6 @@ protected EntityReference(EntityReference reference, EntityReference oldReferenc setName(reference.name); setType(reference.type); - setParameters(reference.parameters); if (reference.parent == null) { if (oldReference == null) { setParent(newReference); @@ -131,6 +137,7 @@ protected EntityReference(EntityReference reference, EntityReference oldReferenc } else { setParent(new EntityReference(reference.parent, oldReference, newReference)); } + setParameters(reference.parameters); } /** @@ -191,7 +198,7 @@ public EntityReference(String name, EntityType type, EntityReference parent, Map } /** - * Clone an EntityReference, but use the specified paramaters. + * Clone an EntityReference, but use the specified parameters. * * @param reference the reference to clone * @param parameters parameters for this reference, may be null @@ -298,15 +305,37 @@ protected void setParameter(String name, Serializable value) if (this.parameters == null) { this.parameters = new TreeMap<>(); } - this.parameters.put(name, value); + if (PARENT_TYPE_PARAMETER.equals(name)) { + setParentTypeParameter(value); + } else { + this.parameters.put(name, value); + } } else if (parameters != null) { this.parameters.remove(name); - if (this.parameters.size() == 0) { + if (this.parameters.isEmpty()) { this.parameters = null; } } } + private void setParentTypeParameter(Serializable value) + { + if (value != null && getParent() == null) { + EntityType parentType; + if (value instanceof EntityType entityType) { + parentType = entityType; + } else { + parentType = EntityType.valueOf(value.toString()); + } + if (getType().getAllowedParents().contains(parentType)) { + this.parameters.put(PARENT_TYPE_PARAMETER, parentType); + } else { + throw new IllegalArgumentException( + "The parent type [" + parentType + "] does not belong to the allowed parents"); + } + } + } + /** * Get the value of a parameter. Return null if the parameter is not set. This method is final so there is no way to * override the map, and the private field in all other methods of this implementation (faster). @@ -681,6 +710,31 @@ public boolean equalsNonRecursive(EntityReference otherReference) && (parameters == null ? otherReference.parameters == null : parameters.equals(otherReference.parameters)); } + /** + * The parent type information is used by resolvers to identify which part of the base reference should be kept. + * If the entity reference has a parent (see {@link #getParent()}) then this type should always be the type of + * the parent. Now if the entity reference doesn't have the parent this value can be given by the + * {@link #PARENT_TYPE_PARAMETER} parameter (see {@link #getParameter(String)}), and if none is given it will + * fall back on first allowed parents (see {@link EntityType#getAllowedParents()} of current type returned by + * {@link #getType()}. + * @return the type of the parent to be used for computing the proper base reference in resolvers. + * @since 17.0.0RC1 + */ + @Unstable + public EntityType getParentType() + { + EntityType result; + if (getParent() == null) { + result = getParameter(PARENT_TYPE_PARAMETER); + if (result == null && !getType().getAllowedParents().isEmpty()) { + result = getType().getAllowedParents().get(0); + } + } else { + result = getParent().getType(); + } + return result; + } + @Override public int hashCode() { diff --git a/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/internal/reference/DefaultReferenceEntityReferenceResolverTest.java b/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/internal/reference/DefaultReferenceEntityReferenceResolverTest.java index 9717c5dbdd91..9181fdf985be 100644 --- a/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/internal/reference/DefaultReferenceEntityReferenceResolverTest.java +++ b/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/internal/reference/DefaultReferenceEntityReferenceResolverTest.java @@ -19,7 +19,8 @@ */ package org.xwiki.model.internal.reference; -import java.util.Arrays; +import java.util.List; +import java.util.Map; import org.junit.jupiter.api.Test; import org.xwiki.model.EntityType; @@ -303,7 +304,7 @@ public void resolveDocumentReferenceWhenTypeIsPage() new EntityReference("page1", EntityType.PAGE, new EntityReference("page2", EntityType.PAGE)), EntityType.DOCUMENT); - assertEquals(new DocumentReference(DEFAULT_WIKI, Arrays.asList("page2", "page1"), DEFAULT_DOCUMENT), reference); + assertEquals(new DocumentReference(DEFAULT_WIKI, List.of("page2", "page1"), DEFAULT_DOCUMENT), reference); } @Test @@ -312,12 +313,25 @@ public void resolveSpaceReferenceWhenTypeIsPage() EntityReference reference = this.resolver.resolve(new EntityReference("page", EntityType.PAGE), EntityType.SPACE); - assertEquals(new SpaceReference(DEFAULT_WIKI, Arrays.asList("page")), reference); + assertEquals(new SpaceReference(DEFAULT_WIKI, List.of("page")), reference); reference = this.resolver.resolve( new EntityReference("page1", EntityType.PAGE, new EntityReference("page2", EntityType.PAGE)), EntityType.SPACE); - assertEquals(new SpaceReference(DEFAULT_WIKI, Arrays.asList("page2", "page1")), reference); + assertEquals(new SpaceReference(DEFAULT_WIKI, List.of("page2", "page1")), reference); + } + + @Test + void resolveRelativeEntityReference() + { + // When the space reference has a parent type parameter of tye space, then it's resolved using the base + // reference space has parent of it. + EntityReference relativeSpaceReference = new EntityReference("Space", EntityType.SPACE, + Map.of(EntityReference.PARENT_TYPE_PARAMETER, EntityType.SPACE)); + + EntityReference reference = this.resolver.resolve(relativeSpaceReference, EntityType.SPACE); + SpaceReference spaceReference = new SpaceReference(DEFAULT_WIKI, List.of(DEFAULT_SPACE, "Space")); + assertEquals(spaceReference, reference); } } diff --git a/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/internal/reference/RelativeStringEntityReferenceResolverTest.java b/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/internal/reference/RelativeStringEntityReferenceResolverTest.java index 92c918597397..98b326229b9c 100644 --- a/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/internal/reference/RelativeStringEntityReferenceResolverTest.java +++ b/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/internal/reference/RelativeStringEntityReferenceResolverTest.java @@ -39,13 +39,13 @@ @ComponentList({ DefaultSymbolScheme.class }) -public class RelativeStringEntityReferenceResolverTest +class RelativeStringEntityReferenceResolverTest { @InjectMockComponents private RelativeStringEntityReferenceResolver resolver; @Test - public void resolveDocumentReference() + void resolveDocumentReference() { EntityReference reference = this.resolver.resolve("", EntityType.DOCUMENT); assertNull(reference); @@ -62,7 +62,7 @@ public void resolveDocumentReference() } @Test - public void resolveDocumentReferenceWithBaseReference() + void resolveDocumentReferenceWithBaseReference() { EntityReference reference = this.resolver.resolve("", EntityType.DOCUMENT, new EntityReference("space", EntityType.SPACE)); diff --git a/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/internal/reference/WithParametersTest.java b/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/internal/reference/WithParametersTest.java index 65f073f81375..8ee62bd32b4e 100644 --- a/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/internal/reference/WithParametersTest.java +++ b/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/internal/reference/WithParametersTest.java @@ -20,6 +20,7 @@ package org.xwiki.model.internal.reference; import java.util.Locale; +import java.util.Map; import org.junit.jupiter.api.Test; import org.xwiki.model.EntityType; @@ -67,5 +68,11 @@ void serializeResolveDocument() EntityReference resolved = this.resolver.resolve(serialized, EntityType.DOCUMENT); assertEquals(documentReference, resolved); + + EntityReference reference = resolver.resolve("wiki:space;param1=value2.page", EntityType.DOCUMENT); + assertEquals("wiki", reference.extractReference(EntityType.WIKI).getName()); + assertEquals("space", reference.extractReference(EntityType.SPACE).getName()); + assertEquals("page", reference.getName()); + assertEquals(Map.of("param1", "value2"), reference.extractReference(EntityType.SPACE).getParameters()); } } diff --git a/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/internal/reference/converter/EntityReferenceConverterTest.java b/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/internal/reference/converter/EntityReferenceConverterTest.java index b2ddbc075157..ad99a0adfa1d 100644 --- a/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/internal/reference/converter/EntityReferenceConverterTest.java +++ b/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/internal/reference/converter/EntityReferenceConverterTest.java @@ -21,6 +21,8 @@ import javax.inject.Inject; +import java.util.Map; + import org.junit.jupiter.api.Test; import org.xwiki.model.EntityType; import org.xwiki.model.reference.EntityReference; @@ -57,7 +59,8 @@ void convertDocumentFromString() new EntityReference("wiki", EntityType.WIKI))); assertEquals(reference, this.converterManager.convert(EntityReference.class, "document:wiki:space.page")); - reference = new EntityReference("page", EntityType.DOCUMENT, new EntityReference("space", EntityType.SPACE)); + reference = new EntityReference("page", EntityType.DOCUMENT, + new EntityReference("space", EntityType.SPACE)); assertEquals(reference, this.converterManager.convert(EntityReference.class, "document:space.page")); assertEquals(reference, this.converterManager.convert(EntityReference.class, "space.page")); diff --git a/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/reference/EntityReferenceSetTest.java b/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/reference/EntityReferenceSetTest.java index 6ebdad43794e..8b9fbdc4e881 100644 --- a/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/reference/EntityReferenceSetTest.java +++ b/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/reference/EntityReferenceSetTest.java @@ -21,16 +21,16 @@ import java.util.Locale; -import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.xwiki.model.EntityType; import org.xwiki.model.internal.reference.DefaultSymbolScheme; import org.xwiki.model.internal.reference.RelativeStringEntityReferenceResolver; import org.xwiki.test.annotation.ComponentList; -import org.xwiki.test.mockito.MockitoComponentMockingRule; +import org.xwiki.test.junit5.mockito.ComponentTest; +import org.xwiki.test.junit5.mockito.InjectMockComponents; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Validate {@link EntityReferenceSet}. @@ -40,11 +40,11 @@ @ComponentList({ DefaultSymbolScheme.class }) -public class EntityReferenceSetTest +@ComponentTest +class EntityReferenceSetTest { - @Rule - public MockitoComponentMockingRule resolverMocker = - new MockitoComponentMockingRule<>(RelativeStringEntityReferenceResolver.class); + @InjectMockComponents + private RelativeStringEntityReferenceResolver resolver; private EntityReferenceSet set = new EntityReferenceSet(); @@ -60,12 +60,12 @@ private void assertNotMatches(EntityReference reference) private void assertMatches(String reference, EntityType type) throws Exception { - assertMatches(this.resolverMocker.getComponentUnderTest().resolve(reference, type)); + assertMatches(this.resolver.resolve(reference, type)); } private void assertNotMatches(String reference, EntityType type) throws Exception { - assertNotMatches(this.resolverMocker.getComponentUnderTest().resolve(reference, type)); + assertNotMatches(this.resolver.resolve(reference, type)); } private void assertMatchesWiki(String reference) throws Exception @@ -107,7 +107,7 @@ private void includes(EntityReference reference) private void includes(String reference, EntityType type) throws Exception { - includes(this.resolverMocker.getComponentUnderTest().resolve(reference, type)); + includes(this.resolver.resolve(reference, type)); } private void includesWiki(String reference) throws Exception @@ -134,7 +134,7 @@ private void excludes(EntityReference reference) private void excludes(String reference, EntityType type) throws Exception { - excludes(this.resolverMocker.getComponentUnderTest().resolve(reference, type)); + excludes(this.resolver.resolve(reference, type)); } private void excludesWiki(String reference) throws Exception @@ -155,7 +155,7 @@ private void excludesDocument(String reference) throws Exception // Tests @Test - public void includeWiki() throws Exception + void includeWiki() throws Exception { includesWiki("wiki"); @@ -175,7 +175,7 @@ public void includeWiki() throws Exception } @Test - public void includeSpace() throws Exception + void includeSpace() throws Exception { includesSpace("wiki:space"); @@ -194,7 +194,7 @@ public void includeSpace() throws Exception } @Test - public void includeNestedSpace() throws Exception + void includeNestedSpace() throws Exception { includesSpace("wiki:space.nested"); @@ -218,7 +218,7 @@ public void includeNestedSpace() throws Exception } @Test - public void includePartialOnlySpace() throws Exception + void includePartialOnlySpace() throws Exception { includesSpace("space"); @@ -230,7 +230,7 @@ public void includePartialOnlySpace() throws Exception } @Test - public void includeDocument() throws Exception + void includeDocument() throws Exception { includesDocument("wiki:space.document"); @@ -242,7 +242,7 @@ public void includeDocument() throws Exception } @Test - public void includeLocalDocumentLocale() + void includeLocalDocumentLocale() { includes(new LocalDocumentReference("space", "document", Locale.ROOT)); @@ -261,7 +261,7 @@ public void includeLocalDocumentLocale() } @Test - public void includeDocumentInNestedSpace() throws Exception + void includeDocumentInNestedSpace() throws Exception { includesDocument("wiki:space.nestedspace.document"); @@ -276,7 +276,7 @@ public void includeDocumentInNestedSpace() throws Exception } @Test - public void includeDocumentsInNestedSpacesWithShortAfterLong() throws Exception + void includeDocumentsInNestedSpacesWithShortAfterLong() throws Exception { includesDocument("wiki:space.nestedspace.document"); includesDocument("wiki:space.document"); @@ -292,7 +292,7 @@ public void includeDocumentsInNestedSpacesWithShortAfterLong() throws Exception } @Test - public void includeDocumentsInNestedSpacesWithLongAfterShort() throws Exception + void includeDocumentsInNestedSpacesWithLongAfterShort() throws Exception { includesDocument("wiki:space.document"); includesDocument("wiki:space.nestedspace.document"); @@ -308,7 +308,7 @@ public void includeDocumentsInNestedSpacesWithLongAfterShort() throws Exception } @Test - public void excludeWiki() throws Exception + void excludeWiki() throws Exception { excludesWiki("wiki"); @@ -328,7 +328,7 @@ public void excludeWiki() throws Exception } @Test - public void excludeSpace() throws Exception + void excludeSpace() throws Exception { excludesSpace("wiki:space"); @@ -340,7 +340,7 @@ public void excludeSpace() throws Exception } @Test - public void excludeNestedSpace() throws Exception + void excludeNestedSpace() throws Exception { excludesSpace("wiki:space.nested"); @@ -355,7 +355,7 @@ public void excludeNestedSpace() throws Exception } @Test - public void excludePartial() throws Exception + void excludePartial() throws Exception { excludesSpace("space"); @@ -367,7 +367,7 @@ public void excludePartial() throws Exception } @Test - public void includeLocale() + void includeLocale() { includes(new DocumentReference("wiki", "space", "document", Locale.ENGLISH)); @@ -387,7 +387,7 @@ public void includeLocale() } @Test - public void excludeLocale() + void excludeLocale() { excludes(new DocumentReference("wiki", "space", "document", Locale.ENGLISH)); diff --git a/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/reference/EntityReferenceTest.java b/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/reference/EntityReferenceTest.java index 177924e8180e..9d04c85afc00 100644 --- a/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/reference/EntityReferenceTest.java +++ b/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/reference/EntityReferenceTest.java @@ -630,4 +630,48 @@ void removeParametersRecursive() assertSame(reference, reference.removeParameters(false)); assertEquals(parentWithoutParameters, parent.removeParameters(true)); } + + @Test + void getParentType() + { + // no parent, no custom type, and no fallback for wiki + EntityReference wikiReference = new EntityReference(WIKI_NAME, EntityType.WIKI); + assertNull(wikiReference.getParentType()); + + // parent type + EntityReference spaceReference = new EntityReference(SPACE_NAME, EntityType.SPACE, wikiReference); + assertEquals(EntityType.WIKI, spaceReference.getParentType()); + + // parent is set, so custom type cannot be set + spaceReference = + new EntityReference(spaceReference, Map.of(EntityReference.PARENT_TYPE_PARAMETER, EntityType.SPACE)); + assertEquals(EntityType.WIKI, spaceReference.getParentType()); + + // no parent, no custom type, but fallback on first allowed parent type + spaceReference = new EntityReference(SPACE_NAME, EntityType.SPACE); + assertEquals(EntityType.WIKI, spaceReference.getParentType()); + + // custom type + spaceReference = + new EntityReference(spaceReference, Map.of(EntityReference.PARENT_TYPE_PARAMETER, EntityType.SPACE)); + assertEquals(EntityType.SPACE, spaceReference.getParentType()); + + // custom type + spaceReference = new EntityReference(SPACE_NAME, EntityType.SPACE, + Map.of(EntityReference.PARENT_TYPE_PARAMETER, "SPACE")); + assertEquals(EntityType.SPACE, spaceReference.getParentType()); + + // invalid parent type, fallback on first allowed parent type + Map parametersMap = + Map.of(EntityReference.PARENT_TYPE_PARAMETER, EntityType.PAGE); + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> + new EntityReference(SPACE_NAME, EntityType.SPACE, parametersMap)); + assertEquals("The parent type [PAGE] does not belong to the allowed parents", exception.getMessage()); + + // invalid parent type value. + Map parametersMap2 = Map.of(EntityReference.PARENT_TYPE_PARAMETER,42); + exception = assertThrows(IllegalArgumentException.class, () -> + new EntityReference(SPACE_NAME, EntityType.SPACE, parametersMap2)); + assertEquals("No enum constant org.xwiki.model.EntityType.42", exception.getMessage()); + } }