diff --git a/controller/src/main/java/org/jboss/as/controller/capability/BinaryCapabilityNameResolver.java b/controller/src/main/java/org/jboss/as/controller/capability/BinaryCapabilityNameResolver.java index b82fd53088a..e09b9d7a909 100644 --- a/controller/src/main/java/org/jboss/as/controller/capability/BinaryCapabilityNameResolver.java +++ b/controller/src/main/java/org/jboss/as/controller/capability/BinaryCapabilityNameResolver.java @@ -52,4 +52,5 @@ public String[] apply(PathAddress address) { return new String[] { grandparent.getParent().getLastElement().getValue(), grandparent.getLastElement().getValue() }; } }, + ; } diff --git a/controller/src/main/java/org/jboss/as/controller/capability/UnaryCapabilityNameResolver.java b/controller/src/main/java/org/jboss/as/controller/capability/UnaryCapabilityNameResolver.java index 6eaa45bca28..31565762adf 100644 --- a/controller/src/main/java/org/jboss/as/controller/capability/UnaryCapabilityNameResolver.java +++ b/controller/src/main/java/org/jboss/as/controller/capability/UnaryCapabilityNameResolver.java @@ -44,4 +44,5 @@ public String[] apply(PathAddress address) { return new String[] { ModelDescriptionConstants.LOCAL }; } }, + ; } diff --git a/discovery/src/main/java/org/wildfly/extension/discovery/AggregateDiscoveryProviderRegistrar.java b/discovery/src/main/java/org/wildfly/extension/discovery/AggregateDiscoveryProviderRegistrar.java index b6b62300ae4..7d52963b3f6 100644 --- a/discovery/src/main/java/org/wildfly/extension/discovery/AggregateDiscoveryProviderRegistrar.java +++ b/discovery/src/main/java/org/wildfly/extension/discovery/AggregateDiscoveryProviderRegistrar.java @@ -23,7 +23,7 @@ public class AggregateDiscoveryProviderRegistrar extends DiscoveryProviderRegist static final PathElement PATH = PathElement.pathElement("aggregate-provider"); static final StringListAttributeDefinition PROVIDER_NAMES = new StringListAttributeDefinition.Builder("providers") - .setCapabilityReference(CapabilityReferenceRecorder.of(DISCOVERY_PROVIDER_CAPABILITY, DISCOVERY_PROVIDER_DESCRIPTOR)) + .setCapabilityReference(CapabilityReferenceRecorder.builder(DISCOVERY_PROVIDER_CAPABILITY, DISCOVERY_PROVIDER_DESCRIPTOR).build()) .setFlags(Flag.RESTART_RESOURCE_SERVICES) .build(); diff --git a/subsystem/src/main/java/org/wildfly/subsystem/resource/ResourceDescriptor.java b/subsystem/src/main/java/org/wildfly/subsystem/resource/ResourceDescriptor.java index 2dd578e5845..acfeed8ff74 100644 --- a/subsystem/src/main/java/org/wildfly/subsystem/resource/ResourceDescriptor.java +++ b/subsystem/src/main/java/org/wildfly/subsystem/resource/ResourceDescriptor.java @@ -364,15 +364,6 @@ default C addCapability(RuntimeCapability capability) { */ C addCapability(RuntimeCapability capability, Predicate filter); - /** - * Adds the specified runtime capabilities to this resource. - * @param capabilities a variable number of runtime capabilities - * @return a reference to this configurator - */ - default C addCapabilities(RuntimeCapability... capabilities) { - return this.addCapabilities(Set.of(capabilities)); - } - /** * Adds the specified runtime capabilities to this resource. * @param capabilities a collection of runtime capabilities @@ -431,15 +422,6 @@ default C addResourceCapabilityReference(ResourceCapabilityReferenceRecorder return this.addResourceCapabilityReferences(Set.of(reference)); } - /** - * Adds a number of capability references that records requirements for this resource. - * @param references a variable number of capability reference recorders - * @return a reference to this configurator - */ - default C addResourceCapabilityReferences(ResourceCapabilityReferenceRecorder... references) { - return this.addResourceCapabilityReferences(Set.of(references)); - } - /** * Adds a number of capability references that records requirements for this resource. * @param references a collection of capability reference recorders diff --git a/subsystem/src/main/java/org/wildfly/subsystem/resource/capability/CapabilityReferenceRecorder.java b/subsystem/src/main/java/org/wildfly/subsystem/resource/capability/CapabilityReferenceRecorder.java index d9a3397ae0a..c4a3f02dbc7 100644 --- a/subsystem/src/main/java/org/wildfly/subsystem/resource/capability/CapabilityReferenceRecorder.java +++ b/subsystem/src/main/java/org/wildfly/subsystem/resource/capability/CapabilityReferenceRecorder.java @@ -4,6 +4,7 @@ */ package org.wildfly.subsystem.resource.capability; +import java.util.ArrayList; import java.util.List; import java.util.ListIterator; import java.util.function.BiFunction; @@ -55,213 +56,268 @@ default String getBaseDependentName() { * @param capability the capability referencing the specified requirement * @param requirement the requirement of the specified capability */ - static CapabilityReferenceRecorder of(RuntimeCapability capability, UnaryServiceDescriptor requirement) { - return new CapabilityServiceDescriptorReferenceRecorder<>(capability, requirement, List.of()); + static Builder builder(RuntimeCapability capability, UnaryServiceDescriptor requirement) { + return new DefaultBuilder<>(capability, requirement, List.of()); } /** * Creates a new reference between the specified capability and the specified requirement. - * Parent reference name is taken from the {@link PathElement} associated with the resource with which this attribute is referenced. + * By default, the requirement's parent segment derives from the path of the current resource. * @param capability the capability referencing the specified requirement * @param requirement the requirement of the specified capability */ - static CapabilityReferenceRecorder of(RuntimeCapability capability, BinaryServiceDescriptor requirement) { - return of(capability, requirement, CapabilityServiceDescriptorReferenceRecorder.CHILD_PATH); + static BinaryBuilder builder(RuntimeCapability capability, BinaryServiceDescriptor requirement) { + return new DefaultBuilder<>(capability, requirement, List.of(DefaultBuilder.CHILD_PATH)); } /** * Creates a new reference between the specified capability and the specified requirement. - * Parent reference name is taken from the {@link PathElement} returned by the specified resolver + * By default, the requirement's grandparent and parent segments derive from the path of the parent and current resources, respectively. * @param capability the capability referencing the specified requirement * @param requirement the requirement of the specified capability - * @param parentPathResolver resolver of the path containing the first segment of the requirement name */ - static CapabilityReferenceRecorder of(RuntimeCapability capability, BinaryServiceDescriptor requirement, Function parentPathResolver) { - return new CapabilityServiceDescriptorReferenceRecorder<>(capability, requirement, List.of(parentPathResolver)); + static TernaryBuilder builder(RuntimeCapability capability, TernaryServiceDescriptor requirement) { + return new DefaultBuilder<>(capability, requirement, List.of(DefaultBuilder.PARENT_PATH, DefaultBuilder.CHILD_PATH)); } /** - * Creates a new reference between the specified capability and the specified requirement - * Parent reference name is taken from the value of the specified {@link AttributeDefinition}. + * Creates a new reference between the specified capability and the specified requirement. + * By default, the requirement's great-grandparent, grandparent, and parent segments derive from the path of the grandparent, parent, and current resources, respectively. * @param capability the capability referencing the specified requirement * @param requirement the requirement of the specified capability - * @param parentAttribute the attribute from which the parent segment of the requirement name is derived */ - static CapabilityReferenceRecorder of(RuntimeCapability capability, BinaryServiceDescriptor requirement, AttributeDefinition parentAttribute) { - return new CapabilityServiceDescriptorReferenceRecorder<>(capability, requirement, parentAttribute); + static QuaternaryBuilder builder(RuntimeCapability capability, QuaternaryServiceDescriptor requirement) { + return new DefaultBuilder<>(capability, requirement, List.of(DefaultBuilder.GRANDPARENT_PATH, DefaultBuilder.PARENT_PATH, DefaultBuilder.CHILD_PATH)); } - /** - * Creates a new reference between the specified capability and the specified requirement. - * Parent reference name is taken from the {@link PathElement} associated with the resource with which this attribute is referenced. - * @param capability the capability referencing the specified requirement - * @param requirement the requirement of the specified capability - */ - static CapabilityReferenceRecorder of(RuntimeCapability capability, TernaryServiceDescriptor requirement) { - return of(capability, requirement, CapabilityServiceDescriptorReferenceRecorder.PARENT_PATH, CapabilityServiceDescriptorReferenceRecorder.CHILD_PATH); + interface Builder { + /** + * Builds a capability reference recorder. + * @return a capability reference recorder + */ + CapabilityReferenceRecorder build(); } - /** - * Creates a new reference between the specified capability and the specified requirement. - * Parent reference name is taken from the {@link PathElement} returned by the specified resolver - * @param capability the capability referencing the specified requirement - * @param requirement the requirement of the specified capability - * @param grandparentPathResolver resolver of the path containing the first segment of the requirement name - * @param parentPathResolver resolver of the path containing the second segment of the requirement name - */ - static CapabilityReferenceRecorder of(RuntimeCapability capability, TernaryServiceDescriptor requirement, Function grandparentPathResolver, Function parentPathResolver) { - return new CapabilityServiceDescriptorReferenceRecorder<>(capability, requirement, List.of(grandparentPathResolver, parentPathResolver)); + interface ParentAttributeProvider { + /** + * Specifies the attribute used to resolves the parent segment of the requirement. + * @param attribute the attribute used to resolve the parent segment of the requirement. + * @return a reference to this builder + */ + Builder withParentAttribute(AttributeDefinition attribute); } - /** - * Creates a new reference between the specified capability and the specified requirement - * Parent reference name is taken from the value of the specified {@link AttributeDefinition}. - * @param capability the capability referencing the specified requirement - * @param requirement the requirement of the specified capability - * @param grandparentAttribute the attribute from which the first segment of the requirement name is derived - * @param parentAttribute the attribute from which the second segment of the requirement name is derived - */ - static CapabilityReferenceRecorder of(RuntimeCapability capability, TernaryServiceDescriptor requirement, AttributeDefinition grandparentAttribute, AttributeDefinition parentAttribute) { - return new CapabilityServiceDescriptorReferenceRecorder<>(capability, requirement, grandparentAttribute, parentAttribute); + interface BinaryBuilder extends ParentAttributeProvider, Builder { + /** + * Overrides the default path resolver for the parent segment of the requirement. + * @param a path resolver + * @return a reference to this builder + */ + Builder withParentPathResolver(Function resolver); } - /** - * Creates a new reference between the specified capability and the specified requirement. - * Parent reference name is taken from the {@link PathElement} associated with the resource with which this attribute is referenced. - * @param capability the capability referencing the specified requirement - * @param requirement the requirement of the specified capability - */ - static CapabilityReferenceRecorder of(RuntimeCapability capability, QuaternaryServiceDescriptor requirement) { - return of(capability, requirement, CapabilityServiceDescriptorReferenceRecorder.GRANDPARENT_PATH, CapabilityServiceDescriptorReferenceRecorder.PARENT_PATH, CapabilityServiceDescriptorReferenceRecorder.CHILD_PATH); + interface GrandparentAttributeProvider { + /** + * Specifies the attribute used to resolves the grandparent segment of the requirement. + * @param attribute the attribute used to resolve the parent segment of the requirement. + * @return a reference to this builder + */ + ParentAttributeProvider withGrandparentAttribute(AttributeDefinition attribute); } - /** - * Creates a new reference between the specified capability and the specified requirement. - * Parent reference name is taken from the {@link PathElement} returned by the specified resolver - * @param capability the capability referencing the specified requirement - * @param requirement the requirement of the specified capability - * @param greatGrandparentPathResolver resolver of the path containing the first segment of the requirement name - * @param grandparentPathResolver resolver of the path containing the second segment of the requirement name - * @param parentPathResolver resolver of the path containing the third segment of the requirement name - */ - static CapabilityReferenceRecorder of(RuntimeCapability capability, QuaternaryServiceDescriptor requirement, Function greatGrandparentPathResolver, Function grandparentPathResolver, Function parentPathResolver) { - return new CapabilityServiceDescriptorReferenceRecorder<>(capability, requirement, List.of(greatGrandparentPathResolver, grandparentPathResolver, parentPathResolver)); + interface TernaryBuilder extends BinaryBuilder, GrandparentAttributeProvider { + /** + * Overrides the default path resolver for the grandparent segment of the requirement. + * @param a path resolver + * @return a reference to this builder + */ + BinaryBuilder withGrandparentPathResolver(Function resolver); } - /** - * Creates a new reference between the specified capability and the specified requirement - * Parent reference name is taken from the value of the specified {@link AttributeDefinition}. - * @param capability the capability referencing the specified requirement - * @param requirement the requirement of the specified capability - * @param greatGrandparentAttribute the attribute from which the first segment of the requirement name is derived - * @param grandparentAttribute the attribute from which the second segment of the requirement name is derived - * @param parentAttribute the attribute from which the third segment of the requirement name is derived - */ - static CapabilityReferenceRecorder of(RuntimeCapability capability, QuaternaryServiceDescriptor requirement, AttributeDefinition greatGrandparentAttribute, AttributeDefinition grandparentAttribute, AttributeDefinition parentAttribute) { - return new CapabilityServiceDescriptorReferenceRecorder<>(capability, requirement, greatGrandparentAttribute, grandparentAttribute, parentAttribute); + interface GreatGrandparentAttributeProvider { + /** + * Specifies the attribute used to resolves the great-grandparent segment of the requirement. + * @param attribute the attribute used to resolve the parent segment of the requirement. + * @return a reference to this builder + */ + GrandparentAttributeProvider withGreatGrandparentAttribute(AttributeDefinition attribute); } - abstract class AbstractCapabilityServiceDescriptorReferenceRecorder implements CapabilityReferenceRecorder { + interface QuaternaryBuilder extends TernaryBuilder, GreatGrandparentAttributeProvider { + /** + * Overrides the default path resolver for the great-grandparent segment of the requirement. + * @param a path resolver + * @return a reference to this builder + */ + TernaryBuilder withGreatGrandparentPathResolver(Function resolver); + } + + class DefaultBuilder implements QuaternaryBuilder { + private static final Function CHILD_PATH = PathAddress::getLastElement; + private static final Function PARENT_PATH = CHILD_PATH.compose(PathAddress::getParent); + private static final Function GRANDPARENT_PATH = PARENT_PATH.compose(PathAddress::getParent); + private static final BiFunction CHILD_REQUIREMENT_NAME_SEGMENT_RESOLVER = (context, value) -> value; + private static final BiFunction CHILD_REQUIREMENT_PATTERN_SEGMENT_RESOLVER = (address, name) -> name; private final RuntimeCapability capability; private final ServiceDescriptor requirement; + private final List> requirementNameSegmentResolvers; + private final List> requirementPatternSegmentResolvers; + private final int greatGrandparentIndex; + private final int grandparentIndex; + private final int parentIndex; - AbstractCapabilityServiceDescriptorReferenceRecorder(RuntimeCapability capability, ServiceDescriptor requirement) { + DefaultBuilder(RuntimeCapability capability, ServiceDescriptor requirement, List> defaultResolvers) { this.capability = capability; this.requirement = requirement; + this.parentIndex = defaultResolvers.size() - 1; + this.grandparentIndex = this.parentIndex - 1; + this.greatGrandparentIndex = this.grandparentIndex - 1; + this.requirementNameSegmentResolvers = new ArrayList<>(defaultResolvers.size() + 1); + this.requirementPatternSegmentResolvers = new ArrayList<>(defaultResolvers.size() + 1); + for (Function resolver : defaultResolvers) { + this.requirementNameSegmentResolvers.add(createRequirementNameSegmentResolver(resolver)); + this.requirementPatternSegmentResolvers.add(createRequirementPatternSegmentResolver(resolver)); + } + this.requirementNameSegmentResolvers.add(CHILD_REQUIREMENT_NAME_SEGMENT_RESOLVER); + this.requirementPatternSegmentResolvers.add(CHILD_REQUIREMENT_PATTERN_SEGMENT_RESOLVER); } @Override - public RuntimeCapability getDependent() { - return this.capability; + public GrandparentAttributeProvider withGreatGrandparentAttribute(AttributeDefinition attribute) { + this.setAttribute(this.greatGrandparentIndex, attribute); + return this; } @Override - public ServiceDescriptor getRequirement() { - return this.requirement; + public ParentAttributeProvider withGrandparentAttribute(AttributeDefinition attribute) { + this.setAttribute(this.grandparentIndex, attribute); + return this; } - String resolveDependentName(OperationContext context) { - return this.getDependent().fromBaseCapability(context.getCurrentAddress()).getName(); + @Override + public Builder withParentAttribute(AttributeDefinition attribute) { + this.setAttribute(this.parentIndex, attribute); + return this; } @Override - public int hashCode() { - return this.capability.getName().hashCode(); + public TernaryBuilder withGreatGrandparentPathResolver(Function resolver) { + this.setPathResolver(this.greatGrandparentIndex, resolver); + return this; } @Override - public boolean equals(Object object) { - return (object instanceof CapabilityReferenceRecorder) ? this.getDependent().equals(((CapabilityReferenceRecorder) object).getDependent()) : false; + public BinaryBuilder withGrandparentPathResolver(Function resolver) { + this.setPathResolver(this.grandparentIndex, resolver); + return this; } - } - class CapabilityServiceDescriptorReferenceRecorder extends AbstractCapabilityServiceDescriptorReferenceRecorder { - private static final Function CHILD_PATH = PathAddress::getLastElement; - private static final Function PARENT_PATH = CHILD_PATH.compose(PathAddress::getParent); - private static final Function GRANDPARENT_PATH = PARENT_PATH.compose(PathAddress::getParent); + @Override + public Builder withParentPathResolver(Function resolver) { + this.setPathResolver(this.parentIndex, resolver); + return this; + } - private final BiFunction requirementNameComposer; - private final BiFunction requirementPatternComposer; + private void setAttribute(int index, AttributeDefinition attribute) { + this.requirementNameSegmentResolvers.set(index, createRequirementNameSegmentResolver(attribute)); + this.requirementPatternSegmentResolvers.set(index, createRequirementPatternSegmentResolver(attribute)); + } - CapabilityServiceDescriptorReferenceRecorder(RuntimeCapability capability, ServiceDescriptor requirement, List> parentPathResolvers) { - super(capability, requirement); - this.requirementNameComposer = new BiFunction<>() { + private void setPathResolver(int index, Function resolver) { + this.requirementNameSegmentResolvers.set(index, createRequirementNameSegmentResolver(resolver)); + this.requirementPatternSegmentResolvers.set(index, createRequirementPatternSegmentResolver(resolver)); + } + + private static BiFunction createRequirementNameSegmentResolver(AttributeDefinition attribute) { + return new BiFunction<>() { @Override - public String[] apply(OperationContext context, String value) { - PathAddress address = context.getCurrentAddress(); - String[] result = new String[parentPathResolvers.size() + 1]; - ListIterator> iterator = parentPathResolvers.listIterator(); - while (iterator.hasNext()) { - result[iterator.nextIndex()] = iterator.next().apply(address).getValue(); - } - result[parentPathResolvers.size()] = value; - return result; + public String apply(OperationContext context, String value) { + ModelNode model = context.readResource(PathAddress.EMPTY_ADDRESS, false).getModel(); + return model.get(attribute.getName()).asString(); } }; - this.requirementPatternComposer = new BiFunction<>() { + } + + private static BiFunction createRequirementNameSegmentResolver(Function pathResolver) { + return new BiFunction<>() { @Override - public String[] apply(PathAddress address, String name) { - String[] result = new String[parentPathResolvers.size() + 1]; - ListIterator> iterator = parentPathResolvers.listIterator(); - while (iterator.hasNext()) { - result[iterator.nextIndex()] = iterator.next().apply(address).getKey(); - } - result[parentPathResolvers.size()] = name; - return result; + public String apply(OperationContext context, String value) { + return pathResolver.apply(context.getCurrentAddress()).getValue(); } }; } - CapabilityServiceDescriptorReferenceRecorder(RuntimeCapability capability, ServiceDescriptor requirement, AttributeDefinition... attributes) { - super(capability, requirement); - this.requirementNameComposer = new BiFunction<>() { + private static BiFunction createRequirementPatternSegmentResolver(AttributeDefinition attribute) { + return new BiFunction<>() { @Override - public String[] apply(OperationContext context, String value) { - String[] result = new String[attributes.length + 1]; - if (attributes.length > 0) { - ModelNode model = context.readResource(PathAddress.EMPTY_ADDRESS, false).getModel(); - for (int i = 0; i < attributes.length; ++i) { - result[i] = model.get(attributes[i].getName()).asString(); - } - } - result[attributes.length] = value; - return result; + public String apply(PathAddress address, String name) { + return attribute.getName(); } }; - this.requirementPatternComposer = new BiFunction<>() { + } + + private static BiFunction createRequirementPatternSegmentResolver(Function pathResolver) { + return new BiFunction<>() { @Override - public String[] apply(PathAddress address, String name) { - String[] result = new String[attributes.length + 1]; - for (int i = 0; i < attributes.length; ++i) { - result[i] = attributes[i].getName(); - } - result[attributes.length] = name; - return result; + public String apply(PathAddress address, String name) { + return pathResolver.apply(address).getKey(); } }; } + @Override + public CapabilityReferenceRecorder build() { + return new CapabilityServiceDescriptorReferenceRecorder<>(this.capability, this.requirement, this.requirementNameSegmentResolvers, this.requirementPatternSegmentResolvers); + } + } + + abstract class AbstractCapabilityServiceDescriptorReferenceRecorder implements CapabilityReferenceRecorder { + + private final RuntimeCapability capability; + private final ServiceDescriptor requirement; + + AbstractCapabilityServiceDescriptorReferenceRecorder(RuntimeCapability capability, ServiceDescriptor requirement) { + this.capability = capability; + this.requirement = requirement; + } + + @Override + public RuntimeCapability getDependent() { + return this.capability; + } + + @Override + public ServiceDescriptor getRequirement() { + return this.requirement; + } + + String resolveDependentName(OperationContext context) { + return this.capability.isDynamicallyNamed() ? this.capability.getDynamicName(context.getCurrentAddress()) : this.capability.getName(); + } + + @Override + public int hashCode() { + return this.capability.getName().hashCode(); + } + + @Override + public boolean equals(Object object) { + return (object instanceof CapabilityReferenceRecorder) ? this.getDependent().equals(((CapabilityReferenceRecorder) object).getDependent()) : false; + } + } + + class CapabilityServiceDescriptorReferenceRecorder extends AbstractCapabilityServiceDescriptorReferenceRecorder { + + private final List> requirementNameSegmentResolvers; + private final List> requirementPatternSegmentResolvers; + + CapabilityServiceDescriptorReferenceRecorder(RuntimeCapability capability, ServiceDescriptor requirement, List> requirementNameSegmentResolvers, List> requirementPatternSegmentResolvers) { + super(capability, requirement); + this.requirementNameSegmentResolvers = List.copyOf(requirementNameSegmentResolvers); + this.requirementPatternSegmentResolvers = List.copyOf(requirementPatternSegmentResolvers); + } + @Override public void addCapabilityRequirements(OperationContext context, Resource resource, String attributeName, String... values) { String dependentName = this.resolveDependentName(context); @@ -279,18 +335,28 @@ public void removeCapabilityRequirements(OperationContext context, Resource reso for (String value : values) { // We did not register a requirement if undefined if (value != null) { - context.deregisterCapabilityRequirement(this.resolveRequirementName(context, value), dependentName); + context.deregisterCapabilityRequirement(this.resolveRequirementName(context, value), dependentName, attributeName); } } } private String resolveRequirementName(OperationContext context, String value) { - return RuntimeCapability.buildDynamicCapabilityName(this.getBaseRequirementName(), this.requirementNameComposer.apply(context, value)); + String[] segments = new String[this.requirementNameSegmentResolvers.size()]; + ListIterator> resolvers = this.requirementNameSegmentResolvers.listIterator(); + while (resolvers.hasNext()) { + segments[resolvers.nextIndex()] = resolvers.next().apply(context, value); + } + return RuntimeCapability.buildDynamicCapabilityName(this.getBaseRequirementName(), segments); } @Override public String[] getRequirementPatternSegments(String name, PathAddress address) { - return this.requirementPatternComposer.apply(address, name); + String[] segments = new String[this.requirementPatternSegmentResolvers.size()]; + ListIterator> resolvers = this.requirementPatternSegmentResolvers.listIterator(); + while (resolvers.hasNext()) { + segments[resolvers.nextIndex()] = resolvers.next().apply(address, name); + } + return segments; } } } diff --git a/subsystem/src/main/java/org/wildfly/subsystem/resource/capability/ResourceCapabilityReferenceRecorder.java b/subsystem/src/main/java/org/wildfly/subsystem/resource/capability/ResourceCapabilityReferenceRecorder.java index 6210c7f0383..c8aaaa8a800 100644 --- a/subsystem/src/main/java/org/wildfly/subsystem/resource/capability/ResourceCapabilityReferenceRecorder.java +++ b/subsystem/src/main/java/org/wildfly/subsystem/resource/capability/ResourceCapabilityReferenceRecorder.java @@ -13,6 +13,7 @@ import org.jboss.as.controller.OperationFailedException; import org.jboss.as.controller.PathAddress; import org.jboss.as.controller.capability.BinaryCapabilityNameResolver; +import org.jboss.as.controller.capability.QuaternaryCapabilityNameResolver; import org.jboss.as.controller.capability.RuntimeCapability; import org.jboss.as.controller.capability.TernaryCapabilityNameResolver; import org.jboss.as.controller.capability.UnaryCapabilityNameResolver; @@ -20,6 +21,7 @@ import org.jboss.dmr.ModelNode; import org.wildfly.service.descriptor.BinaryServiceDescriptor; import org.wildfly.service.descriptor.NullaryServiceDescriptor; +import org.wildfly.service.descriptor.QuaternaryServiceDescriptor; import org.wildfly.service.descriptor.ServiceDescriptor; import org.wildfly.service.descriptor.TernaryServiceDescriptor; import org.wildfly.service.descriptor.UnaryServiceDescriptor; @@ -61,62 +63,102 @@ default void removeCapabilityRequirements(OperationContext context, Resource res } /** - * Creates a new reference between the specified capability and the specified requirement. + * Creates a builder for a new reference between the specified capability and the specified requirement. * @param capability the capability referencing the specified requirement * @param requirement the requirement of the specified capability */ static Builder builder(RuntimeCapability capability, NullaryServiceDescriptor requirement) { - return new Builder<>(capability, requirement, ResourceCapabilityServiceDescriptorReference.EMPTY_RESOLVER); + return new DefaultBuilder<>(capability, requirement, ResourceCapabilityServiceDescriptorReference.EMPTY_RESOLVER); } /** - * Creates a new reference between the specified capability and the specified requirement. + * Creates a builder for a new reference between the specified capability and the specified requirement. + * By default, the requirement name will resolve against the path of the current resource. + * @param capability the capability referencing the specified requirement + * @param requirement the requirement of the specified capability + */ + static NaryBuilder builder(RuntimeCapability capability, UnaryServiceDescriptor requirement) { + return new DefaultBuilder<>(capability, requirement, UnaryCapabilityNameResolver.DEFAULT); + } + + /** + * Creates a builder for a new reference between the specified capability and the specified requirement. + * By default, the requirement name will resolve against the paths of the parent and current resources, respectively. * @param capability the capability referencing the specified requirement * @param requirement the requirement of the specified capability * @param requirementNameResolver function for resolving the dynamic components of the requirement name */ - static Builder builder(RuntimeCapability capability, UnaryServiceDescriptor requirement, UnaryCapabilityNameResolver requirementNameResolver) { - return new Builder<>(capability, requirement, requirementNameResolver); + static NaryBuilder builder(RuntimeCapability capability, BinaryServiceDescriptor requirement) { + return new DefaultBuilder<>(capability, requirement, BinaryCapabilityNameResolver.PARENT_CHILD); } /** - * Creates a new reference between the specified capability and the specified requirement. + * Creates a builder for a new reference between the specified capability and the specified requirement. + * By default, the requirement name will resolve against the paths of the grandparent, parent, and current resources, respectively. * @param capability the capability referencing the specified requirement * @param requirement the requirement of the specified capability * @param requirementNameResolver function for resolving the dynamic components of the requirement name */ - static Builder builder(RuntimeCapability capability, BinaryServiceDescriptor requirement, BinaryCapabilityNameResolver requirementNameResolver) { - return new Builder<>(capability, requirement, requirementNameResolver); + static NaryBuilder builder(RuntimeCapability capability, TernaryServiceDescriptor requirement) { + return new DefaultBuilder<>(capability, requirement, TernaryCapabilityNameResolver.GRANDPARENT_PARENT_CHILD); } /** - * Creates a new reference between the specified capability and the specified requirement. + * Creates a builder for a new reference between the specified capability and the specified requirement. + * By default, the requirement name will resolve against the paths of the great-grandparent, grandparent, parent, and current resources, respectively. * @param capability the capability referencing the specified requirement * @param requirement the requirement of the specified capability * @param requirementNameResolver function for resolving the dynamic components of the requirement name */ - static Builder builder(RuntimeCapability capability, TernaryServiceDescriptor requirement, TernaryCapabilityNameResolver requirementNameResolver) { - return new Builder<>(capability, requirement, requirementNameResolver); + static NaryBuilder builder(RuntimeCapability capability, QuaternaryServiceDescriptor requirement) { + return new DefaultBuilder<>(capability, requirement, QuaternaryCapabilityNameResolver.GREATGRANDPARENT_GRANDPARENT_PARENT_CHILD); + } + + interface Builder { + /** + * Only reference the provided capability if value of the specified attribute complies with the specified predicate. + * @param attribute an attribute of the resource to use for conditional registration + * @param predicate conditionally determines whether to require this capability, depending on the resolve value of the specified attribute + * @return a reference to this builder + */ + Builder when(AttributeDefinition attribute, Predicate predicate); + + /** + * Builds the configured capability reference recorder. + * @return a capability reference recorder + */ + org.wildfly.subsystem.resource.capability.ResourceCapabilityReferenceRecorder build(); } - static class Builder { + interface NaryBuilder extends Builder { + /** + * Overrides the default requirement name resolver. + * @param requirementNameResolver a capability name resolver + * @return a reference to this builder + */ + Builder withRequirementNameResolver(Function requirementNameResolver); + } + + static class DefaultBuilder implements NaryBuilder { private final RuntimeCapability capability; private final ServiceDescriptor requirement; - private final Function requirementNameResolver; + private Function requirementNameResolver; private BiPredicate predicate = ResourceCapabilityServiceDescriptorReference.ALWAYS; - Builder(RuntimeCapability capability, ServiceDescriptor requirement, Function requirementNameResolver) { + DefaultBuilder(RuntimeCapability capability, ServiceDescriptor requirement, Function defaultRequirementNameResolver) { this.capability = capability; this.requirement = requirement; + this.requirementNameResolver = defaultRequirementNameResolver; + } + + @Override + public Builder withRequirementNameResolver(Function requirementNameResolver) { this.requirementNameResolver = requirementNameResolver; + return this; } - /** - * Only reference the provided capability if value of the specified attribute complies with the specified predicate. - * @param attribute an attribute of the resource to use for conditional registration - * @param predicate conditionally determines whether to require this capability, depending on the resolve value of the specified attribute - */ + @Override public Builder when(AttributeDefinition attribute, Predicate predicate) { this.predicate = new BiPredicate<>() { @Override @@ -133,10 +175,7 @@ public boolean test(OperationContext context, Resource resource) { return this; } - /** - * Builds the configured capability reference recorder. - * @return a capability reference recorder - */ + @Override public org.wildfly.subsystem.resource.capability.ResourceCapabilityReferenceRecorder build() { return new ResourceCapabilityServiceDescriptorReference<>(this.capability, this.requirement, this.requirementNameResolver, this.predicate); } @@ -194,23 +233,8 @@ public void removeCapabilityRequirements(OperationContext context, Resource reso } private String resolveRequirementName(OperationContext context) { - String[] parts = this.getRequirementNameResolver().apply(context.getCurrentAddress()); - return (parts.length > 0) ? RuntimeCapability.buildDynamicCapabilityName(this.getBaseRequirementName(), parts) : this.getBaseRequirementName(); - } - - static BiPredicate attributePredicate(AttributeDefinition attribute, Predicate predicate) { - return new BiPredicate<>() { - @Override - public boolean test(OperationContext context, Resource resource) { - try { - return predicate.test(attribute.resolveModelAttribute(context, resource.getModel())); - } catch (OperationFailedException e) { - // OFE would be due to an expression that can't be resolved right now (OperationContext.Stage.MODEL). - // Very unlikely an expression is used and that it uses a resolution source not available in MODEL. - return true; - } - } - }; + String[] segments = this.getRequirementNameResolver().apply(context.getCurrentAddress()); + return (segments.length > 0) ? RuntimeCapability.buildDynamicCapabilityName(this.getBaseRequirementName(), segments) : this.getBaseRequirementName(); } } } diff --git a/subsystem/src/test/java/org/wildfly/subsystem/resource/capabilty/CapabilityReferenceRecorderTestCase.java b/subsystem/src/test/java/org/wildfly/subsystem/resource/capabilty/CapabilityReferenceRecorderTestCase.java new file mode 100644 index 00000000000..ca643eea532 --- /dev/null +++ b/subsystem/src/test/java/org/wildfly/subsystem/resource/capabilty/CapabilityReferenceRecorderTestCase.java @@ -0,0 +1,342 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.subsystem.resource.capabilty; + +import static org.mockito.Mockito.*; + +import java.util.List; + +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.PathAddress; +import org.jboss.as.controller.PathElement; +import org.jboss.as.controller.SimpleAttributeDefinitionBuilder; +import org.jboss.as.controller.capability.RuntimeCapability; +import org.jboss.as.controller.registry.Resource; +import org.jboss.dmr.ModelNode; +import org.jboss.dmr.ModelType; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.wildfly.service.descriptor.BinaryServiceDescriptor; +import org.wildfly.service.descriptor.NullaryServiceDescriptor; +import org.wildfly.service.descriptor.TernaryServiceDescriptor; +import org.wildfly.service.descriptor.UnaryServiceDescriptor; +import org.wildfly.subsystem.resource.capability.CapabilityReferenceRecorder; + +/** + * Unit test for {@link CapabilityReferenceRecorder}. + */ +public class CapabilityReferenceRecorderTestCase { + + @Test + public void testUnary() { + String attributeName = "attribute"; + NullaryServiceDescriptor descriptor = NullaryServiceDescriptor.of("capability", Object.class); + RuntimeCapability capability = RuntimeCapability.Builder.of(descriptor).build(); + UnaryServiceDescriptor requirement = UnaryServiceDescriptor.of("requirement", Object.class); + CapabilityReferenceRecorder recorder = CapabilityReferenceRecorder.builder(capability, requirement).build(); + + Assert.assertSame(capability, recorder.getDependent()); + Assert.assertEquals(capability.getName(), recorder.getBaseDependentName()); + Assert.assertSame(requirement, recorder.getRequirement()); + Assert.assertEquals(requirement.getName(), recorder.getBaseRequirementName()); + + PathAddress address = PathAddress.pathAddress(PathElement.pathElement("subsystem", "test"), PathElement.pathElement("component", "foo")); + OperationContext context = mock(OperationContext.class); + Resource resource = mock(Resource.class); + + doReturn(address).when(context).getCurrentAddress(); + + Assert.assertArrayEquals(new String[] { attributeName }, recorder.getRequirementPatternSegments(attributeName, address)); + + ArgumentCaptor capturedRequirement = ArgumentCaptor.forClass(String.class); + ArgumentCaptor capturedDependent = ArgumentCaptor.forClass(String.class); + ArgumentCaptor capturedAttribute = ArgumentCaptor.forClass(String.class); + + doNothing().when(context).registerAdditionalCapabilityRequirement(capturedRequirement.capture(), capturedDependent.capture(), capturedAttribute.capture()); + + recorder.addCapabilityRequirements(context, resource, attributeName, "bar"); + + List requirements = capturedRequirement.getAllValues(); + List dependents = capturedDependent.getAllValues(); + List attributes = capturedAttribute.getAllValues(); + + Assert.assertEquals(1, requirements.size()); + Assert.assertEquals(1, dependents.size()); + Assert.assertEquals(1, attributes.size()); + + Assert.assertEquals("requirement.bar", requirements.get(0)); + Assert.assertSame(capability.getName(), dependents.get(0)); + Assert.assertSame(attributeName, attributes.get(0)); + + doNothing().when(context).deregisterCapabilityRequirement(capturedRequirement.capture(), capturedDependent.capture(), capturedAttribute.capture()); + + recorder.removeCapabilityRequirements(context, resource, attributeName, "bar"); + + requirements = capturedRequirement.getAllValues(); + dependents = capturedDependent.getAllValues(); + attributes = capturedAttribute.getAllValues(); + + Assert.assertEquals(2, requirements.size()); + Assert.assertEquals(2, dependents.size()); + Assert.assertEquals(2, attributes.size()); + + Assert.assertEquals("requirement.bar", requirements.get(1)); + Assert.assertSame(capability.getName(), dependents.get(1)); + Assert.assertSame(attributeName, attributes.get(1)); + } + + @Test + public void testUnaryDynamic() { + String attributeName = "attribute"; + UnaryServiceDescriptor descriptor = UnaryServiceDescriptor.of("capability", Object.class); + RuntimeCapability capability = RuntimeCapability.Builder.of(descriptor).build(); + UnaryServiceDescriptor requirement = UnaryServiceDescriptor.of("requirement", Object.class); + CapabilityReferenceRecorder recorder = CapabilityReferenceRecorder.builder(capability, requirement).build(); + + Assert.assertSame(capability, recorder.getDependent()); + Assert.assertEquals(capability.getName(), recorder.getBaseDependentName()); + Assert.assertSame(requirement, recorder.getRequirement()); + Assert.assertEquals(requirement.getName(), recorder.getBaseRequirementName()); + + PathAddress address = PathAddress.pathAddress(PathElement.pathElement("subsystem", "test"), PathElement.pathElement("component", "foo")); + ModelNode model = new ModelNode(); + model.set(attributeName, "bar"); + OperationContext context = mock(OperationContext.class); + Resource resource = mock(Resource.class); + + doReturn(address).when(context).getCurrentAddress(); + + Assert.assertArrayEquals(new String[] { attributeName }, recorder.getRequirementPatternSegments(attributeName, address)); + + ArgumentCaptor capturedRequirement = ArgumentCaptor.forClass(String.class); + ArgumentCaptor capturedDependent = ArgumentCaptor.forClass(String.class); + ArgumentCaptor capturedAttribute = ArgumentCaptor.forClass(String.class); + + doNothing().when(context).registerAdditionalCapabilityRequirement(capturedRequirement.capture(), capturedDependent.capture(), capturedAttribute.capture()); + + recorder.addCapabilityRequirements(context, resource, attributeName, "bar"); + + List requirements = capturedRequirement.getAllValues(); + List dependents = capturedDependent.getAllValues(); + List attributes = capturedAttribute.getAllValues(); + + Assert.assertEquals(1, requirements.size()); + Assert.assertEquals(1, dependents.size()); + Assert.assertEquals(1, attributes.size()); + + Assert.assertEquals("requirement.bar", requirements.get(0)); + Assert.assertEquals("capability.foo", dependents.get(0)); + Assert.assertSame(attributeName, attributes.get(0)); + + doNothing().when(context).deregisterCapabilityRequirement(capturedRequirement.capture(), capturedDependent.capture(), capturedAttribute.capture()); + + recorder.removeCapabilityRequirements(context, resource, attributeName, "bar"); + + requirements = capturedRequirement.getAllValues(); + dependents = capturedDependent.getAllValues(); + attributes = capturedAttribute.getAllValues(); + + Assert.assertEquals(2, requirements.size()); + Assert.assertEquals(2, dependents.size()); + Assert.assertEquals(2, attributes.size()); + + Assert.assertEquals("requirement.bar", requirements.get(1)); + Assert.assertEquals("capability.foo", dependents.get(1)); + Assert.assertSame(attributeName, attributes.get(1)); + } + + @Test + public void testBinaryWithParentAttribute() { + String parentAttributeName = "parent-attribute"; + AttributeDefinition parentAttribute = SimpleAttributeDefinitionBuilder.create(parentAttributeName, ModelType.STRING).build(); + String attributeName = "attribute"; + NullaryServiceDescriptor descriptor = NullaryServiceDescriptor.of("capability", Object.class); + RuntimeCapability capability = RuntimeCapability.Builder.of(descriptor).build(); + BinaryServiceDescriptor requirement = BinaryServiceDescriptor.of("requirement", Object.class); + CapabilityReferenceRecorder recorder = CapabilityReferenceRecorder.builder(capability, requirement).withParentAttribute(parentAttribute).build(); + + Assert.assertSame(capability, recorder.getDependent()); + Assert.assertEquals(capability.getName(), recorder.getBaseDependentName()); + Assert.assertSame(requirement, recorder.getRequirement()); + Assert.assertEquals(requirement.getName(), recorder.getBaseRequirementName()); + + PathAddress address = PathAddress.pathAddress(PathElement.pathElement("subsystem", "test"), PathElement.pathElement("component", "foo")); + ModelNode model = new ModelNode(); + model.set(attributeName, "bar"); + model.set(parentAttributeName, "baz"); + OperationContext context = mock(OperationContext.class); + Resource resource = mock(Resource.class); + + doReturn(address).when(context).getCurrentAddress(); + doReturn(resource).when(context).readResource(PathAddress.EMPTY_ADDRESS, false); + doReturn(model).when(resource).getModel(); + + Assert.assertArrayEquals(new String[] { parentAttributeName, attributeName }, recorder.getRequirementPatternSegments(attributeName, address)); + + ArgumentCaptor capturedRequirement = ArgumentCaptor.forClass(String.class); + ArgumentCaptor capturedDependent = ArgumentCaptor.forClass(String.class); + ArgumentCaptor capturedAttribute = ArgumentCaptor.forClass(String.class); + + doNothing().when(context).registerAdditionalCapabilityRequirement(capturedRequirement.capture(), capturedDependent.capture(), capturedAttribute.capture()); + + recorder.addCapabilityRequirements(context, resource, attributeName, "bar"); + + List requirements = capturedRequirement.getAllValues(); + List dependents = capturedDependent.getAllValues(); + List attributes = capturedAttribute.getAllValues(); + + Assert.assertEquals(1, requirements.size()); + Assert.assertEquals(1, dependents.size()); + Assert.assertEquals(1, attributes.size()); + + Assert.assertEquals("requirement.baz.bar", requirements.get(0)); + Assert.assertSame(capability.getName(), dependents.get(0)); + Assert.assertSame(attributeName, attributes.get(0)); + + doNothing().when(context).deregisterCapabilityRequirement(capturedRequirement.capture(), capturedDependent.capture(), capturedAttribute.capture()); + + recorder.removeCapabilityRequirements(context, resource, attributeName, "bar"); + + requirements = capturedRequirement.getAllValues(); + dependents = capturedDependent.getAllValues(); + attributes = capturedAttribute.getAllValues(); + + Assert.assertEquals(2, requirements.size()); + Assert.assertEquals(2, dependents.size()); + Assert.assertEquals(2, attributes.size()); + + Assert.assertEquals("requirement.baz.bar", requirements.get(1)); + Assert.assertSame(capability.getName(), dependents.get(1)); + Assert.assertSame(attributeName, attributes.get(1)); + } + + @Test + public void testBinaryWithParentPath() { + String attributeName = "attribute"; + NullaryServiceDescriptor descriptor = NullaryServiceDescriptor.of("capability", Object.class); + RuntimeCapability capability = RuntimeCapability.Builder.of(descriptor).build(); + BinaryServiceDescriptor requirement = BinaryServiceDescriptor.of("requirement", Object.class); + CapabilityReferenceRecorder recorder = CapabilityReferenceRecorder.builder(capability, requirement).build(); + + Assert.assertSame(capability, recorder.getDependent()); + Assert.assertEquals(capability.getName(), recorder.getBaseDependentName()); + Assert.assertSame(requirement, recorder.getRequirement()); + Assert.assertEquals(requirement.getName(), recorder.getBaseRequirementName()); + + PathAddress address = PathAddress.pathAddress(PathElement.pathElement("subsystem", "test"), PathElement.pathElement("component", "foo")); + ModelNode model = new ModelNode(); + model.set(attributeName, "bar"); + OperationContext context = mock(OperationContext.class); + Resource resource = mock(Resource.class); + + doReturn(address).when(context).getCurrentAddress(); + + Assert.assertArrayEquals(new String[] { "component", attributeName }, recorder.getRequirementPatternSegments(attributeName, address)); + + ArgumentCaptor capturedRequirement = ArgumentCaptor.forClass(String.class); + ArgumentCaptor capturedDependent = ArgumentCaptor.forClass(String.class); + ArgumentCaptor capturedAttribute = ArgumentCaptor.forClass(String.class); + + doNothing().when(context).registerAdditionalCapabilityRequirement(capturedRequirement.capture(), capturedDependent.capture(), capturedAttribute.capture()); + + recorder.addCapabilityRequirements(context, resource, attributeName, "bar"); + + List requirements = capturedRequirement.getAllValues(); + List dependents = capturedDependent.getAllValues(); + List attributes = capturedAttribute.getAllValues(); + + Assert.assertEquals(1, requirements.size()); + Assert.assertEquals(1, dependents.size()); + Assert.assertEquals(1, attributes.size()); + + Assert.assertEquals("requirement.foo.bar", requirements.get(0)); + Assert.assertSame(capability.getName(), dependents.get(0)); + Assert.assertSame(attributeName, attributes.get(0)); + + doNothing().when(context).deregisterCapabilityRequirement(capturedRequirement.capture(), capturedDependent.capture(), capturedAttribute.capture()); + + recorder.removeCapabilityRequirements(context, resource, attributeName, "bar"); + + requirements = capturedRequirement.getAllValues(); + dependents = capturedDependent.getAllValues(); + attributes = capturedAttribute.getAllValues(); + + Assert.assertEquals(2, requirements.size()); + Assert.assertEquals(2, dependents.size()); + Assert.assertEquals(2, attributes.size()); + + Assert.assertEquals("requirement.foo.bar", requirements.get(1)); + Assert.assertSame(capability.getName(), dependents.get(1)); + Assert.assertSame(attributeName, attributes.get(1)); + } + + @Test + public void testTernary() { + String parentAttributeName = "parent-attribute"; + AttributeDefinition parentAttribute = SimpleAttributeDefinitionBuilder.create(parentAttributeName, ModelType.STRING).build(); + String attributeName = "attribute"; + NullaryServiceDescriptor descriptor = NullaryServiceDescriptor.of("capability", Object.class); + RuntimeCapability capability = RuntimeCapability.Builder.of(descriptor).build(); + TernaryServiceDescriptor requirement = TernaryServiceDescriptor.of("requirement", Object.class); + CapabilityReferenceRecorder recorder = CapabilityReferenceRecorder.builder(capability, requirement).withGrandparentPathResolver(PathAddress::getLastElement).withParentAttribute(parentAttribute).build(); + + Assert.assertSame(capability, recorder.getDependent()); + Assert.assertEquals(capability.getName(), recorder.getBaseDependentName()); + Assert.assertSame(requirement, recorder.getRequirement()); + Assert.assertEquals(requirement.getName(), recorder.getBaseRequirementName()); + + PathAddress address = PathAddress.pathAddress(PathElement.pathElement("subsystem", "test"), PathElement.pathElement("component", "foo")); + ModelNode model = new ModelNode(); + model.set(attributeName, "bar"); + model.set(parentAttributeName, "baz"); + OperationContext context = mock(OperationContext.class); + Resource resource = mock(Resource.class); + + doReturn(address).when(context).getCurrentAddress(); + doReturn(resource).when(context).readResource(PathAddress.EMPTY_ADDRESS, false); + doReturn(model).when(resource).getModel(); + + Assert.assertArrayEquals(new String[] { "component", parentAttributeName, attributeName }, recorder.getRequirementPatternSegments(attributeName, address)); + + ArgumentCaptor capturedRequirement = ArgumentCaptor.forClass(String.class); + ArgumentCaptor capturedDependent = ArgumentCaptor.forClass(String.class); + ArgumentCaptor capturedAttribute = ArgumentCaptor.forClass(String.class); + + doNothing().when(context).registerAdditionalCapabilityRequirement(capturedRequirement.capture(), capturedDependent.capture(), capturedAttribute.capture()); + + recorder.addCapabilityRequirements(context, resource, attributeName, "bar"); + + List requirements = capturedRequirement.getAllValues(); + List dependents = capturedDependent.getAllValues(); + List attributes = capturedAttribute.getAllValues(); + + Assert.assertEquals(1, requirements.size()); + Assert.assertEquals(1, dependents.size()); + Assert.assertEquals(1, attributes.size()); + + Assert.assertEquals("requirement.foo.baz.bar", requirements.get(0)); + Assert.assertSame(capability.getName(), dependents.get(0)); + Assert.assertSame(attributeName, attributes.get(0)); + + doNothing().when(context).deregisterCapabilityRequirement(capturedRequirement.capture(), capturedDependent.capture(), capturedAttribute.capture()); + + recorder.removeCapabilityRequirements(context, resource, attributeName, "bar"); + + requirements = capturedRequirement.getAllValues(); + dependents = capturedDependent.getAllValues(); + attributes = capturedAttribute.getAllValues(); + + Assert.assertEquals(2, requirements.size()); + Assert.assertEquals(2, dependents.size()); + Assert.assertEquals(2, attributes.size()); + + Assert.assertEquals("requirement.foo.baz.bar", requirements.get(1)); + Assert.assertSame(capability.getName(), dependents.get(1)); + Assert.assertSame(attributeName, attributes.get(1)); + } +}