/*
 * Copyright The WildFly Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package org.jboss.as.controller;

import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ACTIVE_OPERATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ATTACHED_STREAMS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CALLER_TYPE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CORE_SERVICE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILED;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILURE_DESCRIPTION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.HOST;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MANAGEMENT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MANAGEMENT_OPERATIONS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MIME_TYPE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OPERATION_HEADERS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OUTCOME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PROCESS_STATE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESPONSE_HEADERS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SERVICE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.STEPS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.USER;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.UUID;
import static org.jboss.as.controller.logging.ControllerLogger.MGMT_OP_LOGGER;
import static org.jboss.as.controller.logging.ControllerLogger.ROOT_LOGGER;
import static org.jboss.as.controller.operations.common.Util.validateOperation;
import static org.jboss.as.controller.operations.global.GlobalOperationHandlers.STD_READ_OPS;

import java.io.IOException;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;

import org.jboss.as.controller.OperationContext.RollbackHandler;
import org.jboss.as.controller.access.Authorizer;
import org.jboss.as.controller.audit.AuditLogger;
import org.jboss.as.controller.audit.ManagedAuditLogger;
import org.jboss.as.controller.capability.registry.RuntimeCapabilityRegistry;
import org.jboss.as.controller.client.ModelControllerClient;
import org.jboss.as.controller.client.Operation;
import org.jboss.as.controller.client.OperationAttachments;
import org.jboss.as.controller.client.OperationMessageHandler;
import org.jboss.as.controller.client.OperationResponse;
import org.jboss.as.controller.extension.ExtensionAddHandler;
import org.jboss.as.controller.extension.MutableRootResourceRegistrationProvider;
import org.jboss.as.controller.extension.ParallelExtensionAddHandler;
import org.jboss.as.controller.logging.ControllerLogger;
import org.jboss.as.controller.notification.NotificationSupport;
import org.jboss.as.controller.operations.global.ReadResourceHandler;
import org.jboss.as.controller.persistence.ConfigurationExtension;
import org.jboss.as.controller.persistence.ConfigurationPersistenceException;
import org.jboss.as.controller.persistence.ConfigurationPersister;
import org.jboss.as.controller.registry.DelegatingResource;
import org.jboss.as.controller.registry.ImmutableManagementResourceRegistration;
import org.jboss.as.controller.registry.ManagementResourceRegistration;
import org.jboss.as.controller.registry.NotificationHandlerRegistration;
import org.jboss.as.controller.registry.OperationEntry;
import org.jboss.as.controller.registry.PlaceholderResource;
import org.jboss.as.controller.registry.Resource;
import org.jboss.as.controller.registry.Resource.ResourceEntry;
import org.jboss.as.core.security.AccessMechanism;
import org.jboss.as.version.Stability;
import org.jboss.dmr.ModelNode;
import org.jboss.msc.service.ServiceRegistry;
import org.jboss.msc.service.ServiceTarget;
import org.wildfly.security.auth.server.SecurityIdentity;

/**
 * Default {@link ModelController} implementation.
 *
 * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
 */
class ModelControllerImpl implements ModelController {

    private static final String INITIAL_BOOT_OPERATION = "initial-boot-operation";
    private static final String POST_EXTENSION_BOOT_OPERATION = "post-extension-boot-operation";
    static final ModelNode EMPTY_ADDRESS = new ModelNode().setEmptyList();

    static {
        EMPTY_ADDRESS.protect();
    }

    private static final PathAddress MODEL_CONTROLLER_ADDRESS = PathAddress.pathAddress(PathElement.pathElement(CORE_SERVICE, MANAGEMENT),
            PathElement.pathElement(SERVICE, MANAGEMENT_OPERATIONS));

    private final ServiceRegistry serviceRegistry;
    private final ServiceTarget serviceTarget;
    private final ModelControllerLock controllerLock = new ModelControllerLock();
    private final ContainerStateMonitor stateMonitor;
    private final AtomicReference<ManagementModelImpl> managementModel = new AtomicReference<>();
    private final ConfigurationPersister persister;
    private final ProcessType processType;
    private final Stability stability;
    private final RunningModeControl runningModeControl;
    private final AtomicBoolean bootingFlag = new AtomicBoolean(true);
    private final AtomicBoolean bootingReadOnlyFlag = new AtomicBoolean(false);
    private final OperationStepHandler prepareStep;
    private final ControlledProcessState processState;
    private final ExecutorService executorService;
    private final ExpressionResolver expressionResolver;
    private final Authorizer authorizer;
    private final Supplier<SecurityIdentity> securityIdentitySupplier;

    private final ConcurrentMap<Integer, OperationContextImpl> activeOperations = new ConcurrentHashMap<>();
    private final Random random = new Random();
    private final ManagedAuditLogger auditLogger;
    private final BootErrorCollector bootErrorCollector;

    private final NotificationSupport notificationSupport;

    /** Tracks the relationship between domain resources and hosts and server groups */
    private final HostServerGroupTracker hostServerGroupTracker;
    private final Resource.ResourceEntry modelControllerResource;
    private final OperationStepHandler extraValidationStepHandler;

    private final AbstractControllerService.PartialModelIndicator partialModelIndicator;
    private final AbstractControllerService.ControllerInstabilityListener instabilityListener;

    private volatile ModelControllerClientFactoryImpl clientFactory;

    private PathAddress modelControllerResourceAddress;

    ModelControllerImpl(final ServiceRegistry serviceRegistry, final ServiceTarget serviceTarget,
                        final ManagementResourceRegistration rootRegistration,
                        final ContainerStateMonitor stateMonitor, final ConfigurationPersister persister,
                        final ProcessType processType, Stability stability, final RunningModeControl runningModeControl,
                        final OperationStepHandler prepareStep, final ControlledProcessState processState, final ExecutorService executorService,
                        final ExpressionResolver expressionResolver, final Authorizer authorizer, final Supplier<SecurityIdentity> securityIdentitySupplier,
                        final ManagedAuditLogger auditLogger, NotificationSupport notificationSupport,
                        final BootErrorCollector bootErrorCollector, final OperationStepHandler extraValidationStepHandler,
                        final CapabilityRegistry capabilityRegistry,
                        final AbstractControllerService.PartialModelIndicator partialModelIndicator,
                        final AbstractControllerService.ControllerInstabilityListener instabilityListener) {
        this.partialModelIndicator = partialModelIndicator;
        this.instabilityListener = instabilityListener;
        assert serviceRegistry != null;
        this.serviceRegistry = serviceRegistry;
        assert serviceTarget != null;
        this.serviceTarget = serviceTarget;
        assert rootRegistration != null;

        assert capabilityRegistry != null;
        ManagementModelImpl mmi = new ManagementModelImpl(rootRegistration, Resource.Factory.create(), capabilityRegistry);
        //ModelControllerImpl.this.managementModel.set(mmi);
        mmi.publish();
        assert stateMonitor != null;
        this.stateMonitor = stateMonitor;
        assert persister != null;
        this.persister = persister;
        assert processType != null;
        this.processType = processType;
        assert stability != null;
        this.stability = stability;
        assert runningModeControl != null;
        this.runningModeControl = runningModeControl;
        assert notificationSupport != null;
        this.notificationSupport = notificationSupport;
        this.prepareStep = prepareStep == null ? new DefaultPrepareStepHandler() : prepareStep;
        assert processState != null;
        this.processState = processState;
        this.executorService = executorService;
        assert expressionResolver != null;
        this.expressionResolver = expressionResolver;
        assert securityIdentitySupplier != null;
        this.securityIdentitySupplier = securityIdentitySupplier;
        assert authorizer != null;
        this.authorizer = authorizer;
        assert auditLogger != null;
        this.auditLogger = auditLogger;
        assert bootErrorCollector != null;
        this.bootErrorCollector = bootErrorCollector;
        this.hostServerGroupTracker = processType.isManagedDomain() ? new HostServerGroupTracker() : null;
        this.modelControllerResource = new ModelControllerResource();
        this.extraValidationStepHandler = extraValidationStepHandler;
        if (processType.isServer()) {
            this.modelControllerResourceAddress = MODEL_CONTROLLER_ADDRESS;
        }
        auditLogger.startBoot();
    }

    private ModelControllerClientFactoryImpl getClientFactory() {
        if (clientFactory == null) {
            // In a race this could result in > 1 factories being instantiated but that is harmless
            this.clientFactory = new ModelControllerClientFactoryImpl(this, securityIdentitySupplier);
        }
        return clientFactory;
    }

    /**
     * Executes an operation on the controller
     * @param operation the operation
     * @param handler the handler
     * @param control the transaction control
     * @param attachments the operation attachments
     * @return the result of the operation
     */
    @Override
    public ModelNode execute(final ModelNode operation, final OperationMessageHandler handler, final OperationTransactionControl control, final OperationAttachments attachments) {
        return executeOperation(operation, handler, control, attachments, false);
    }

    @Override
    public OperationResponse execute(Operation operation, OperationMessageHandler handler, OperationTransactionControl control) {
        return executeOperation(operation, handler, control, false);
    }

    final ModelNode executeOperation(final ModelNode operation, final OperationMessageHandler handler, final OperationTransactionControl control,
                            final OperationAttachments attachments, final boolean forBoot) {
        OperationResponse or = executeForResponse(operation, handler, control, attachments, forBoot);
        ModelNode result = or.getResponseNode();
        try {
            or.close();
        } catch (IOException e) {
            ROOT_LOGGER.debugf(e, "Caught exception closing response to %s whose associated streams, " +
                    "if any, were not wanted", operation);
        }
        return result;
    }

    final OperationResponse executeOperation(Operation operation, OperationMessageHandler handler, OperationTransactionControl control, boolean forBoot) {
        return executeForResponse(operation.getOperation(), handler, control, operation, forBoot);
    }

    final OperationResponse executeForResponse(final ModelNode operation, OperationMessageHandler handler, OperationTransactionControl control,
                                         final OperationAttachments attachments, final boolean forBoot) {
        SecurityIdentity securityIdentity = securityIdentitySupplier.get();
        return securityIdentity.runAs((PrivilegedAction<OperationResponse>) () -> internalExecute(operation,
                handler, control, attachments, prepareStep, false, partialModelIndicator.isModelPartial(), forBoot));
    }

    private AbstractOperationContext getDelegateContext(final int operationId) {
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(ModelController.ACCESS_PERMISSION);
        }
        // Get the primary context to delegate the reads to
        final AbstractOperationContext delegateContext = activeOperations.get(operationId);
        if(delegateContext == null) {
            // TODO we might just allow this case too, but for now it's just wrong (internal) usage
            throw ControllerLogger.ROOT_LOGGER.noContextToDelegateTo(operationId);
        }
        return delegateContext;
    }

    /**
     * Executes an operation on the controller latching onto an existing transaction
     *
     * @param operation the operation
     * @param handler the handler
     * @param control the transaction control
     * @param prepareStep the prepare step to be executed before any other steps
     * @param operationId the id of the current transaction
     * @return the result of the operation
     */
    @SuppressWarnings("deprecation")
    protected ModelNode executeReadOnlyOperation(final ModelNode operation, final OperationMessageHandler handler, final OperationTransactionControl control, final OperationStepHandler prepareStep, final int operationId) {
        final AbstractOperationContext delegateContext = getDelegateContext(operationId);
        CurrentOperationIdHolder.setCurrentOperationID(operationId);
        try {
            return executeReadOnlyOperation(operation, delegateContext.getManagementModel(), control, prepareStep, delegateContext);
        } finally {
            CurrentOperationIdHolder.setCurrentOperationID(null);
        }
    }
    @SuppressWarnings("deprecation")
    protected ModelNode executeReadOnlyOperation(final ModelNode operation, final OperationTransactionControl control, final OperationStepHandler prepareStep) {
        final AbstractOperationContext delegateContext = getDelegateContext(CurrentOperationIdHolder.getCurrentOperationID());
        return executeReadOnlyOperation(operation, delegateContext.getManagementModel(), control, prepareStep, delegateContext);
    }

    protected ModelNode executeReadOnlyOperation(final ModelNode operation, final Resource resource, final OperationTransactionControl control, final OperationStepHandler prepareStep) {
        // Get the primary context to delegate the reads to
        @SuppressWarnings("deprecation")
        final int operationId = CurrentOperationIdHolder.getCurrentOperationID();
        final AbstractOperationContext delegateContext = getDelegateContext(operationId);
        final ManagementModelImpl current = delegateContext.getManagementModel();
        final ManagementModelImpl mgmtModel = new ManagementModelImpl(current.getRootResourceRegistration(), resource, current.capabilityRegistry);
        return executeReadOnlyOperation(operation, mgmtModel, control, prepareStep, delegateContext);
    }

    protected ModelNode executeReadOnlyOperation(final ModelNode operation, final ManagementModelImpl model, final OperationTransactionControl control, final OperationStepHandler prepareStep, AbstractOperationContext delegateContext) {
        final ModelNode response = new ModelNode();
        @SuppressWarnings("deprecation")
        final int operationId = CurrentOperationIdHolder.getCurrentOperationID();
        final OperationTransactionControl txControl = control == null ? null : new OperationTransactionControl() {
            @Override
            public void operationPrepared(OperationTransaction transaction, ModelNode result) {
                control.operationPrepared(transaction, response);
            }
        };

        // Use a read-only context
        try (ReadOnlyContext context = new ReadOnlyContext(processType, this.stability, runningModeControl.getRunningMode(), txControl, processState, false, model, delegateContext, this, operationId, securityIdentitySupplier)) {
            context.addStep(response, operation, prepareStep, OperationContext.Stage.MODEL);
            context.executeOperation();
        }

        if (!response.hasDefined(RESPONSE_HEADERS) || !response.get(RESPONSE_HEADERS).hasDefined(PROCESS_STATE)) {
            ControlledProcessState.State state = processState.getState();
            switch (state) {
                case RELOAD_REQUIRED:
                case RESTART_REQUIRED:
                    response.get(RESPONSE_HEADERS, PROCESS_STATE).set(state.toString());
                    break;
                default:
                    break;
            }
        }
        return response;
    }

    /**
     * Executes an operation on the controller
     * @param operation the operation
     * @param handler the handler
     * @param control the transaction control
     * @param attachments the operation attachments
     * @param prepareStep the prepare step to be executed before any other steps
     * @param attemptLock set to {@code true} to try to obtain the controller lock
     * @param partialModel {@code true} if the model will only be partially complete after this operation
     * @return the result of the operation
     */
    protected OperationResponse internalExecute(final ModelNode operation, final OperationMessageHandler handler, final OperationTransactionControl control,
                                                final OperationAttachments attachments, final OperationStepHandler prepareStep, final boolean attemptLock, boolean partialModel) {
        return internalExecute(operation, handler, control, attachments, prepareStep, attemptLock, partialModel, false);
    }

    private OperationResponse internalExecute(final ModelNode operation, final OperationMessageHandler handler, final OperationTransactionControl control,
                                              final OperationAttachments attachments, final OperationStepHandler prepareStep,
                                              final boolean attemptLock, final boolean partialModel, final boolean forBoot) {

        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(ModelController.ACCESS_PERMISSION);
        }

        final ModelNode responseNode = validateOperation(operation);
        if(responseNode.hasDefined(FAILURE_DESCRIPTION)) {
            return OperationResponse.Factory.createSimple(responseNode);
        }

        OperationHeaders headers;
        try {
            headers = OperationHeaders.fromOperation(operation);
        } catch (OperationFailedException ofe) {
            return OperationHeaders.fromFailure(ofe);
        }

        // Report the correct operation response, otherwise the preparedResult would only contain
        // the result of the last active step in a composite operation
        final OperationTransactionControl originalResultTxControl = control == null ? null : new OperationTransactionControl() {
            @Override
            public void operationPrepared(OperationTransaction transaction, ModelNode result, OperationContext context) {
                control.operationPrepared(transaction, responseNode, context);
            }

            @Override
            public void operationPrepared(OperationTransaction transaction, ModelNode result) {
                control.operationPrepared(transaction, responseNode);
            }
        };
        Map<String, OperationResponse.StreamEntry> responseStreams;
        AccessMechanism accessMechanism = null;
        AccessAuditContext accessContext = SecurityActions.currentAccessAuditContext();
        if (accessContext != null) {
            // External caller of some sort.

            // Internal domain ManagementRequestHandler impls will set a header to track an op through the domain
            // This header will only be set at this point if the request came in that way; for user-originated
            // requests on this process the header is added later during op execution by OperationCoordinatorStepHandler
            if (headers.getDomainUUID() != null) {
                accessContext.setDomainUuid(headers.getDomainUUID());
                accessContext.setDomainRollout(true);
            }
            // Native and http ManagementRequestHandler impls, plus those used for intra-domain comms
            // will always set a header to specify the access mechanism. JMX directly sets it on the accessContext
            if (headers.getAccessMechanism() != null) {
                accessContext.setAccessMechanism(headers.getAccessMechanism());
            }
            accessMechanism = accessContext.getAccessMechanism();
        } // else its an internal caller as external callers always get an AccessAuditContext

        // WFCORE-184. Exclude external callers during boot. AccessMechanism is always set for external callers
        // If the booting flag was cleared, then check if we are in a booting read only mode and if the current
        // operation is not an allowed as a readOnly Operation, then it gets rejected
        if ( (accessMechanism != null && bootingFlag.get()) ||
                (accessMechanism != null && !bootingFlag.get() && bootingReadOnlyFlag.get() && !isReadOnlyOperation(operation)) ) {
            return handleExternalRequestDuringBoot();
        }

        for (;;) {
            responseStreams = null;
            // Create a random operation-id
            final Integer operationID = random.nextInt();
            final OperationContextImpl context = new OperationContextImpl(operationID, operation.get(OP).asString(),
                    operation.get(OP_ADDR), this, processType, this.stability, runningModeControl.getRunningMode(),
                    headers, handler, attachments, managementModel.get(), originalResultTxControl, processState, auditLogger,
                    bootingFlag.get(), forBoot, hostServerGroupTracker, accessContext, notificationSupport,
                    false, extraValidationStepHandler, partialModel, securityIdentitySupplier);
            // Try again if the operation-id is already taken
            if(activeOperations.putIfAbsent(operationID, context) == null) {
                //noinspection deprecation
                CurrentOperationIdHolder.setCurrentOperationID(operationID);
                boolean shouldUnlock = false;
                try (context) {
                    if (attemptLock) {
                        if (!controllerLock.detectDeadlockAndGetLock(operationID)) {
                            responseNode.get(OUTCOME).set(FAILED);
                            responseNode.get(FAILURE_DESCRIPTION).set(ControllerLogger.ROOT_LOGGER.cannotGetControllerLock());
                            return OperationResponse.Factory.createSimple(responseNode);
                        }
                        shouldUnlock = true;
                    }

                    context.addStep(responseNode, operation, prepareStep, OperationContext.Stage.MODEL);
                    ControllerLogger.MGMT_OP_LOGGER.tracef("Executing %s", operation);
                    context.executeOperation();
                    responseStreams = context.getResponseStreams();
                } catch (Error e) {
                    try {
                        controllerUnstable();
                    } catch (Error ignored) {
                        // we already have the main error
                    }
                    throw e;
                } finally {

                    if (!responseNode.hasDefined(RESPONSE_HEADERS) || !responseNode.get(RESPONSE_HEADERS).hasDefined(PROCESS_STATE)) {
                        ControlledProcessState.State state = processState.getState();
                        switch (state) {
                            case RELOAD_REQUIRED:
                            case RESTART_REQUIRED:
                                responseNode.get(RESPONSE_HEADERS, PROCESS_STATE).set(state.toString());
                                break;
                            default:
                                break;
                        }
                    }

                    if (shouldUnlock) {
                        controllerLock.unlock(operationID);
                    }
                    activeOperations.remove(operationID);
                    //noinspection deprecation
                    CurrentOperationIdHolder.setCurrentOperationID(null);
                }
                break;
            }
        }
        if (responseStreams == null || responseStreams.size() == 0) {
            return OperationResponse.Factory.createSimple(responseNode);
        } else {
            return new OperationResponseImpl(responseNode, responseStreams);
        }
    }

    private static OperationResponse handleExternalRequestDuringBoot() {
        ModelNode result = new ModelNode();
        result.get(OUTCOME).set(FAILED);
        result.get(FAILURE_DESCRIPTION).set(ControllerLogger.MGMT_OP_LOGGER.managementUnavailableDuringBoot());
        // TODO WFCORE-185 once we have http codes for failure messages, include those, e.g.
        // result.get("http-code").set(nsre.getHttpCode());
        return OperationResponse.Factory.createSimple(result);
    }

    boolean boot(final List<ModelNode> bootList, final OperationMessageHandler handler, final OperationTransactionControl control,
                 final boolean rollbackOnRuntimeFailure, MutableRootResourceRegistrationProvider parallelBootRootResourceRegistrationProvider,
                 final boolean skipModelValidation, final boolean partialModel, final ConfigurationExtension configExtension) {

        final Integer operationID = random.nextInt();

        OperationHeaders headers = OperationHeaders.forBoot(rollbackOnRuntimeFailure);

        //For the initial operations the model will not be complete, so defer the validation
        final AbstractOperationContext context = new OperationContextImpl(operationID, INITIAL_BOOT_OPERATION, EMPTY_ADDRESS,
                this, processType, this.stability, runningModeControl.getRunningMode(),
                headers, handler, null, managementModel.get(), control, processState, auditLogger, bootingFlag.get(), true,
                hostServerGroupTracker, null, notificationSupport, true, extraValidationStepHandler, true, securityIdentitySupplier);

        // Add to the context all ops prior to the first ExtensionAddHandler as well as all ExtensionAddHandlers; save the rest.
        // This gets extensions registered before proceeding to other ops that count on these registrations
        BootOperations bootOperations = organizeBootOperations(bootList, operationID, parallelBootRootResourceRegistrationProvider);

        OperationContext.ResultAction resultAction = bootOperations.invalid ? OperationContext.ResultAction.ROLLBACK : OperationContext.ResultAction.KEEP;
        if (!bootOperations.initialOps.isEmpty()) {
            // Run the steps up to the last ExtensionAddHandler
            for (ParsedBootOp initialOp : bootOperations.initialOps) {
                context.addBootStep(initialOp);
            }
            try (context) {
                resultAction = context.executeOperation();
            }
        }
        //here the meta-model is available
        if (resultAction == OperationContext.ResultAction.KEEP && bootOperations.postExtensionOps != null) {
            // Success. Now any extension handlers are registered. Continue with remaining ops
            final AbstractOperationContext postExtContext = new OperationContextImpl(operationID, POST_EXTENSION_BOOT_OPERATION,
                    EMPTY_ADDRESS, this, processType, this.stability, runningModeControl.getRunningMode(),
                    headers, handler, null, managementModel.get(), control, processState, auditLogger,
                            bootingFlag.get(), true, hostServerGroupTracker, null, notificationSupport, true,
                            extraValidationStepHandler, partialModel, securityIdentitySupplier);
            if (configExtension != null && configExtension.shouldProcessOperations(runningModeControl)) {
                configExtension.processOperations(managementModel.get().getRootResourceRegistration(), bootOperations.postExtensionOps);
            }
            for (ParsedBootOp parsedOp : bootOperations.postExtensionOps) {
                if (parsedOp.handler == null) {
                    // The extension should have registered the handler now
                    parsedOp = new ParsedBootOp(parsedOp,
                            managementModel.get().getRootResourceRegistration().getOperationHandler(parsedOp.address, parsedOp.operationName));
                }
                if (parsedOp.handler == null) {
                    logNoHandler(parsedOp);
                    postExtContext.setRollbackOnly();
                    // stop
                    break;
                } else {
                    if(parsedOp.isBootHandlerUpdateNeeded()) {
                        ParallelBootOperationStepHandler updatedHandler =  new ParallelBootOperationStepHandler(executorService, managementModel.get().getRootResourceRegistration(), processState, this, operationID, extraValidationStepHandler);
                        for(ModelNode childOp : parsedOp.getChildOperations()) {
                            updatedHandler.addSubsystemOperation(new ParsedBootOp(childOp));
                        }
                        postExtContext.addBootStep(new ParsedBootOp(parsedOp.operation, updatedHandler));
                    } else {
                        postExtContext.addBootStep(parsedOp);
                    }
                }
            }
            try (postExtContext) {
                resultAction = postExtContext.executeOperation();
            }

            if (!skipModelValidation && resultAction == OperationContext.ResultAction.KEEP && bootOperations.postExtensionOps != null) {
                //Get the modified resources from the initial operations and add to the resources to be validated by the post operations
                Set<PathAddress> validateAddresses = new HashSet<>();
                Resource root = managementModel.get().getRootResource();
                addAllAddresses(managementModel.get().getRootResourceRegistration(), PathAddress.EMPTY_ADDRESS, root, validateAddresses);

                try (AbstractOperationContext validateContext = new OperationContextImpl(operationID, POST_EXTENSION_BOOT_OPERATION,
                        EMPTY_ADDRESS, this, processType, this.stability, runningModeControl.getRunningMode(),
                        headers, handler, null, managementModel.get(), control, processState, auditLogger,
                                bootingFlag.get(), true, hostServerGroupTracker, null, notificationSupport, false,
                                extraValidationStepHandler, partialModel, securityIdentitySupplier)) {
                    validateContext.addModifiedResourcesForModelValidation(validateAddresses);
                    resultAction = validateContext.executeOperation();
                }
            }
        }
        return  resultAction == OperationContext.ResultAction.KEEP;
    }

    private void addAllAddresses(ImmutableManagementResourceRegistration mrr, PathAddress current, Resource resource, Set<PathAddress> addresses) {
        addresses.add(current);
        for (String name : getNonIgnoredChildTypes(mrr)) {
            for (ResourceEntry entry : resource.getChildren(name)) {
                if (!entry.isProxy() && !entry.isRuntime()) {
                    addAllAddresses(mrr.getSubModel(PathAddress.pathAddress(entry.getPathElement())), current.append(entry.getPathElement()), entry, addresses);
                }
            }
        }
    }

    /**
     * Creates a set of child types that are not {@linkplain ImmutableManagementResourceRegistration#isRemote() remote}
     * or {@linkplain ImmutableManagementResourceRegistration#isRuntimeOnly() runtime} child types.
     *
     * @param mrr the resource registration to process the child types for
     *
     * @return a collection of non-remote and non-runtime only child types
     */
    private static Set<String> getNonIgnoredChildTypes(ImmutableManagementResourceRegistration mrr) {
        final Set<String> result = new LinkedHashSet<>();
        for (PathElement pe : mrr.getChildAddresses(PathAddress.EMPTY_ADDRESS)) {
            ImmutableManagementResourceRegistration childMrr = mrr.getSubModel(PathAddress.pathAddress(pe));
            if (childMrr != null && !childMrr.isRemote() && !childMrr.isRuntimeOnly()) {
                result.add(pe.getKey());
            }
        }
        return result;
    }

    /**
     * Organizes the list of boot operations such that all extension add operations are executed in the given context,
     * while all non-extension add operations found after the first extension add are stored for subsequent invocation
     * in a separate context. Also:
     * <ol>
     *     <li>Ensures that any operations affecting interfaces or sockets are run before any operations affecting
     *     subsystems. This improves boot performance by ensuring required services are available as soon as possible.
     *     </li>
     *     <li>If an executor service is available, organizes all extension add ops so the extension initialization
     *      can be done in parallel by the executor service.
     *     </li>
     *     <li>If an executor service is available and the controller type is SERVER, organizes all subsystem ops so
     *     they can be done in parallel by the executor service.
     *     </li>
     * </ol>
     *
     *
     * @param bootList the list of boot operations
     * @param lockPermit lockPermit to use in any {@link org.jboss.as.controller.ParallelBootOperationContext}
     * @param parallelBootRootResourceRegistrationProvider the root resource registration provider used for the parallel extension add handler. If {@code null} the default one will be used
     * @return data structure organizing the boot ops for initial execution and post-extension-add execution
     */
    private BootOperations organizeBootOperations(List<ModelNode> bootList, final int lockPermit, MutableRootResourceRegistrationProvider parallelBootRootResourceRegistrationProvider) {

        final List<ParsedBootOp> initialOps = new ArrayList<ParsedBootOp>();
        List<ParsedBootOp> postExtensionOps = null;
        boolean invalid = false;
        boolean sawExtensionAdd = false;
        final ManagementResourceRegistration rootRegistration = managementModel.get().getRootResourceRegistration();
        final MutableRootResourceRegistrationProvider parallellBRRRProvider = parallelBootRootResourceRegistrationProvider != null ?
                parallelBootRootResourceRegistrationProvider : getMutableRootResourceRegistrationProvider();
        ParallelExtensionAddHandler parallelExtensionAddHandler = executorService == null ? null : new ParallelExtensionAddHandler(executorService, parallellBRRRProvider);
        ParallelBootOperationStepHandler parallelSubsystemHandler = (executorService != null && processType.isServer() && runningModeControl.getRunningMode() == RunningMode.NORMAL)
                ? new ParallelBootOperationStepHandler(executorService, rootRegistration, processState, this, lockPermit, extraValidationStepHandler) : null;
        boolean registeredParallelSubsystemHandler = false;
        int subsystemIndex = 0;
        for (ModelNode bootOp : bootList) {
            final ParsedBootOp parsedOp = new ParsedBootOp(bootOp);
            if (postExtensionOps != null) {
                // Handle cases like AppClient where extension adds are interleaved with subsystem ops
                if (parsedOp.isExtensionAdd()) {
                    final ExtensionAddHandler stepHandler = (ExtensionAddHandler) rootRegistration.getOperationHandler(parsedOp.address, parsedOp.operationName);
                    if (parallelExtensionAddHandler != null) {
                        parallelExtensionAddHandler.addParsedOp(parsedOp, stepHandler);
                    } else {
                        initialOps.add(new ParsedBootOp(parsedOp, stepHandler));
                    }
                } else {
                    if (parallelSubsystemHandler == null || !parallelSubsystemHandler.addSubsystemOperation(parsedOp)) {
                        // Put any interface/socket op before the subsystem op
                        if (registeredParallelSubsystemHandler && (parsedOp.isInterfaceOperation() || parsedOp.isSocketOperation())) {
                            postExtensionOps.add(subsystemIndex++, parsedOp);
                        } else {
                            postExtensionOps.add(parsedOp);
                        }
                    } else if (!registeredParallelSubsystemHandler) {
                        postExtensionOps.add(parallelSubsystemHandler.getParsedBootOp());
                        subsystemIndex = postExtensionOps.size() - 1;
                        registeredParallelSubsystemHandler = true;
                    }
                }
            } else {
                final OperationStepHandler stepHandler = rootRegistration.getOperationHandler(parsedOp.address, parsedOp.operationName);
                if (!sawExtensionAdd && stepHandler == null) {
                    // Odd case. An op prior to the first extension add where there is no handler. This would really
                    // only happen during AS development
                    logNoHandler(parsedOp);
                    invalid = true;
                    // stop
                    break;
                } else if (stepHandler instanceof ExtensionAddHandler) {
                    if (parallelExtensionAddHandler != null) {
                        parallelExtensionAddHandler.addParsedOp(parsedOp, (ExtensionAddHandler) stepHandler);
                        if (!sawExtensionAdd) {
                            initialOps.add(parallelExtensionAddHandler.getParsedBootOp());
                        }
                    } else {
                        initialOps.add(new ParsedBootOp(parsedOp, stepHandler));
                    }
                    sawExtensionAdd = true;
                } else if (!sawExtensionAdd) {
                    // An operation prior to the first Extension Add
                    initialOps.add(new ParsedBootOp(parsedOp, stepHandler));
                } else {
                    // Start the postExtension list
                    postExtensionOps = new ArrayList<ParsedBootOp>(32);
                    if (parallelSubsystemHandler == null || !parallelSubsystemHandler.addSubsystemOperation(parsedOp)) {
                        postExtensionOps.add(parsedOp);
                    } else {
                        // First subsystem op; register the parallel handler and add the op to it
                        postExtensionOps.add(parallelSubsystemHandler.getParsedBootOp());
                        registeredParallelSubsystemHandler = true;
                    }
                }
            }
        }

        return new BootOperations(initialOps, postExtensionOps, invalid);
    }

    void finishBoot() {
        // Notify the audit logger that we're done booting
        auditLogger.bootDone();
        bootingReadOnlyFlag.set(false);
        bootingFlag.set(false);
    }

    /**
     * Finish boot using an specific value for booting read only flag
     * @param readOnly
     */
    void finishBoot(boolean readOnly) {
        // Notify the audit logger that we're done booting
        auditLogger.bootDone();
        bootingReadOnlyFlag.set(readOnly);
        bootingFlag.set(false);
    }

    /**
     * Clears the current booting read only flag
     */
    void clearBootingReadOnlyFlag() {
        bootingReadOnlyFlag.set(false);
    }

    ManagementModel getManagementModel() {
        return managementModel.get();
    }

    Resource.ResourceEntry getModelControllerResource() {
        return modelControllerResource;
    }

    ModelControllerClient createBootClient(final Executor executor) {
        return getClientFactory().createBootClient(executor);
    }

    ConfigurationPersister.PersistenceResource writeModel(final ManagementModelImpl model, final Set<PathAddress> affectedAddresses,
                                                          final boolean resourceTreeModified, final boolean capabilityRegistryModified,
                                                          final boolean resourceRegistrationModified) throws ConfigurationPersistenceException {
        final ConfigurationPersister.PersistenceResource delegate;
        if (resourceTreeModified) {
            // Don't do an expensive Resource.Tools.readModel if the persister isn't going to use the result
            if (persister.isPersisting()) {
                ControllerLogger.MGMT_OP_LOGGER.tracef("persisting %s from %s", model.rootResource, model);
                final ModelNode newModel = Resource.Tools.readModel(model.rootResource, model.resourceRegistration);
                delegate = persister.store(newModel, affectedAddresses);
            } else {
                ControllerLogger.MGMT_OP_LOGGER.tracef("Ignoring permanent persistence during boot");
                delegate = null;
            }
        } else {
            ControllerLogger.MGMT_OP_LOGGER.tracef("persisting with no resource tree changes to %s", model);
            delegate = null;
        }
        return new ConfigurationPersister.PersistenceResource() {

            @Override
            public void commit() {
                // Discard the tracker first, so if there's any race the new OperationContextImpl
                // gets a cleared tracker
                if (hostServerGroupTracker != null) {
                    hostServerGroupTracker.invalidate();
                }
                // Publish capability registry mods if the caller knows it modified it
                // or if it modified the resource reg tree, as the registrations may
                // have modified the cap reg without the caller knowing
                if ((capabilityRegistryModified || resourceRegistrationModified)
                        && model.capabilityRegistry.isModified()) {
                    model.capabilityRegistry.publish();
                }
                if (resourceTreeModified) {
                    model.publish();
                    if (delegate != null) {
                        delegate.commit();
                    }
                }
            }

            @Override
            public void rollback() {
                // Don't discard the model here; let that happen via finally block calls to MCI.discardModel
                //model.discard();
                if (delegate != null) {
                    delegate.rollback();
                }
            }
        };
    }

    void discardModel(final ManagementModelImpl model,
                      final boolean resourceTreeModified, final boolean capabilityRegistryModified,
                      final boolean resourceRegistrationModified) {
        // Roll back capability registry mods if the caller knows it modified it
        // or if it modified the resource reg tree, as the registrations may
        // have modified the cap reg without the caller knowing
        if ((capabilityRegistryModified || resourceRegistrationModified)
                && model.capabilityRegistry.isModified()) {
            model.capabilityRegistry.rollback();
        }
        if (resourceTreeModified) {
            model.discard();
        }
    }

    void acquireWriteLock(Integer permit, final boolean interruptibly) throws InterruptedException {
        if (interruptibly) {
            //noinspection LockAcquiredButNotSafelyReleased
            controllerLock.lockInterruptibly(permit);
        } else {
            //noinspection LockAcquiredButNotSafelyReleased
            controllerLock.lock(permit);
        }
    }

    void acquireReadLock(Integer permit, final boolean interruptibly) throws InterruptedException {
        if (interruptibly) {
            //noinspection LockAcquiredButNotSafelyReleased
            controllerLock.lockSharedInterruptibly(permit);
        } else {
            //noinspection LockAcquiredButNotSafelyReleased
            controllerLock.lockShared(permit);
        }
    }

    boolean acquireWriteLock(Integer permit, final boolean interruptibly, long timeout) throws InterruptedException {
        if (interruptibly) {
            //noinspection LockAcquiredButNotSafelyReleased
            return controllerLock.lockInterruptibly(permit, timeout, TimeUnit.SECONDS);
        } else {
            //noinspection LockAcquiredButNotSafelyReleased
            return controllerLock.lock(permit, timeout, TimeUnit.SECONDS);
        }
    }

    void releaseWriteLock(Integer permit) {
        controllerLock.unlock(permit);
    }

    void releaseReadLock(Integer permit) {
        controllerLock.unlockShared(permit);
    }
    /**
     * Log a report of any problematic container state changes and reset container state change history
     * so another run of this method or of {@link #awaitContainerStateChangeReport(long, java.util.concurrent.TimeUnit)}
     * will produce a report not including any changes included in a report returned by this run.
     */
    void logContainerStateChangesAndReset() {
        stateMonitor.logContainerStateChangesAndReset();
    }

    /**
     * Await service container stability.
     *
     * @param timeout maximum period to wait for service container stability
     * @param timeUnit unit in which {@code timeout} is expressed
     * @param interruptibly {@code false} if thread interruption should be ignored
     *
     * @throws java.lang.InterruptedException if {@code interruptibly} is {@code true} and the thread is interrupted while awaiting service container stability
     * @throws java.util.concurrent.TimeoutException if service container stability is not reached before the specified timeout
     */
    void awaitContainerStability(long timeout, TimeUnit timeUnit, final boolean interruptibly)
            throws InterruptedException, TimeoutException {
        if (interruptibly) {
            stateMonitor.awaitStability(timeout, timeUnit);
        } else {
            stateMonitor.awaitStabilityUninterruptibly(timeout, timeUnit);
        }
    }

    /**
     * Await service container stability and then report on container state changes. Does not reset change history,
     * so another run of this method with no intervening call to {@link #logContainerStateChangesAndReset()} will produce a report including
     * any changes included in a report returned by the first run.
     *
     * @param timeout maximum period to wait for service container stability
     * @param timeUnit unit in which {@code timeout} is expressed
     *
     * @return a change report, or {@code null} if there is nothing to report
     *
     * @throws java.lang.InterruptedException if the thread is interrupted while awaiting service container stability
     * @throws java.util.concurrent.TimeoutException if service container stability is not reached before the specified timeout
     */
    ContainerStateMonitor.ContainerStateChangeReport awaitContainerStateChangeReport(long timeout, TimeUnit timeUnit)
            throws InterruptedException, TimeoutException {
        return stateMonitor.awaitContainerStateChangeReport(timeout, timeUnit);
    }

    /** Notification from an operation that MSC could not stabilize before operation completion. */
    void containerCannotStabilize() {
        controllerUnstable();
    }

    private void controllerUnstable() {
        processState.setRestartRequired();
        if (instabilityListener != null) {
            instabilityListener.controllerUnstable();
        }
    }

    ServiceRegistry getServiceRegistry() {
        return serviceRegistry;
    }

    ServiceTarget getServiceTarget() {
        return serviceTarget;
    }


    NotificationHandlerRegistration getNotificationRegistry() {
        return notificationSupport.getNotificationRegistry();
    }

    NotificationSupport getNotificationSupport() {
        return notificationSupport;
    }

    ModelNode resolveExpressions(ModelNode node, OperationContext context) throws OperationFailedException {
        return expressionResolver.resolveExpressions(node, context);
    }

    Authorizer getAuthorizer() {
        return authorizer;
    }

    private void logNoHandler(ParsedBootOp parsedOp) {
        ImmutableManagementResourceRegistration child = managementModel.get().getRootResourceRegistration().getSubModel(parsedOp.address);
        if (child == null) {
            ROOT_LOGGER.error(ROOT_LOGGER.noSuchResourceType(parsedOp.address));
        } else {
            ROOT_LOGGER.error(ROOT_LOGGER.noHandlerForOperation(parsedOp.operationName, parsedOp.address));
        }

    }

    AuditLogger getAuditLogger() {
        return auditLogger;
    }

    static MutableRootResourceRegistrationProvider getMutableRootResourceRegistrationProvider() {
        return MutableRootResourceRegistrationProviderImpl.INSTANCE;
    }

    void addFailureDescription(ModelNode operation, ModelNode failure) {
        if (bootingFlag.get()) {
            bootErrorCollector.addFailureDescription(operation, failure);
        }
    }

    PathAddress getModelControllerResourceAddress(ManagementModel managementModel) {
        if (modelControllerResourceAddress == null) {
            Set<String> hosts = managementModel.getRootResource().getChildrenNames(HOST);
            // if we don't actually have a host name yet (e.g. --empty-host-config) we return null for now.
            if (hosts.isEmpty()) {
                return null;
            }
            String hostName = hosts.iterator().next();
            modelControllerResourceAddress = PathAddress.pathAddress(HOST, hostName).append(MODEL_CONTROLLER_ADDRESS);
        }
        return modelControllerResourceAddress;
    }

    private class DefaultPrepareStepHandler implements OperationStepHandler {

        @Override
        public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
            if (MGMT_OP_LOGGER.isTraceEnabled()) {
                MGMT_OP_LOGGER.tracef("Executing %s %s", operation.get(OP), operation.get(OP_ADDR));
            }
            final PathAddress address = context.getCurrentAddress();
            final String operationName =  operation.require(OP).asString();
            final OperationEntry stepOperation = resolveOperationHandler(address, operationName);
            if (stepOperation != null) {
                if (!context.isBooting()
                        && stepOperation.getType() == OperationEntry.EntryType.PRIVATE
                        && operation.hasDefined(OPERATION_HEADERS, CALLER_TYPE)
                        && USER.equals(operation.get(OPERATION_HEADERS, CALLER_TYPE).asString())) {
                    // End user trying to invoke a private op. Respond as if there is no such operation
                    context.getFailureDescription().set(ControllerLogger.ROOT_LOGGER.noHandlerForOperation(operationName, address));
                } else {
                    context.addModelStep(stepOperation.getOperationDefinition(), stepOperation.getOperationHandler(), false);
                }
            } else {

                ImmutableManagementResourceRegistration child = managementModel.get().getRootResourceRegistration().getSubModel(address);
                if (child == null) {
                    context.getFailureDescription().set(ControllerLogger.ROOT_LOGGER.noSuchResourceType(address));
                } else {
                    context.getFailureDescription().set(ControllerLogger.ROOT_LOGGER.noHandlerForOperation(operationName, address));
                }
            }
            context.completeStep(new RollbackHandler() {
                @Override
                public void handleRollback(OperationContext context, ModelNode operation) {
                    if (!context.getFailureDescription().isDefined() && context.getAttachment(ReadResourceHandler.ROLLBACKED_FAILURE_DESC) != null) {
                        context.getFailureDescription().set(context.getAttachment(ReadResourceHandler.ROLLBACKED_FAILURE_DESC));
                    }
                }
            });
        }
    }

    private OperationEntry resolveOperationHandler(final PathAddress address, final String operationName) {
        ManagementResourceRegistration rootRegistration = managementModel.get().getRootResourceRegistration();
        OperationEntry result = rootRegistration.getOperationEntry(address, operationName);
        if (result == null && address.size() > 0) {
            // For wildcard elements, check specific registrations where the same OSH is used
            // for all such registrations
            PathElement pe = address.getLastElement();
            if (pe.isWildcard()) {
                String type = pe.getKey();
                PathAddress parent = address.subAddress(0, address.size() - 1);
                Set<PathElement> children = rootRegistration.getChildAddresses(parent);
                if (children != null) {
                    OperationEntry found = null;
                    for (PathElement child : children) {
                        if (type.equals(child.getKey())) {
                            OperationEntry oe = rootRegistration.getOperationEntry(parent.append(child), operationName);
                            OperationStepHandler osh = oe == null ? null : oe.getOperationHandler();
                            if (osh == null || (found != null && !osh.equals(found.getOperationHandler()))) {
                                // Not all children have the same handler; give up
                                found = null;
                                break;
                            }
                            // We have a candidate OSH
                            found = oe;
                        }
                    }
                    if (found != null) {
                        result = found;
                    }
                }
            }
        }
        return result;
    }

    private boolean isReadOnlyOperation(final ModelNode operation) {
        if (operation.hasDefined(STEPS)) {
            final List<ModelNode> steps = operation.get(STEPS).asList();
            boolean readOnly = !steps.isEmpty();
            for (ModelNode step : steps) {
                readOnly = isReadOnlyOperation(step);
                if (!readOnly) {
                    break;
                }
            }
            return readOnly;
        } else {
            final String operationName = operation.get(OP).asString();
            return STD_READ_OPS.contains(operationName) ||
                    "read-boot-errors".equals(operationName) ||
                    "find-non-progressing-operation".equals(operationName) ||
                    "whoami".equals(operationName) ||
                    "read-log-file".equals(operationName);
        }
    }

    private static final class BootOperations {
        private final List<ParsedBootOp> initialOps;
        private final List<ParsedBootOp> postExtensionOps;
        private final boolean invalid;

        private BootOperations(List<ParsedBootOp> initialOps, List<ParsedBootOp> postExtensionOps, boolean invalid) {
            this.initialOps = initialOps;
            this.postExtensionOps = postExtensionOps;
            this.invalid = invalid;
        }
    }

    private final class ModelControllerResource extends PlaceholderResource.PlaceholderResourceEntry {

        private ModelControllerResource() {
            super(SERVICE, MANAGEMENT_OPERATIONS);
        }

        @Override
        public boolean hasChild(PathElement element) {
            try {
                return ACTIVE_OPERATION.equals(element.getKey())
                        && activeOperations.containsKey(Integer.valueOf(element.getValue()));
            } catch (NumberFormatException e) {
                return false;
            }
        }

        @Override
        public Resource getChild(PathElement element) {
            Resource result = null;
            if (ACTIVE_OPERATION.equals(element.getKey())) {
                try {
                    OperationContextImpl context = activeOperations.get(Integer.valueOf(element.getValue()));
                    if (context != null) {
                        result = context.getActiveOperationResource();
                    }
                } catch (NumberFormatException e) {
                    // just return null
                }
            }
            return result;
        }

        @Override
        public Resource requireChild(PathElement element) {
            final Resource resource = getChild(element);
            if(resource == null) {
                throw new NoSuchResourceException(element);
            }
            return resource;
        }

        @Override
        public boolean hasChildren(String childType) {
            return ACTIVE_OPERATION.equals(childType) && activeOperations.size() > 0;
        }

        @Override
        public Set<String> getChildTypes() {
            return Collections.singleton(ACTIVE_OPERATION);
        }

        @Override
        public Set<String> getChildrenNames(String childType) {
            Set<String> result = new HashSet<String>(activeOperations.size());
            for (Integer id : activeOperations.keySet()) {
                result.add(id.toString());
            }
            return result;
        }

        @Override
        public Set<ResourceEntry> getChildren(String childType) {
            Set<ResourceEntry> result = new HashSet<ResourceEntry>(activeOperations.size());
            for (OperationContextImpl context : activeOperations.values()) {
                result.add(context.getActiveOperationResource());
            }
            return result;
        }
    }

    private static class MutableRootResourceRegistrationProviderImpl implements MutableRootResourceRegistrationProvider {
        private static final MutableRootResourceRegistrationProvider INSTANCE = new MutableRootResourceRegistrationProviderImpl();

        @Override
        public ManagementResourceRegistration getRootResourceRegistrationForUpdate(OperationContext context) {
            assert context instanceof AbstractOperationContext;
            return ((AbstractOperationContext) context).getRootResourceRegistrationForUpdate();
        }
    }

    final class ManagementModelImpl implements ManagementModel {
        // The root MRR
        private final ManagementResourceRegistration resourceRegistration;
        // The possibly unpublished root Resource
        private final Resource rootResource;
        // The root MRR we expose
        private final ManagementResourceRegistration delegatingResourceRegistration;
        // The root Resource we expose
        private final Resource delegatingResource;
        // The capability registry
        private final CapabilityRegistry capabilityRegistry;

        private volatile boolean published;

        ManagementModelImpl(final ManagementResourceRegistration resourceRegistration,
                            final Resource rootResource,
                            final CapabilityRegistry capabilityRegistry) {
            this.resourceRegistration = resourceRegistration;
            this.rootResource = rootResource;
            assert capabilityRegistry != null;
            this.capabilityRegistry = capabilityRegistry;
            // What we expose depends on the state of our 'published' field. If 'true' we've been published
            // to the ModelController, and from then on callers should get whatever the MC has as current.
            // If 'false' we haven't been published; we are a local copy created by some OperationContext,
            // so callers should see our local members

            // TODO use this if we ever actually support cloning the MRR
//            this.delegatingResourceRegistration = new DelegatingManagementResourceRegistration(new DelegatingManagementResourceRegistration.RegistrationDelegateProvider() {
//                @Override
//                public ManagementResourceRegistration getDelegateRegistration() {
//                    ManagementResourceRegistration result;
//                    if (published) {
//                        result = ModelControllerImpl.this.managementModel.get().resourceRegistration;
//                    } else {
//                        result = resourceRegistration;
//                    }
//                    return result;
//                }
//            });
            this.delegatingResourceRegistration = resourceRegistration;
            this.delegatingResource = new DelegatingResource(new DelegatingResource.ResourceDelegateProvider() {
                @Override
                public Resource getDelegateResource() {
                    Resource result;
                    if (published) {
                        result = ModelControllerImpl.this.managementModel.get().rootResource;
                    } else {
                        result = rootResource;
                    }
                    return result;
                }
            });
        }

        @Override
        public ManagementResourceRegistration getRootResourceRegistration() {
            return delegatingResourceRegistration;
        }

        @Override
        public Resource getRootResource() {
            return delegatingResource;
        }

        @Override
        public RuntimeCapabilityRegistry getCapabilityRegistry() {
            return capabilityRegistry;
        }

        /**
         * Creates a new {@code ManagementModelImpl} that uses a clone of this one's root {@link ManagementResourceRegistration}.
         * The caller can safely modify that {@code ManagementResourceRegistration} without changes being exposed
         * to other callers. Use {@link ModelControllerImpl#writeModel(ManagementModelImpl, Set, boolean, boolean, boolean)}
         * to publish changes.
         *
         * @return the new {@code ManagementModelImpl}. Will not return {@code null}
         */
        /* TODO enable this if we ever support actually cloning the MRR
        ManagementModelImpl cloneRootResourceRegistration() {
            ManagementResourceRegistration mrr;
            Resource currentResource;
            CapabilityRegistryImpl currentCaps;
            if (published) {
                // This is the first clone since this was published. Use the current stuff as the basis
                // to ensure that the clone is based on the latest even if we are not the latest.
                ManagementModelImpl currentPublished = ModelControllerImpl.this.managementModel.get();
                mrr = currentPublished.resourceRegistration;
                currentResource = currentPublished.rootResource;
            } else {
                // We've already been cloned, which means the thread calling this has the controller lock
                // and our stuff hasn't been superceded by another thread. So use our stuff
                mrr = resourceRegistration;
                currentResource = rootResource;
            }
            ManagementResourceRegistration clone = mrr.clone();
            ManagementModelImpl result = new ManagementModelImpl(clone, currentResource, currentCaps);
            ControllerLogger.MGMT_OP_LOGGER.tracef("cloned to %s to create %s and %s", mrr, clone, result);
            return result;
        }
        */

        /**
         * Creates a new {@code ManagementModelImpl} that uses a clone of this one's root {@link Resource}.
         * The caller can safely modify that {@code Resource} without changes being exposed
         * to other callers. Use {@link ModelControllerImpl#writeModel(ManagementModelImpl, Set, boolean, boolean, boolean)}
         * to publish changes.
         *
         * @return the new {@code ManagementModelImpl}. Will not return {@code null}
         */
        ManagementModelImpl cloneRootResource() {
            ManagementResourceRegistration mrr;
            Resource currentResource;
            CapabilityRegistry currentCaps;
            if (published) {
                // This is the first clone since this was published. Use the current stuff as the basis
                // to ensure that the clone is based on the latest even if we are not the latest.
                ManagementModelImpl currentPublished = ModelControllerImpl.this.managementModel.get();
                mrr = currentPublished.resourceRegistration;
                currentResource = currentPublished.rootResource;
                currentCaps = currentPublished.capabilityRegistry;
            } else {
                // We've already been cloned, which means the thread calling this has the controller lock
                // and our stuff hasn't been superceded by another thread. So use our stuff
                mrr = resourceRegistration;
                currentResource = rootResource;
                currentCaps = capabilityRegistry;
            }
            Resource clone = currentResource.clone();
            ManagementModelImpl result = new ManagementModelImpl(mrr, clone, currentCaps);
            ControllerLogger.MGMT_OP_LOGGER.tracef("cloned to %s to create %s and %s", currentResource, clone, result);
            return result;
        }

        /**
         * Compares the registered requirements to the registered capabilities, returning any missing
         * or inconsistent requirements.
         *
         * @param forceCheck  {@code true} if a full validation should be performed regardless of whether
         *                    any changes have occurred since the last check
         * @param hostXmlOnly {@code true} if a Host Controller boot is occurring and only host model data is present
         *
         * @return a validation result object. Will not return {@code null}
         */
      CapabilityRegistry.CapabilityValidation validateCapabilityRegistry(boolean forceCheck, boolean hostXmlOnly) {
          if (!published || capabilityRegistry.isModified() || forceCheck) {
                return capabilityRegistry.resolveCapabilities(getRootResource(), hostXmlOnly);
            } else {
                // we're unmodified so nothing to validate
                return CapabilityRegistry.CapabilityValidation.OK;
            }
        }
        private void publish() {
            ModelControllerImpl.this.managementModel.set(this);
            published = true;
            ControllerLogger.MGMT_OP_LOGGER.tracef("published %s", this);
        }

        private void discard() {
            // We don't actually "discard". What we do is mark ourselves as published
            // without actually publishing. The result is calls against this object
            // will now see the value of ModelControllerImpl.this.managementModel.get,
            // which will be
            published = true;
            // Don't roll back the capability registry here; let that happen via finally block calls to MCI.discardModel
            // capabilityRegistry.rollback();
            ControllerLogger.MGMT_OP_LOGGER.tracef("discarded %s", this);
        }
    }

    private static class OperationResponseImpl implements OperationResponse {

        private final ModelNode simpleResponse;
        private final Map<String, StreamEntry> inputStreams;

        private OperationResponseImpl(ModelNode simpleResponse, Map<String, StreamEntry> inputStreams) {
            this.simpleResponse = simpleResponse;
            this.inputStreams = inputStreams;
            // TODO doing this here isn't so nice
            ModelNode header = simpleResponse.get(RESPONSE_HEADERS, ATTACHED_STREAMS);
            header.setEmptyList();
            for (StreamEntry entry : inputStreams.values()) {
                ModelNode streamNode = new ModelNode();
                streamNode.get(UUID).set(entry.getUUID());
                streamNode.get(MIME_TYPE).set(entry.getMimeType());
                header.add(streamNode);
            }
        }

        @Override
        public ModelNode getResponseNode() {
            return simpleResponse;
        }

        @Override
        public List<OperationResponse.StreamEntry> getInputStreams() {
            return new ArrayList<>(inputStreams.values());
        }

        @Override
        public StreamEntry getInputStream(String uuid) {
            return inputStreams.get(uuid);
        }

        @Override
        public void close() throws IOException {
            int i = 0;
            for (OperationResponse.StreamEntry is : inputStreams.values()) {
                try {
                    is.getStream().close();
                } catch (Exception e) {
                    ControllerLogger.MGMT_OP_LOGGER.debugf(e, "Failed closing response stream at index %d", i);
                }
                i++;
            }
        }
    }
}
