/*
* JBoss, Home of Professional Open Source.
* Copyright 2016, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.controller;
import java.nio.file.FileSystems;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.HOST;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
import org.jboss.as.controller.capability.Capability;
import org.jboss.as.controller.capability.RuntimeCapability;
import org.jboss.as.controller.capability.registry.CapabilityId;
import org.jboss.as.controller.capability.registry.CapabilityRegistration;
import org.jboss.as.controller.capability.registry.CapabilityResolutionContext;
import org.jboss.as.controller.capability.registry.CapabilityScope;
import org.jboss.as.controller.capability.registry.ImmutableCapabilityRegistry;
import org.jboss.as.controller.capability.registry.PossibleCapabilityRegistry;
import org.jboss.as.controller.capability.registry.RegistrationPoint;
import org.jboss.as.controller.capability.registry.RuntimeCapabilityRegistration;
import org.jboss.as.controller.capability.registry.RuntimeCapabilityRegistry;
import org.jboss.as.controller.capability.registry.RuntimeRequirementRegistration;
import org.jboss.as.controller.logging.ControllerLogger;
import org.jboss.as.controller.registry.ImmutableManagementResourceRegistration;
import org.jboss.as.controller.registry.Resource;
import org.jboss.msc.service.ServiceName;
/**
* Registry of {@link org.jboss.as.controller.capability.AbstractCapability capabilities} available in the system.
*
* @author Brian Stansberry (c) 2014 Red Hat Inc.
* @author Tomaz Cerar (c) 2015 Red Hat Inc.
*/
public final class CapabilityRegistry implements ImmutableCapabilityRegistry, PossibleCapabilityRegistry, RuntimeCapabilityRegistry {
private final Map<CapabilityId, RuntimeCapabilityRegistration> capabilities = new HashMap<>();
private final Map<CapabilityId, Map<String, RuntimeRequirementRegistration>> requirements = new HashMap<>();
private final Map<CapabilityId, Map<String, RuntimeRequirementRegistration>> runtimeOnlyRequirements = new HashMap<>();
private final boolean forServer;
private final Set<CapabilityScope> knownContexts;
private final ResolutionContextImpl resolutionContext = new ResolutionContextImpl();
private final Map<CapabilityId, CapabilityRegistration<?>> possibleCapabilities = new ConcurrentHashMap<>();
private final Set<CapabilityId> reloadCapabilities = new HashSet<>();
private final Set<CapabilityId> restartCapabilities = new HashSet<>();
private final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
private final ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
//holds reference to parent published registry
private final CapabilityRegistry publishedFullRegistry;
private boolean modified = false;
public CapabilityRegistry(boolean forServer) {
this(forServer, null);
}
private CapabilityRegistry(boolean forServer, CapabilityRegistry parent) {//for published view
this.forServer = forServer;
this.knownContexts = forServer ? null : new HashSet<>();
this.publishedFullRegistry = parent;
}
/**
* Creates updateable version of capability registry that on publish pushes all changes to main registry
* this is used to create context local registry that only on completion commits changes to main registry
*
* @return writable registry
*/
CapabilityRegistry createShadowCopy() {
CapabilityRegistry result = new CapabilityRegistry(forServer, this);
readLock.lock();
try {
try {
result.writeLock.lock();
copy(this, result);
} finally {
result.writeLock.unlock();
}
} finally {
readLock.unlock();
}
return result;
}
private static void copyCapabilities(final Map<CapabilityId, RuntimeCapabilityRegistration> source,
final Map<CapabilityId, RuntimeCapabilityRegistration> dest) {
for (Map.Entry<CapabilityId, RuntimeCapabilityRegistration> entry : source.entrySet()) {
dest.put(entry.getKey(), new RuntimeCapabilityRegistration(entry.getValue()));
}
}
private static void copyRequirements(Map<CapabilityId, Map<String, RuntimeRequirementRegistration>> source,
Map<CapabilityId, Map<String, RuntimeRequirementRegistration>> dest) {
for (Map.Entry<CapabilityId, Map<String, RuntimeRequirementRegistration>> entry : source.entrySet()) {
Map<String, RuntimeRequirementRegistration> mapCopy = new HashMap<>();
for (Map.Entry<String, RuntimeRequirementRegistration> innerEntry : entry.getValue().entrySet()) {
mapCopy.put(innerEntry.getKey(), new RuntimeRequirementRegistration(innerEntry.getValue()));
}
dest.put(entry.getKey(), mapCopy);
}
}
/**
* Registers a capability with the system. Any
* {@link org.jboss.as.controller.capability.AbstractCapability#getRequirements() requirements}
* associated with the capability will be recorded as requirements.
*
* @param capabilityRegistration the capability. Cannot be {@code null}
*/
@Override
public void registerCapability(RuntimeCapabilityRegistration capabilityRegistration) {
writeLock.lock();
try {
CapabilityId capabilityId = capabilityRegistration.getCapabilityId();
RegistrationPoint rp = capabilityRegistration.getOldestRegistrationPoint();
RuntimeCapabilityRegistration currentRegistration = capabilities.get(capabilityId);
if (currentRegistration != null) {
// The actual capability must be the same, and we must not already have a registration
// from this resource
if (!Objects.equals(capabilityRegistration.getCapability(), currentRegistration.getCapability())
|| !currentRegistration.addRegistrationPoint(rp)) {
throw ControllerLogger.MGMT_OP_LOGGER.capabilityAlreadyRegisteredInContext(capabilityId.getName(),
capabilityId.getScope().getName());
}
// else it was ok, and we just recorded the additional registration point
} else {
capabilities.put(capabilityId, capabilityRegistration);
}
// Add any hard requirements
for (String req : capabilityRegistration.getCapability().getRequirements()) {
registerRequirement(new RuntimeRequirementRegistration(req, capabilityId.getName(),
capabilityId.getScope(), rp));
}
if (!forServer) {
CapabilityScope capContext = capabilityId.getScope();
knownContexts.add(capContext);
}
modified = true;
} finally {
writeLock.unlock();
}
}
/**
* Registers an additional requirement a capability has beyond what it was aware of when {@code capability}
* was passed to {@link #registerCapability(RuntimeCapabilityRegistration)}. Used for cases
* where a capability optionally depends on another capability, and whether or not that requirement is needed is
* not known when the capability is first registered.
*
*
* @param requirement the requirement
* @throws java.lang.IllegalArgumentException if no matching capability is currently
* {@link #registerCapability(RuntimeCapabilityRegistration) registered} for either {@code required} or {@code dependent}
*/
@Override
public void registerAdditionalCapabilityRequirement(RuntimeRequirementRegistration requirement) {
writeLock.lock();
try {
registerRequirement(requirement);
} finally {
writeLock.unlock();
}
}
/**
* This must be called with the write lock held.
* @param requirement the requirement
*/
private void registerRequirement(RuntimeRequirementRegistration requirement) {
assert writeLock.isHeldByCurrentThread();
CapabilityId dependentId = requirement.getDependentId();
if (!capabilities.containsKey(dependentId)) {
throw ControllerLogger.MGMT_OP_LOGGER.unknownCapabilityInContext(dependentId.getName(),
dependentId.getScope().getName());
}
Map<CapabilityId, Map<String, RuntimeRequirementRegistration>> requirementMap =
requirement.isRuntimeOnly() ? runtimeOnlyRequirements : requirements;
Map<String, RuntimeRequirementRegistration> dependents = requirementMap.get(dependentId);
if (dependents == null) {
dependents = new HashMap<>();
requirementMap.put(dependentId, dependents);
}
RuntimeRequirementRegistration existing = dependents.get(requirement.getRequiredName());
if (existing == null) {
dependents.put(requirement.getRequiredName(), requirement);
} else {
existing.addRegistrationPoint(requirement.getOldestRegistrationPoint());
}
modified = true;
}
/**
* Remove a previously registered requirement for a capability.
*
* @param requirementRegistration the requirement. Cannot be {@code null}
* @see #registerAdditionalCapabilityRequirement(org.jboss.as.controller.capability.registry.RuntimeRequirementRegistration)
*/
@Override
public void removeCapabilityRequirement(RuntimeRequirementRegistration requirementRegistration) {
// We don't know if this got registered as an runtime-only requirement or a hard one
// so clean it from both maps
writeLock.lock();
try {
removeRequirement(requirementRegistration, false);
removeRequirement(requirementRegistration, true);
} finally {
writeLock.unlock();
}
}
/**
* Remove a previously registered capability if all registration points for it have been removed.
*
* @param capabilityName the name of the capability. Cannot be {@code null}
* @param scope the context in which the capability is registered. Cannot be {@code null}
* @param registrationPoint the specific registration point that is being removed
* @return the capability that was removed, or {@code null} if no matching capability was registered or other
* registration points for the capability still exist
*/
@Override
public RuntimeCapabilityRegistration removeCapability(String capabilityName, CapabilityScope scope,
PathAddress registrationPoint) {
writeLock.lock();
try {
CapabilityId capabilityId = new CapabilityId(capabilityName, scope);
RuntimeCapabilityRegistration removed = null;
RuntimeCapabilityRegistration candidate = capabilities.get(capabilityId);
if (candidate != null) {
RegistrationPoint rp = new RegistrationPoint(registrationPoint, null);
if (candidate.removeRegistrationPoint(rp)) {
if (candidate.getRegistrationPointCount() == 0) {
removed = capabilities.remove(capabilityId);
requirements.remove(capabilityId);
runtimeOnlyRequirements.remove(capabilityId);
} else {
// There are still registration points for this capability.
// So just remove the requirements for this registration point
Map<String, RuntimeRequirementRegistration> candidateRequirements = requirements.get(capabilityId);
if (candidateRequirements != null) {
// Iterate over array to avoid ConcurrentModificationException
for (String req : candidateRequirements.keySet().toArray(new String[candidateRequirements.size()])) {
removeRequirement(new RuntimeRequirementRegistration(req, capabilityName, scope, rp), false);
}
}
candidateRequirements = runtimeOnlyRequirements.get(capabilityId);
if (candidateRequirements != null) {
// Iterate over array to avoid ConcurrentModificationException
for (String req : candidateRequirements.keySet().toArray(new String[candidateRequirements.size()])) {
removeRequirement(new RuntimeRequirementRegistration(req, capabilityName, scope, rp), true);
}
}
}
}
}
if (removed != null) {
modified = true;
}
return removed;
} finally {
writeLock.unlock();
}
}
private void removeRequirement(RuntimeRequirementRegistration requirementRegistration, boolean optional) {
assert writeLock.isHeldByCurrentThread();
Map<CapabilityId, Map<String, RuntimeRequirementRegistration>> requirementMap = optional ? runtimeOnlyRequirements : requirements;
Map<String, RuntimeRequirementRegistration> dependents = requirementMap.get(requirementRegistration.getDependentId());
if (dependents != null) {
RuntimeRequirementRegistration rrr = dependents.get(requirementRegistration.getRequiredName());
if (rrr != null) {
rrr.removeRegistrationPoint(requirementRegistration.getOldestRegistrationPoint());
if (rrr.getRegistrationPointCount() == 0) {
dependents.remove(requirementRegistration.getRequiredName());
}
if (dependents.size() == 0) {
requirementMap.remove(requirementRegistration.getDependentId());
}
modified = true;
}
}
}
@Override
public Map<CapabilityId, RuntimeStatus> getRuntimeStatus(PathAddress address, ImmutableManagementResourceRegistration resourceRegistration) {
readLock.lock();
try {
Map<CapabilityId, RuntimeStatus> result;
Set<CapabilityId> ids = getCapabilitiesForAddress(address, resourceRegistration);
int size = ids.size();
if (size == 0) {
result = Collections.emptyMap();
} else {
Set<CapabilityId> examined = new HashSet<>();
if (size == 1) {
CapabilityId id = ids.iterator().next();
result = Collections.singletonMap(id, getCapabilityStatus(id, examined));
} else {
result = new HashMap<>(size);
for (CapabilityId id : ids) {
result.put(id, getCapabilityStatus(id, examined));
}
}
}
return result;
} finally {
readLock.unlock();
}
}
private RuntimeStatus getCapabilityStatus(CapabilityId id, Set<CapabilityId> examined) {
// This is meant for checking runtime stuff, which should only be for servers or
// HC runtime stuff, both of which use CapabilityScope.GLOBAL or HostCapabilityScope. So this assert
// is to check that assumption is valid, as further thought is needed if not (e.g. see WFCORE-1710).
// The id.getScope().getName().equals(HOST) check is a bit of a hack into HostCapabilityScope's
// internals, but oh well.
assert id.getScope().equals(CapabilityScope.GLOBAL) || id.getScope().getName().equals(HOST);
if (restartCapabilities.contains(id)) {
return RuntimeStatus.RESTART_REQUIRED;
}
if (reloadCapabilities.contains(id)) {
return RuntimeStatus.RELOAD_REQUIRED;
}
examined.add(id);
Map<String, RuntimeRequirementRegistration> dependents = requirements.get(id);
return getDependentCapabilityStatus(dependents, id, examined);
}
private RuntimeStatus getDependentCapabilityStatus(Map<String, RuntimeRequirementRegistration> dependents, CapabilityId requiror, Set<CapabilityId> examined) {
RuntimeStatus result = RuntimeStatus.NORMAL;
if (dependents != null) {
for (String dependent : dependents.keySet()) {
CapabilityScope requirorScope = requiror.getScope();
List<CapabilityScope> toCheck = requirorScope == CapabilityScope.GLOBAL
? Collections.singletonList(requirorScope)
: Arrays.asList(requirorScope, CapabilityScope.GLOBAL);
for (CapabilityScope scope : toCheck) {
CapabilityId dependentId = new CapabilityId(dependent, scope);
if (!examined.contains(dependentId)) {
RuntimeStatus status = getCapabilityStatus(dependentId, examined);
if (status == RuntimeStatus.RESTART_REQUIRED) {
return status; // no need to check anything else
} else if (status == RuntimeStatus.RELOAD_REQUIRED) {
result = status;
}
}
}
}
}
return result;
}
@Override
public void capabilityReloadRequired(PathAddress address, ImmutableManagementResourceRegistration resourceRegistration) {
writeLock.lock();
try {
reloadCapabilities.addAll(getCapabilitiesForAddress(address, resourceRegistration));
} finally {
writeLock.unlock();
}
}
@Override
public void capabilityRestartRequired(PathAddress address, ImmutableManagementResourceRegistration resourceRegistration) {
writeLock.lock();
try {
restartCapabilities.addAll(getCapabilitiesForAddress(address, resourceRegistration));
} finally {
writeLock.unlock();
}
}
private Set<CapabilityId> getCapabilitiesForAddress(PathAddress address, ImmutableManagementResourceRegistration resourceRegistration) {
Set<CapabilityId> result = null;
PathAddress curAddress = address;
ImmutableManagementResourceRegistration curReg = resourceRegistration;
while (result == null) {
// Track the names of any incorporating capabilities associated with this address
Set<RuntimeCapability> incorporating = curReg.getIncorporatingCapabilities();
Set<String> incorporatingDynamic = null;
Set<String> incorporatingFull = null;
if (incorporating != null && !incorporating.isEmpty()) {
for (RuntimeCapability rc : incorporating) {
if (rc.isDynamicallyNamed()) {
if (incorporatingDynamic == null) {
incorporatingDynamic = new HashSet<>();
}
incorporatingDynamic.add(rc.getName());
} else {
if (incorporatingFull == null) {
incorporatingFull = new HashSet<>();
}
incorporatingFull.add(rc.getName());
}
}
}
// TODO this is inefficient. But it's only called for post-boot write ops
// when the process is already reload-required
for (Map.Entry<CapabilityId, RuntimeCapabilityRegistration> entry : capabilities.entrySet()) {
boolean checkIncorporating = false;
if (incorporatingFull != null) {
checkIncorporating = incorporatingFull.contains(entry.getKey().getName());
}
if (!checkIncorporating && incorporatingDynamic != null) {
String name = entry.getKey().getName();
int lastDot = name.lastIndexOf('.');
if (lastDot > 0) {
String baseName = name.substring(0, lastDot);
checkIncorporating = incorporatingDynamic.contains(baseName);
}
}
for (RegistrationPoint point : entry.getValue().getRegistrationPoints()) {
PathAddress pointAddress = point.getAddress();
if (curAddress.equals(pointAddress)
|| (checkIncorporating && curAddress.size() > pointAddress.size()
&& pointAddress.equals(curAddress.subAddress(0, pointAddress.size())))) {
if (result == null) {
result = new HashSet<>();
}
result.add(entry.getKey());
break;
}
}
}
if (result == null && incorporating != null) {
// No match, but incorporating != null means the MRR doesn't want us to keep looking higher
result = Collections.emptySet();
}
if (result == null) {
// This address exposed no capability, but it may represent a config chunk for
// a capability exposed by a parent resource, so we need to check parents.
//
// Here we check up to the first child level. The root resource for a process
// will not expose a capability that is configured by children, so it is incorrect
// to check beyond the first level. In the domain mode /host=* tree, we
// only check up to the 2nd child level, because the /host=x level is the root node
// for the HC process.
// We stop navigating up when the current address' final path element is subsystem=X,
// because above that level are kernel resources, and kernel resources do not expose
// capabilities that are configured by subsystem children.
int addrSize = curAddress.size();
if (addrSize > 1 && !SUBSYSTEM.equals(curAddress.getLastElement().getKey())
&& !(addrSize == 2 && HOST.equals(curAddress.getElement(0).getKey()))) {
curAddress = curAddress.getParent();
curReg = curReg.getParent();
// loop continues
} else {
result = Collections.emptySet();
}
}
}
return result;
}
/**
* Registers a capability with the system. Any
* {@link org.jboss.as.controller.capability.AbstractCapability#getRequirements() requirements}
* associated with the capability will be recorded as requirements.
*
* @param capability the capability. Cannot be {@code null}
*/
@Override
public void registerPossibleCapability(Capability capability, PathAddress registrationPoint) {
final CapabilityId capabilityId = new CapabilityId(capability.getName(), CapabilityScope.GLOBAL);
RegistrationPoint point = new RegistrationPoint(registrationPoint, null);
CapabilityRegistration<?> capabilityRegistration = new CapabilityRegistration<>(capability, CapabilityScope.GLOBAL, point);
writeLock.lock();
try {
possibleCapabilities.computeIfPresent(capabilityId, (capabilityId1, currentRegistration) -> {
RegistrationPoint rp = capabilityRegistration.getOldestRegistrationPoint();
// The actual capability must be the same, and we must not already have a registration
// from this resource
if (!Objects.equals(capabilityRegistration.getCapability(), currentRegistration.getCapability())
|| !currentRegistration.addRegistrationPoint(rp)) {
throw ControllerLogger.MGMT_OP_LOGGER.capabilityAlreadyRegisteredInContext(capabilityId.getName(),
capabilityId.getScope().getName());
}
return currentRegistration;
});
possibleCapabilities.putIfAbsent(capabilityId, capabilityRegistration);
modified = true;
} finally {
writeLock.unlock();
}
}
/**
* Remove a previously registered capability if all registration points for it have been removed.
*
* @param capability the capability. Cannot be {@code null}
* @param registrationPoint the specific registration point that is being removed
* @return the capability that was removed, or {@code null} if no matching capability was registered or other
* registration points for the capability still exist
*/
@Override
public CapabilityRegistration<?> removePossibleCapability(Capability capability, PathAddress registrationPoint) {
CapabilityId capabilityId = new CapabilityId(capability.getName(), CapabilityScope.GLOBAL);
CapabilityRegistration<?> removed = null;
writeLock.lock();
try {
CapabilityRegistration<?> candidate = possibleCapabilities.get(capabilityId);
if (candidate != null) {
RegistrationPoint rp = new RegistrationPoint(registrationPoint, null);
if (candidate.removeRegistrationPoint(rp)) {
if (candidate.getRegistrationPointCount() == 0) {
removed = possibleCapabilities.remove(capabilityId);
} else {
removed = candidate;
}
}
}
if (removed != null) {
modified = true;
}
return removed;
} finally {
writeLock.unlock();
}
}
//ImmutableCapabilityRegistry methods
@Override
public boolean hasCapability(String capabilityName, CapabilityScope scope) {
readLock.lock();
try {
return findSatisfactoryCapability(capabilityName, scope, !forServer) != null;
} finally {
readLock.unlock();
}
}
@Override
public <T> T getCapabilityRuntimeAPI(String capabilityName, CapabilityScope scope, Class<T> apiType) {
// Here we can't know the dependent name. So this can only be called when resolution is complete.
assert resolutionContext.resolutionComplete;
readLock.lock();
try {
RuntimeCapabilityRegistration reg = getCapabilityRegistration(capabilityName, scope);
Object api = reg.getCapability().getRuntimeAPI();
if (api == null) {
throw ControllerLogger.MGMT_OP_LOGGER.capabilityDoesNotExposeRuntimeAPI(capabilityName);
}
return apiType.cast(api);
} finally {
readLock.unlock();
}
}
@Override
public Set<CapabilityRegistration<?>> getCapabilities() {
readLock.lock();
try {
return Collections.unmodifiableSet(new TreeSet<>(capabilities.values()));
} finally {
readLock.unlock();
}
}
@Override
public Set<CapabilityRegistration<?>> getPossibleCapabilities() {
readLock.lock();
try {
return Collections.unmodifiableSet(new TreeSet<>(possibleCapabilities.values()));
} finally {
readLock.unlock();
}
}
@Override
public ServiceName getCapabilityServiceName(String capabilityName, CapabilityScope scope, Class<?> serviceType) {
// Here we can't know the dependent name. So this can only be called when resolution is complete.
assert resolutionContext.resolutionComplete;
readLock.lock();
try {
RuntimeCapabilityRegistration reg = getCapabilityRegistration(capabilityName, scope);
RuntimeCapability<?> cap = reg.getCapability();
return cap.getCapabilityServiceName(serviceType);
} finally {
readLock.unlock();
}
}
@Override
public Set<PathAddress> getPossibleProviderPoints(CapabilityId capabilityId) {
Set<PathAddress> result = new LinkedHashSet<>();
readLock.lock();
try {
capabilityId = capabilityId.getScope() == CapabilityScope.GLOBAL ? capabilityId : new CapabilityId(capabilityId.getName(), CapabilityScope.GLOBAL); //possible registry is only in global scope
CapabilityRegistration<?> reg = possibleCapabilities.get(capabilityId);
if (reg != null) {
result.addAll(reg.getRegistrationPoints().stream().map(RegistrationPoint::getAddress).collect(Collectors.toList()));
}
} finally {
readLock.unlock();
}
return result;
}
@Override
public CapabilityRegistration<?> getCapability(CapabilityId capabilityId){
readLock.lock();
try {
CapabilityRegistration<?> reg = capabilities.get(capabilityId);
return reg != null ? new CapabilityRegistration<>(reg) : null;
} finally {
readLock.unlock();
}
}
//end ImmutableCapabilityRegistry methods
/**
* Publish the changes to main registry
*/
void publish() {
assert publishedFullRegistry != null : "Cannot write directly to main registry";
writeLock.lock();
try {
if (!modified) {
return;
}
publishedFullRegistry.writeLock.lock();
try {
publishedFullRegistry.clear(true);
copy(this, publishedFullRegistry);
modified = false;
} finally {
publishedFullRegistry.writeLock.unlock();
}
} finally {
writeLock.unlock();
}
}
/**
* Discard the changes.
*/
void rollback() {
if (publishedFullRegistry == null) {
return;
}
writeLock.lock();
try {
publishedFullRegistry.readLock.lock();
try {
clear(true);
copy(publishedFullRegistry, this);
modified = false;
} finally {
publishedFullRegistry.readLock.unlock();
}
} finally {
writeLock.unlock();
}
}
boolean isModified() {
readLock.lock();
try {
return modified;
} finally {
readLock.unlock();
}
}
private void copy(CapabilityRegistry source, CapabilityRegistry target) {
assert target.writeLock.isHeldByCurrentThread();
copyCapabilities(source.capabilities, target.capabilities);
source.possibleCapabilities.entrySet().stream().forEach(entry -> {
target.possibleCapabilities.put(entry.getKey(), new CapabilityRegistration<>(entry.getValue()));
});
copyRequirements(source.requirements, target.requirements);
copyRequirements(source.runtimeOnlyRequirements, target.runtimeOnlyRequirements);
target.reloadCapabilities.addAll(source.reloadCapabilities);
target.restartCapabilities.addAll(source.restartCapabilities);
if (!forServer) {
target.knownContexts.addAll(source.knownContexts);
}
}
/**
* Clears capability registry
*/
void clear() {
clear(false);
}
private void clear(boolean restartRequired) {
writeLock.lock();
try {
capabilities.clear();
possibleCapabilities.clear();
requirements.clear();
runtimeOnlyRequirements.clear();
reloadCapabilities.clear();
if (restartRequired) {
restartCapabilities.clear();
}
modified = true;
} finally {
writeLock.unlock();
}
}
CapabilityValidation resolveCapabilities(Resource rootResource, boolean hostXmlOnly) {
readLock.lock();
try {
resolutionContext.setRootResource(rootResource);
assert resolutionContext.rootResource != null;
Map<CapabilityId, Set<RuntimeRequirementRegistration>> missing = new HashMap<>();
// Vars for tracking inconsistent contexts
boolean isInconsistent = false;
Map<CapabilityScope, Set<RuntimeRequirementRegistration>> requiresConsistency = null;
Map<CapabilityScope, Set<CapabilityScope>> consistentSets = null;
for (Map.Entry<CapabilityId, Map<String, RuntimeRequirementRegistration>> entry : requirements.entrySet()) {
CapabilityId dependentId = entry.getKey();
String dependentName = dependentId.getName();
CapabilityScope dependentContext = dependentId.getScope();
Set<CapabilityScope> consistentSet = consistentSets == null ? null : consistentSets.get(dependentContext);
for (RuntimeRequirementRegistration req : entry.getValue().values()) {
SatisfactoryCapability satisfactory = findSatisfactoryCapability(req.getRequiredName(), dependentContext, !forServer);
if (satisfactory == null) {
// Missing
if (hostXmlOnly && dependentName.startsWith("org.wildfly.domain.server-config.")
&& (req.getRequiredName().startsWith("org.wildfly.domain.server-group.")
|| req.getRequiredName().startsWith("org.wildfly.domain.socket-binding-group."))) {
// HACK. We can't resolve these now as we have no domain model at this part of boot
// We can resolve them when the domain model ops run, so wait to validate then
ControllerLogger.MGMT_OP_LOGGER.tracef("Ignoring that dependent %s cannot resolve required capability %s as the 'hostXmlOnly' param is set", dependentId, req.getRequiredName());
continue;
}
CapabilityId basicId = new CapabilityId(req.getRequiredName(), dependentContext);
Set<RuntimeRequirementRegistration> set = missing.get(basicId);
if (set == null) {
set = new HashSet<>();
missing.put(basicId, set);
}
set.add(req);
} else if (satisfactory.multipleCapabilities != null) {
// This requirement is one that needs tracking to ensure that all similar ones for this
// dependent context can be resolved against at least one context
if (requiresConsistency == null) {
requiresConsistency = new HashMap<>();
consistentSets = new HashMap<>();
}
CapabilityScope reqDependent = req.getDependentContext();
recordConsistentSets(requiresConsistency, consistentSets, reqDependent, consistentSet, req, satisfactory, reqDependent);
isInconsistent = isInconsistent || (consistentSet != null && consistentSet.size() == 0);
// Record for any contexts that include this one
for (CapabilityScope including : dependentContext.getIncludingScopes(resolutionContext)) {
consistentSet = consistentSets.get(including);
recordConsistentSets(requiresConsistency, consistentSets, including, consistentSet, req, satisfactory, reqDependent);
isInconsistent = isInconsistent || (consistentSet != null && consistentSet.size() == 0);
}
} // else simple capability match
}
}
// We've finished resolution
resolutionContext.resolutionComplete = true;
if (isInconsistent) {
// This is the exception case. Figure out the details of the problems
return new CapabilityValidation(missing, findInconsistent(requiresConsistency, consistentSets), resolutionContext);
} else if (!missing.isEmpty()) {
return new CapabilityValidation(missing, null, resolutionContext);
}
return CapabilityValidation.OK;
} finally {
readLock.unlock();
}
}
private void recordConsistentSets(Map<CapabilityScope, Set<RuntimeRequirementRegistration>> requiresConsistency, Map<CapabilityScope, Set<CapabilityScope>> consistentSets, CapabilityScope dependentContext, Set<CapabilityScope> consistentSet, RuntimeRequirementRegistration req, SatisfactoryCapability satisfactory, CapabilityScope reqDependent) {
Set<RuntimeRequirementRegistration> requiresForDependent = requiresConsistency.get(reqDependent);
if (requiresForDependent == null) {
requiresForDependent = new HashSet<>();
requiresConsistency.put(reqDependent, requiresForDependent);
}
requiresForDependent.add(req);
if (consistentSet == null) {
consistentSet = new HashSet<>(satisfactory.multipleCapabilities); // copy okContexts so retainAll calls won't mutate it
consistentSets.put(dependentContext, consistentSet);
} else {
consistentSet.retainAll(satisfactory.multipleCapabilities);
}
}
private static Set<RuntimeRequirementRegistration> findInconsistent(Map<CapabilityScope, Set<RuntimeRequirementRegistration>> requiresConsistency,
Map<CapabilityScope, Set<CapabilityScope>> consistentSets) {
Set<RuntimeRequirementRegistration> result = new HashSet<>();
for (Map.Entry<CapabilityScope, Set<CapabilityScope>> entry : consistentSets.entrySet()) {
if (entry.getValue().isEmpty()) {
// This one is a problem; see what all requirements are from the dependent context
Set<RuntimeRequirementRegistration> contextDependents = requiresConsistency.get(entry.getKey());
if (contextDependents != null) {
result.addAll(contextDependents);
}
}
}
return result;
}
private RuntimeCapabilityRegistration getCapabilityRegistration(String capabilityName, CapabilityScope capabilityScope) {
SatisfactoryCapability satisfactoryCapability = findSatisfactoryCapability(capabilityName, capabilityScope, false);
if (satisfactoryCapability == null) {
if (forServer) {
throw ControllerLogger.MGMT_OP_LOGGER.unknownCapability(capabilityName);
} else {
throw ControllerLogger.MGMT_OP_LOGGER.unknownCapabilityInContext(capabilityName, capabilityScope.getName());
}
}
return capabilities.get(satisfactoryCapability.singleCapability);
}
private SatisfactoryCapability findSatisfactoryCapability(String capabilityName, CapabilityScope dependentContext,
boolean requireConsistency) {
// Check for a simple match
CapabilityId requestedId = new CapabilityId(capabilityName, dependentContext);
if (capabilities.containsKey(requestedId)) {
return new SatisfactoryCapability(requestedId);
}
if (!forServer) {
// Try other contexts that satisfy the requested one
Set<CapabilityScope> multiple = null;
for (CapabilityScope satisfies : knownContexts) {
if (satisfies.equals(dependentContext)) {
// We already know this one doesn't exist
continue;
}
CapabilityId satisfiesId = new CapabilityId(capabilityName, satisfies);
if (capabilities.containsKey(satisfiesId) && satisfies.canSatisfyRequirement(capabilityName, dependentContext, resolutionContext)) {
if (!requireConsistency || !satisfies.requiresConsistencyCheck()) {
return new SatisfactoryCapability(satisfiesId);
} else {
if (multiple == null) {
multiple = new HashSet<>();
}
multiple.add(satisfies);
multiple.addAll(satisfies.getIncludingScopes(resolutionContext));
}
}
}
if (multiple != null) {
return new SatisfactoryCapability(multiple);
}
}
return null;
}
@Override
public Set<String> getDynamicCapabilityNames(String referencedCapability,
CapabilityScope dependentScope) {
if (referencedCapability == null || referencedCapability.isEmpty()
|| dependentScope == null) {
return Collections.emptySet();
}
// Retrieve all the provider points that matching capabilities
// must be compliant with. Each ProviderPoint is converted onto a
// PathMatcher that is used to filter-in/out the capabilities.
//For possible capabilities it is always global.
CapabilityId id = new CapabilityId(referencedCapability,
CapabilityScope.GLOBAL);
List<PathMatcher> matchers = getPossibleProviderPoints(id).stream().
map((possiblePoint) -> FileSystems.getDefault().getPathMatcher("glob:"
+ possiblePoint.toPathStyleString())).collect(Collectors.toList());
// Filter the streams of capabilities to extract matching ones
return getCapabilities().stream().
// Keep capability that matches at least one of the registration point
filter((registration) -> {
return registration.getRegistrationPoints().stream().
map((rp) -> Paths.get(rp.getAddress().toPathStyleString())).
filter((path) -> matchers.stream().anyMatch((matcher) -> matcher.matches(path))).
count() > 0;
}).
// Keep capability that can be reached from the provided scope
filter((registration) -> hasCapability(registration.getCapabilityName(), dependentScope)).
// Remove static name
filter((registration) -> !registration.getCapabilityName().equals(referencedCapability)).
// Finally convert remaining capabilities onto capability dynamic name.
map((registration) -> {
return registration.getCapabilityName().
substring(referencedCapability.length() + 1);
}).
collect(Collectors.toSet());
}
private static class ResolutionContextImpl extends CapabilityResolutionContext {
private boolean resolutionComplete;
private Resource rootResource;
@Override
public Resource getResourceRoot() {
assert rootResource != null;
return rootResource;
}
void setRootResource(Resource rootResource) {
this.rootResource = rootResource;
reset();
this.resolutionComplete = false;
}
}
private static class SatisfactoryCapability {
final CapabilityId singleCapability;
final Set<CapabilityScope> multipleCapabilities;
SatisfactoryCapability(CapabilityId singleCapability) {
this.singleCapability = singleCapability;
this.multipleCapabilities = null;
}
SatisfactoryCapability(Set<CapabilityScope> multipleCapabilities) {
this.singleCapability = null;
this.multipleCapabilities = multipleCapabilities;
}
}
/**
*
*/
static class CapabilityValidation {
public static final CapabilityValidation OK = new CapabilityValidation(null, null, null);
private final Map<CapabilityId, Set<RuntimeRequirementRegistration>> missingRequirements;
private final Set<RuntimeRequirementRegistration> inconsistentRequirements;
private final CapabilityResolutionContext resolutionContext;
private CapabilityValidation(Map<CapabilityId, Set<RuntimeRequirementRegistration>> missingRequirements,
Set<RuntimeRequirementRegistration> inconsistentRequirements,
CapabilityResolutionContext resolutionContext) {
this.resolutionContext = resolutionContext;
this.missingRequirements = missingRequirements == null
? Collections.<CapabilityId, Set<RuntimeRequirementRegistration>>emptyMap() : missingRequirements;
this.inconsistentRequirements = inconsistentRequirements == null
? Collections.emptySet() : inconsistentRequirements;
}
/**
* @return a map whose keys are missing capabilities and whose values are the requirement registrations
* from other capabilities that require that capability. Will not return {@code null}
*/
public Map<CapabilityId, Set<RuntimeRequirementRegistration>> getMissingRequirements() {
return missingRequirements;
}
/**
* @return requirement registrations that cannot be consistently resolved. Will nto return {@code null}
*/
public Set<RuntimeRequirementRegistration> getInconsistentRequirements() {
return inconsistentRequirements;
}
/**
* @return the resolution context used to perform the resolution. May be {@code null} if no resolution
* problems occurred
*/
public CapabilityResolutionContext getCapabilityResolutionContext() {
return resolutionContext;
}
public boolean isValid() {
return missingRequirements.isEmpty() && inconsistentRequirements.isEmpty();
}
}
}