blob: bf4266e68097320641db06bdfe1886764a2580c2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sling.resourcemerger.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections4.iterators.IteratorIterable;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ModifiableValueMap;
import org.apache.sling.api.resource.NonExistingResource;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.hamcrest.ResourceMatchers;
import org.apache.sling.resourcemerger.spi.MergedResourcePicker2;
import org.apache.sling.spi.resource.provider.ResolveContext;
import org.apache.sling.spi.resource.provider.ResourceContext;
import org.apache.sling.testing.resourceresolver.MockHelper;
import org.apache.sling.testing.resourceresolver.MockResourceResolverFactory;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import com.google.common.collect.ImmutableMap;
/**
* Tests common merging behaviour (independent of actual picker implementation)
*
*/
public class CommonMergedResourceProviderTest {
private ResourceResolver resolver;
private MergingResourceProvider provider;
private Resource base;
private Resource overlay;
private ResolveContext<Void> ctx;
/**
* A very simple resource picker which will just merge two different resources (base and overlay) directly on the mount point.
* Supports also merging of arbitrary child resources below the mount point.
*/
private final class SimpleMergedResourcePicker implements MergedResourcePicker2 {
private final List<Resource> rootResourcesToMerge;
public SimpleMergedResourcePicker() {
rootResourcesToMerge = new ArrayList<Resource>();
rootResourcesToMerge.add(base);
rootResourcesToMerge.add(overlay);
}
@Override
public List<Resource> pickResources(ResourceResolver resolver, String relativePath, Resource relatedResource) {
// merging in the root path is always successfull
if (relativePath.isEmpty()) {
return rootResourcesToMerge;
} else {
List<Resource> resourcesToMerge = new ArrayList<Resource>();
// if deeper merging is wanted, check in both locations if the relative path is available at all
Resource resourceToPick = base.getChild(relativePath);
if (resourceToPick != null) {
resourcesToMerge.add(resourceToPick);
} else {
resourcesToMerge.add(new NonExistingResource(resolver, base.getPath() + "/" + relativePath));
}
resourceToPick = overlay.getChild(relativePath);
if (resourceToPick != null) {
resourcesToMerge.add(resourceToPick);
} else {
resourcesToMerge.add(new NonExistingResource(resolver, overlay.getPath() + "/" + relativePath));
}
return resourcesToMerge;
}
}
}
@Before
public void setup() throws LoginException, PersistenceException {
final ResourceResolverFactory factory = new MockResourceResolverFactory();
this.resolver = factory.getResourceResolver(null);
this.ctx = new BasicResolveContext<Void>(resolver);
MockHelper.create(this.resolver)
.resource("/apps").resource("base")
.resource("/apps/overlay").commit();
base = this.resolver.getResource("/apps/base");
overlay = this.resolver.getResource("/apps/overlay");
this.provider = new CRUDMergingResourceProvider("/merged", new SimpleMergedResourcePicker(), true);
}
@Test
public void testHideChildren() throws PersistenceException {
// create new child nodes below base and overlay
MockHelper.create(this.resolver)
.resource("/apps/base/child1").p("property1", "frombase")
.resource("/apps/base/child2").p("property1", "frombase")
.resource("/apps/base/child1/grandchild").p("propertygrandchild1", "frombase")
.resource("/apps/base/child1/grandchild/grandgrandchildfrombase").p("propertygrandgrandchild1", "frombase")
.resource("/apps/overlay/child1").p("property1", "fromoverlay")
.resource("/apps/overlay/child1/grandchild").p("propertygrandchild1", "fromoverlay")
.resource("/apps/overlay/child1/grandchild/grandgrandchildfromoverlay").p("propertygrandgrandchild1", "fromoverlay")
.resource("/apps/overlay/child1/grandchild1").p("propertygrandchild1", "fromoverlay")
.resource("/apps/overlay/child1/grandchild1/grandgrandchild1").p("propertygrandgrandchild1", "fromoverlay")
.resource("/apps/overlay/child3").p("property1", "fromoverlay")
.commit();
ModifiableValueMap properties = overlay.adaptTo(ModifiableValueMap.class);
properties.put(MergedResourceConstants.PN_HIDE_CHILDREN, "*");
resolver.commit();
Resource mergedResource = this.provider.getResource(ctx, "/merged", ResourceContext.EMPTY_CONTEXT, null);
// convert the iterator returned by list children into an iterable (to be able to perform some tests)
IteratorIterable<Resource> iterableChildren = new IteratorIterable<Resource>(provider.listChildren(ctx, mergedResource), true);
// all overlay resource are still exposed, because hiding children by wildcard only hides children from underlying resources
Assert.assertThat(iterableChildren, Matchers.containsInAnyOrder(
ResourceMatchers.nameAndProps("child1", Collections.singletonMap("property1", (Object)"fromoverlay")),
ResourceMatchers.nameAndProps("child3", Collections.singletonMap("property1", (Object)"fromoverlay"))
));
// go down one level!
Resource mergedChildResource = this.provider.getResource(ctx, "/merged/child1", ResourceContext.EMPTY_CONTEXT, null);
// convert the iterator returned by list children into an iterable (to be able to perform some tests)
IteratorIterable<Resource> iterableGrandchildren = new IteratorIterable<Resource>(provider.listChildren(ctx, mergedChildResource), true);
// all overlay resource are still exposed, because hiding children by wildcard only hides children from underlying resources
Assert.assertThat(iterableGrandchildren, Matchers.containsInAnyOrder(
ResourceMatchers.nameAndProps("grandchild", Collections.singletonMap("propertygrandchild1", (Object)"fromoverlay")),
ResourceMatchers.nameAndProps("grandchild1", Collections.singletonMap("propertygrandchild1", (Object)"fromoverlay")))
);
// go down two levels
Resource mergedGrandChildResource = this.provider.getResource(ctx, "/merged/child1/grandchild", ResourceContext.EMPTY_CONTEXT, null);
// convert the iterator returned by list children into an iterable (to be able to perform some tests)
IteratorIterable<Resource> iterableGrandGrandchildren = new IteratorIterable<Resource>(provider.listChildren(ctx, mergedGrandChildResource), true);
// all overlay resource are still exposed, because hiding children by wildcard only hides children from underlying resources
Assert.assertThat(iterableGrandGrandchildren, Matchers.contains(
ResourceMatchers.nameAndProps("grandgrandchildfromoverlay", Collections.singletonMap("propertygrandgrandchild1", (Object)"fromoverlay")))
);
// go down two levels (in node which is only available in overlay!)
mergedGrandChildResource = this.provider.getResource(ctx, "/merged/child1/grandchild1", ResourceContext.EMPTY_CONTEXT, null);
// convert the iterator returned by list children into an iterable (to be able to perform some tests)
iterableGrandGrandchildren = new IteratorIterable<Resource>(provider.listChildren(ctx, mergedGrandChildResource), true);
// all overlay resource are still exposed, because hiding children by wildcard only hides children from underlying resources
Assert.assertThat(iterableGrandGrandchildren, Matchers.contains(
ResourceMatchers.nameAndProps("grandgrandchild1", Collections.singletonMap("propertygrandgrandchild1", (Object)"fromoverlay")))
);
// now hide by explicit value
properties.put(MergedResourceConstants.PN_HIDE_CHILDREN, "child1");
resolver.commit();
// child1 is no longer exposed from overlay, because hiding children by name hides children from underlying as well as from local resources, child2 is exposed from base
iterableChildren = new IteratorIterable<Resource>(provider.listChildren(ctx, mergedResource), true);
Assert.assertThat(iterableChildren, Matchers.containsInAnyOrder(
ResourceMatchers.name("child2"),
ResourceMatchers.name("child3")));
// now hide by negated value (hide all children except for the one with name child2)
properties.put(MergedResourceConstants.PN_HIDE_CHILDREN, new String[]{"!child2", "*", "child3"});
resolver.commit();
iterableChildren = new IteratorIterable<Resource>(provider.listChildren(ctx, mergedResource), true);
Assert.assertThat(iterableChildren, Matchers.containsInAnyOrder(
ResourceMatchers.name("child2")
));
}
@Test
public void testHideChildrenWithResourceNamesStartingWithExclamationMark() throws PersistenceException {
// create new child nodes below base and overlay
MockHelper.create(this.resolver)
.resource("/apps/base/!child1").p("property1", "frombase")
.resource("/apps/overlay/!child1").p("property1", "fromoverlay")
.resource("/apps/overlay/!child3").p("property1", "fromoverlay")
.commit();
ModifiableValueMap properties = overlay.adaptTo(ModifiableValueMap.class);
properties.put(MergedResourceConstants.PN_HIDE_CHILDREN, "!!child3"); // escape the resource name with another exclamation mark
resolver.commit();
Resource mergedResource = this.provider.getResource(ctx, "/merged", ResourceContext.EMPTY_CONTEXT, null);
// convert the iterator returned by list children into an iterable (to be able to perform some tests)
IteratorIterable<Resource> iterable = new IteratorIterable<Resource>(provider.listChildren(ctx, mergedResource), true);
// the resource named "!child3" should be hidden
Assert.assertThat(iterable, Matchers.contains(ResourceMatchers.nameAndProps("!child1", Collections.singletonMap("property1", (Object)"fromoverlay"))));
}
@Test
public void testHideChildrenBeingSetOnParent() throws PersistenceException {
// create new child nodes below base and overlay
MockHelper.create(this.resolver)
.resource("/apps/base/child").p("property1", "frombase").resource("grandchild").p("property1", "frombase")
.resource("/apps/base/child2").p("property1", "frombase")
.resource("/apps/overlay/child").p("property1", "fromoverlay")
.commit();
ModifiableValueMap properties = overlay.adaptTo(ModifiableValueMap.class);
properties.put(MergedResourceConstants.PN_HIDE_CHILDREN, "*");
resolver.commit();
Resource mergedResource = this.provider.getResource(ctx, "/merged", ResourceContext.EMPTY_CONTEXT, null);
// the child was hidden on the parent (but only for the underlying resource), the local child from the overlay is still exposed
Assert.assertThat(provider.getResource(ctx, "/merged/child", ResourceContext.EMPTY_CONTEXT, mergedResource), ResourceMatchers.nameAndProps("child", Collections.singletonMap("property1", (Object)"fromoverlay")));
}
@Test
public void testHideProperties() {
ModifiableValueMap properties = base.adaptTo(ModifiableValueMap.class);
properties.put("property1", "frombase");
properties.put("property2", "frombase");
properties.put(MergedResourceConstants.PN_HIDE_CHILDREN, "some invalid resource");
// hide with wildcard
ModifiableValueMap overlayProperties = overlay.adaptTo(ModifiableValueMap.class);
overlayProperties.put(MergedResourceConstants.PN_HIDE_PROPERTIES, "*");
Map<String, Object> expectedProperties = new HashMap<String, Object>();
expectedProperties.put("property1", "fromoverlay");
expectedProperties.put("property3", "fromoverlay");
overlayProperties.putAll(expectedProperties);
this.provider = new CRUDMergingResourceProvider("/merged", new SimpleMergedResourcePicker(), false);
Resource mergedResource = this.provider.getResource(ctx, "/merged", ResourceContext.EMPTY_CONTEXT, null);
// property1 is still exposed from overlay, because hiding properties by wildcard only hides children from underlying resources
Assert.assertThat(mergedResource, ResourceMatchers.props(expectedProperties));
// all properties from underlying resource are hidden!
Assert.assertThat(mergedResource, Matchers.not(ResourceMatchers.props(properties)));
// make sure no special properties are exposed
Assert.assertFalse(mergedResource.getValueMap().containsKey(MergedResourceConstants.PN_HIDE_CHILDREN));
Assert.assertFalse(mergedResource.getValueMap().containsKey(MergedResourceConstants.PN_HIDE_PROPERTIES));
// hide by value
overlayProperties.put(MergedResourceConstants.PN_HIDE_PROPERTIES, new String[]{"property1"});
mergedResource = this.provider.getResource(ctx, "/merged", ResourceContext.EMPTY_CONTEXT, null);
expectedProperties.put("property2", "frombase");
expectedProperties.remove("property1");
// property2 and property 3 are still exposed
Assert.assertThat(mergedResource, ResourceMatchers.props(expectedProperties));
// property1 is no longer exposed from overlay nor base, because hiding properties by name also hides local properties
Assert.assertThat(mergedResource, Matchers.not(ResourceMatchers.props(Collections.singletonMap("property1", (Object)"fromoverlay"))));
// make sure no special properties are exposed
Assert.assertFalse(mergedResource.getValueMap().containsKey(MergedResourceConstants.PN_HIDE_CHILDREN));
Assert.assertFalse(mergedResource.getValueMap().containsKey(MergedResourceConstants.PN_HIDE_PROPERTIES));
}
@Test
public void testHidePropertiesWithMultiValue() {
ModifiableValueMap properties = base.adaptTo(ModifiableValueMap.class);
properties.put("!property1", "frombase");
properties.put("!!property2", "frombase");
properties.put("!!!property3", "frombase");
properties.put(MergedResourceConstants.PN_HIDE_CHILDREN, "some invalid resource");
// hide by value
ModifiableValueMap overlayProperties = overlay.adaptTo(ModifiableValueMap.class);
overlayProperties.put(MergedResourceConstants.PN_HIDE_PROPERTIES, new String[]{"!!property1", "!!!!!!property3"});
Resource mergedResource = this.provider.getResource(ctx, "/merged", ResourceContext.EMPTY_CONTEXT, null);
Map<String, Object> expectedProperties = new HashMap<String, Object>();
expectedProperties.put("!!property2", "frombase");
// property2 is still exposed
Assert.assertThat(mergedResource, ResourceMatchers.props(expectedProperties));
// property1 is no longer exposed from overlay nor base, because hiding properties by name also hides local properties
Assert.assertThat(mergedResource, Matchers.not(ResourceMatchers.props(Collections.singletonMap("!property1", (Object)"frombase"))));
Assert.assertThat(mergedResource, Matchers.not(ResourceMatchers.props(Collections.singletonMap("!!!property3", (Object)"frombase"))));
}
@Test
public void testOrderOfPartiallyOverwrittenChildren() throws PersistenceException {
// see https://issues.apache.org/jira/browse/SLING-4915
// and https://issues.apache.org/jira/browse/SLING-6956
// create new child nodes below base and overlay
MockHelper.create(this.resolver)
.resource("/apps/base/child1")
.resource("/apps/base/child2")
.resource("/apps/base/child3")
.resource("/apps/overlay/child4")
.resource("/apps/overlay/child2")
.resource("/apps/overlay/child3")
.commit();
Resource mergedResource = this.provider.getResource(ctx, "/merged", ResourceContext.EMPTY_CONTEXT, null);
// convert the iterator returned by list children into an iterable (to be able to perform some tests)
IteratorIterable<Resource> iterable = new IteratorIterable<Resource>(provider.listChildren(ctx, mergedResource), true);
Assert.assertThat(iterable, Matchers.contains(ResourceMatchers.name("child1"),ResourceMatchers.name("child4"), ResourceMatchers.name("child2"), ResourceMatchers.name("child3")));
}
@Test
public void testOrderOfPartiallyOverlappingChildren() throws PersistenceException {
// see https://issues.apache.org/jira/browse/SLING-4915
// and https://issues.apache.org/jira/browse/SLING-6956
// create new child nodes below base and overlay
MockHelper.create(this.resolver)
.resource("/apps/base/child1")
.resource("/apps/base/child2")
.resource("/apps/base/child3")
.resource("/apps/overlay/child4")
.resource("/apps/overlay/child2")
.resource("/apps/overlay/child3")
.commit();
Resource mergedResource = this.provider.getResource(ctx, "/merged", ResourceContext.EMPTY_CONTEXT, null);
// convert the iterator returned by list children into an iterable (to be able to perform some tests)
IteratorIterable<Resource> iterable = new IteratorIterable<Resource>(provider.listChildren(ctx, mergedResource), true);
Assert.assertThat(iterable, Matchers.contains(ResourceMatchers.name("child1"),ResourceMatchers.name("child4"), ResourceMatchers.name("child2"), ResourceMatchers.name("child3")));
}
@Test
public void testOrderOfPartiallyOverlappingChildrenWithDifferentOrder() throws PersistenceException {
// see https://issues.apache.org/jira/browse/SLING-4915
// and https://issues.apache.org/jira/browse/SLING-6956
// create new child nodes below base and overlay
MockHelper.create(this.resolver)
.resource("/apps/base/child1")
.resource("/apps/base/child3")
.resource("/apps/base/child5")
.resource("/apps/overlay/child2")
.resource("/apps/overlay/child3")
.resource("/apps/overlay/child4")
.resource("/apps/overlay/child1")
.commit();
Resource mergedResource = this.provider.getResource(ctx, "/merged", ResourceContext.EMPTY_CONTEXT, null);
// convert the iterator returned by list children into an iterable (to be able to perform some tests)
IteratorIterable<Resource> iterable = new IteratorIterable<Resource>(provider.listChildren(ctx, mergedResource), true);
Assert.assertThat(iterable, Matchers.contains(ResourceMatchers.name("child5"),ResourceMatchers.name("child2"), ResourceMatchers.name("child3"), ResourceMatchers.name("child4"),ResourceMatchers.name("child1")));
}
@Test
public void testOrderOfNonOverlappingChildren() throws PersistenceException {
// create new child nodes below base and overlay
// https://issues.apache.org/jira/browse/SLING-6956
MockHelper.create(this.resolver)
.resource("/apps/base/child1")
.resource("/apps/base/child2")
.resource("/apps/overlay/child3")
.resource("/apps/overlay/child4")
.commit();
Resource mergedResource = this.provider.getResource(ctx, "/merged", ResourceContext.EMPTY_CONTEXT, null);
// convert the iterator returned by list children into an iterable (to be able to perform some tests)
IteratorIterable<Resource> iterable = new IteratorIterable<Resource>(provider.listChildren(ctx, mergedResource), true);
Assert.assertThat(iterable, Matchers.contains(ResourceMatchers.name("child1"),ResourceMatchers.name("child2"), ResourceMatchers.name("child3"), ResourceMatchers.name("child4")));
}
@Test
public void testOrderOfFullyOverlappingChildren() throws PersistenceException {
// create new child nodes below base and overlay
// https://issues.apache.org/jira/browse/SLING-6956
MockHelper.create(this.resolver)
.resource("/apps/base/child1")
.resource("/apps/base/child2")
.resource("/apps/base/child3")
.resource("/apps/overlay/child2")
.commit();
Resource mergedResource = this.provider.getResource(ctx, "/merged", ResourceContext.EMPTY_CONTEXT, null);
// convert the iterator returned by list children into an iterable (to be able to perform some tests)
IteratorIterable<Resource> iterable = new IteratorIterable<Resource>(provider.listChildren(ctx, mergedResource), true);
Assert.assertThat(iterable, Matchers.contains(ResourceMatchers.name("child1"),ResourceMatchers.name("child2"), ResourceMatchers.name("child3")));
}
@Test
public void testOrderOfFullyOverlappingChildrenWithDifferentOrder() throws PersistenceException {
// create new child nodes below base and overlay
// https://issues.apache.org/jira/browse/SLING-6956
MockHelper.create(this.resolver)
.resource("/apps/base/child1")
.resource("/apps/base/child2")
.resource("/apps/base/child3")
.resource("/apps/overlay/child3")
.resource("/apps/overlay/child2")
.commit();
Resource mergedResource = this.provider.getResource(ctx, "/merged", ResourceContext.EMPTY_CONTEXT, null);
// convert the iterator returned by list children into an iterable (to be able to perform some tests)
IteratorIterable<Resource> iterable = new IteratorIterable<Resource>(provider.listChildren(ctx, mergedResource), true);
Assert.assertThat(iterable, Matchers.contains(ResourceMatchers.name("child1"),ResourceMatchers.name("child2"), ResourceMatchers.name("child3")));
}
@Test
public void testOrderBeingModifiedThroughOrderBefore() throws PersistenceException {
// create new child nodes below base and overlay
MockHelper.create(this.resolver)
.resource("/apps/base/child1")
.resource("/apps/base/child2")
.resource("/apps/base/child3")
.resource("/apps/base/child4")
.resource("/apps/overlay/child5").p(MergedResourceConstants.PN_ORDER_BEFORE, "child2")
.resource("/apps/overlay/child6").p(MergedResourceConstants.PN_ORDER_BEFORE, "child2")
.resource("/apps/overlay/child7").p(MergedResourceConstants.PN_ORDER_BEFORE, "child4")
.resource("/apps/overlay/child8").p(MergedResourceConstants.PN_ORDER_BEFORE, "child3")
.resource("/apps/overlay/child9").p(MergedResourceConstants.PN_ORDER_BEFORE, "child3")
.resource("/apps/overlay/child10")
.resource("/apps/overlay/child3")
.commit();
Resource mergedResource = this.provider.getResource(ctx, "/merged", ResourceContext.EMPTY_CONTEXT, null);
// convert the iterator returned by list children into an iterable (to be able to perform some tests)
IteratorIterable<Resource> iterable = new IteratorIterable<Resource>(provider.listChildren(ctx, mergedResource), true);
Assert.assertThat(iterable, Matchers.contains(ResourceMatchers.name("child1"),ResourceMatchers.name("child5"), ResourceMatchers.name("child6"), ResourceMatchers.name("child2"), ResourceMatchers.name("child8"),ResourceMatchers.name("child9"), ResourceMatchers.name("child7"),ResourceMatchers.name("child4"),ResourceMatchers.name("child10"),ResourceMatchers.name("child3")));
}
@Test
public void testOrderBeingModifiedThroughOrderBeforeAndFullyOverlappingChildren() throws PersistenceException {
// create new child nodes below base and overlay
MockHelper.create(this.resolver)
.resource("/apps/base/child1")
.resource("/apps/base/child2")
.resource("/apps/overlay/child1")
.resource("/apps/overlay/child3").p(MergedResourceConstants.PN_ORDER_BEFORE, "child2")
.resource("/apps/overlay/child2")
.commit();
Resource mergedResource = this.provider.getResource(ctx, "/merged", ResourceContext.EMPTY_CONTEXT, null);
// convert the iterator returned by list children into an iterable (to be able to perform some tests)
IteratorIterable<Resource> iterable = new IteratorIterable<Resource>(provider.listChildren(ctx, mergedResource), true);
Assert.assertThat(iterable, Matchers.contains(ResourceMatchers.name("child1"),ResourceMatchers.name("child3"),ResourceMatchers.name("child2")));
}
@Test
public void testNodesDefinedInBaseStructureShouldBeReflectedInOverlaidStructureWhenNotExcludedInCombinationWithWildcard() throws PersistenceException {
final String title = "title";
final String field = "field";
final String description = "description";
MockHelper.create(this.resolver)
// Set up the base structure, including a tab that is not desired in the overlaid situation
.resource("/apps/base/test")
.resource("/apps/base/test/tabs")
.resource("/apps/base/test/tabs/undesired-tab")
.p(title, "Undesired Tab")
.resource("/apps/base/test/tabs/desired-tab")
.p(title, "Desired Tab")
// Set up a field inside the items node in the desired tab, that should be reflected in the overlaid situation
.resource("/apps/base/test/tabs/desired-tab/items")
.resource("/apps/base/test/tabs/desired-tab/items/" + field)
// Define a property on the field inside the base tab, that should be reflected int he overlaid situation
.p(field, field)
// Set up the overlaid structure, defining the tabs again
.resource("/apps/overlay/test")
.resource("/apps/overlay/test/tabs")
// Explicitly define that *all* children need to be hidden from the base structure, except for the desired tab
.p(MergedResourceConstants.PN_HIDE_CHILDREN, new String[]{"!desired-tab", "*"})
.resource("/apps/overlay/test/tabs/desired-tab")
.resource("/apps/overlay/test/tabs/desired-tab/items")
.resource("/apps/overlay/test/tabs/desired-tab/items/" + field)
// Define an additional property on a node inside the desired tab, so that it should end up getting 2 properties
.p(description, description)
.commit();
// Ensure that the undesired tab is not found
final Resource tabsResource = this.provider.getResource(ctx, "/merged/test/tabs", ResourceContext.EMPTY_CONTEXT, null);
Assert.assertNotNull(tabsResource);
final IteratorIterable<Resource> tabs = new IteratorIterable<Resource>(this.provider.listChildren(ctx, tabsResource), true);
Assert.assertThat(tabs, Matchers.contains(
ResourceMatchers.nameAndProps("desired-tab", title, "Desired Tab")
));
// Ensure that the desired tab also has the newly added description, as well as its original name
final Resource desiredTabItemsResource = this.provider.getResource(ctx, "/merged/test/tabs/desired-tab/items", ResourceContext.EMPTY_CONTEXT, null);
Assert.assertNotNull(desiredTabItemsResource);
final IteratorIterable<Resource> desiredTabItems = new IteratorIterable<Resource>(provider.listChildren(ctx, desiredTabItemsResource), true);
Assert.assertThat(desiredTabItems, Matchers.contains(
ResourceMatchers.nameAndProps(field, ImmutableMap.<String, Object>builder()
.put(field, field)
.put(description, description)
.build())
)
);
}
}