/* * JBoss, Home of Professional Open Source. * Copyright 2014, 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.domain.controller.operations; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD_INDEX; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.COMPOSITE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CORE_SERVICE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOYMENT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOYMENT_OVERLAY; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.EXTENSION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILURE_DESCRIPTION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.INTERFACE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MANAGEMENT_CLIENT_CONTENT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.NAME; 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.PATH; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PROFILE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REMOVE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REQUEST_PROPERTIES; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SERVER_GROUP; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SOCKET_BINDING_GROUP; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.STEPS; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SYNC_REMOVED_FOR_READD; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SYSTEM_PROPERTY; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.UNDEFINE_ATTRIBUTE_OPERATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.WRITE_ATTRIBUTE_OPERATION; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.ListIterator; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import org.jboss.as.controller.ModelController; import org.jboss.as.controller.OperationContext; import org.jboss.as.controller.OperationFailedException; import org.jboss.as.controller.OperationStepHandler; import org.jboss.as.controller.PathAddress; import org.jboss.as.controller.PathElement; import org.jboss.as.controller.client.helpers.Operations; import org.jboss.as.controller.descriptions.DescriptionProvider; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MANAGEMENT_MAJOR_VERSION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MANAGEMENT_MICRO_VERSION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MANAGEMENT_MINOR_VERSION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.NAMESPACES; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PRODUCT_NAME; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PRODUCT_VERSION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RELEASE_CODENAME; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RELEASE_VERSION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SCHEMA_LOCATIONS; import org.jboss.as.controller.logging.ControllerLogger; import org.jboss.as.controller.operations.common.OrderedChildTypesAttachment; import org.jboss.as.controller.operations.common.Util; import org.jboss.as.controller.registry.AttributeAccess; import org.jboss.as.controller.registry.ImmutableManagementResourceRegistration; import org.jboss.as.controller.registry.Resource; import org.jboss.as.domain.controller.logging.DomainControllerLogger; import org.jboss.as.domain.controller.operations.deployment.SyncModelParameters; import org.jboss.as.host.controller.mgmt.HostControllerRegistrationHandler; import org.jboss.dmr.ModelNode; /** * Internal {@code OperationStepHandler} which synchronizes the model based on a comparison of local and remote operations. * * Basically it compares the current state of the model to the one from the master. Where the initial connection to the * master tries to sync the whole model, fetching missing configuration only looks at server-groups and it's references, * ignoring all other resources. * * @author Emanuel Muckenhuber */ class SyncModelOperationHandler implements OperationStepHandler { private final Resource remoteModel; private final List<ModelNode> localOperations; private final Set<String> missingExtensions; private final SyncModelParameters parameters; private final OrderedChildTypesAttachment localOrderedChildTypes; private static final Set<String> ROOT_ATTRIBUTES = new HashSet<>(Arrays.asList( MANAGEMENT_MAJOR_VERSION, MANAGEMENT_MINOR_VERSION, MANAGEMENT_MICRO_VERSION, PRODUCT_NAME, PRODUCT_VERSION, RELEASE_CODENAME, RELEASE_VERSION, NAMESPACES, NAME, SCHEMA_LOCATIONS)); SyncModelOperationHandler(List<ModelNode> localOperations, Resource remoteModel, Set<String> missingExtensions, SyncModelParameters parameters, OrderedChildTypesAttachment localOrderedChildTypes) { this.localOperations = localOperations; this.remoteModel = remoteModel; this.missingExtensions = missingExtensions; this.parameters = parameters; this.localOrderedChildTypes = localOrderedChildTypes; } public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { // In case we want to automatically ignore extensions we would need to add them before describing the operations // This is also required for resolving the corresponding OperationStepHandler here // final ManagementResourceRegistration registration = context.getResourceRegistrationForUpdate(); // for (String extension : missingExtensions) { // final PathElement element = PathElement.pathElement(EXTENSION, extension); // if (ignoredResourceRegistry.isResourceExcluded(PathAddress.pathAddress(element))) { // continue; // } // context.addResource(PathAddress.pathAddress(element), new ExtensionResource(extension, extensionRegistry)); // initializeExtension(extension, registration); // } // There should be no missing extensions for now, unless they are manually ignored if (!missingExtensions.isEmpty()) { throw DomainControllerLogger.HOST_CONTROLLER_LOGGER.missingExtensions(missingExtensions); } final ModelNode readOp = new ModelNode(); readOp.get(OP).set(ReadMasterDomainOperationsHandler.OPERATION_NAME); readOp.get(OP_ADDR).setEmptyList(); // Describe the operations based on the remote model final ReadMasterDomainOperationsHandler readOperationsHandler = new ReadMasterDomainOperationsHandler(); final HostControllerRegistrationHandler.OperationExecutor operationExecutor = parameters.getOperationExecutor(); final ModelNode result = operationExecutor.executeReadOnly(readOp, remoteModel, readOperationsHandler, ModelController.OperationTransactionControl.COMMIT); if (result.hasDefined(FAILURE_DESCRIPTION)) { context.getFailureDescription().set(result.get(FAILURE_DESCRIPTION)); return; } final List<ModelNode> remoteOperations = result.get(RESULT).asList(); // Create the node models based on the operations final Node currentRoot = new Node(null, PathAddress.EMPTY_ADDRESS); final Node remoteRoot = new Node(null, PathAddress.EMPTY_ADDRESS); // Process the local and remote operations process(currentRoot, localOperations, localOrderedChildTypes); process(remoteRoot, remoteOperations, readOperationsHandler.getOrderedChildTypes()); // Compare the nodes and create the operations to sync the model //final List<ModelNode> operations = new ArrayList<>(); OrderedOperationsCollection operations = new OrderedOperationsCollection(context); processAttributes(currentRoot, remoteRoot, operations, context.getRootResourceRegistration()); processChildren(currentRoot, remoteRoot, operations, context.getRootResourceRegistration()); //Process root domain attributes manually as those are read-only if(context.getCurrentAddress().size() == 0) { Resource rootResource = context.readResourceForUpdate(PathAddress.EMPTY_ADDRESS); ModelNode rootModel = rootResource.getModel().clone(); ModelNode remoteRootModel = remoteModel.getModel(); ROOT_ATTRIBUTES.stream().filter((attributeName) -> (remoteRootModel.hasDefined(attributeName))).forEach((attributeName) -> { rootModel.get(attributeName).set(remoteRootModel.get(attributeName)); }); rootResource.writeModel(rootModel); } // Reverse, since we are adding the steps on top of the queue final List<ModelNode> ops = operations.getReverseList(); for (final ModelNode op : ops) { final String operationName = op.require(OP).asString(); final PathAddress address = PathAddress.pathAddress(op.require(OP_ADDR)); if (parameters.getIgnoredResourceRegistry().isResourceExcluded(address)) { continue; } // Ignore all extension:add operations, since we've added them before // if (address.size() == 1 && EXTENSION.equals(address.getElement(0).getKey()) && ADD.equals(operationName)) { // continue; // } final ImmutableManagementResourceRegistration rootRegistration = context.getRootResourceRegistration(); final OperationStepHandler stepHandler = rootRegistration.getOperationHandler(address, operationName); if(stepHandler != null) { context.addStep(op, stepHandler, OperationContext.Stage.MODEL, true); } else { final ImmutableManagementResourceRegistration child = rootRegistration.getSubModel(address); if (child == null) { context.getFailureDescription().set(ControllerLogger.ROOT_LOGGER.noSuchResourceType(address)); } else { context.getFailureDescription().set(ControllerLogger.ROOT_LOGGER.noHandlerForOperation(operationName, address)); } } } if (!context.isBooting() && operations.getAllOps().size() > 0 && parameters.isFullModelTransfer()) { //Only do this is if it is a full model transfer as a result of a _reconnect_ to the DC. //When fetching missing configuration while connected, the servers will get put into reload-required as a // result of changing the server-group, profile or the socket-binding-group context.addStep(new SyncServerStateOperationHandler(parameters, operations.getAllOps()), OperationContext.Stage.MODEL, true); } } private void processAttributes(final Node current, final Node remote, final OrderedOperationsCollection operations, final ImmutableManagementResourceRegistration registration) { for (final String attribute : remote.attributes.keySet()) { // Remove from current model final ModelNode currentOp = current.attributes.remove(attribute); if (currentOp == null) { operations.add(remote.attributes.get(attribute)); } else { final ModelNode remoteOp = remote.attributes.get(attribute); if (!remoteOp.equals(currentOp)) { operations.add(remoteOp); } } } // Undefine operations if the remote write-attribute operation does not exist for (final String attribute : current.attributes.keySet()) { final ModelNode op = Operations.createUndefineAttributeOperation(current.address.toModelNode(), attribute); operations.add(op); } } private void processChildren(final Node current, final Node remote, final OrderedOperationsCollection operations, final ImmutableManagementResourceRegistration registration) { ChildContext childContext = ChildContext.create(current, remote); for (String type : childContext.orderedInsertCapableTypes) { processOrderedChildrenOfType(childContext, type, operations, registration, true); } for (String type : childContext.orderedNotInsertCapableTypes) { processOrderedChildrenOfType(childContext, type, operations, registration, false); } for (String type : childContext.nonOrderedTypes) { processNonOrderedChildrenOfType(childContext, type, operations, registration); } } private void processOrderedChildrenOfType(final ChildContext childContext, final String type, final OrderedOperationsCollection operations, final ImmutableManagementResourceRegistration registration, boolean attemptInsert) { final Map<PathElement, Node> remoteChildren = childContext.getRemoteChildrenOfType(type); final Map<PathElement, Node> currentChildren = childContext.getCurrentChildrenOfType(type); //Anything which is local only, we can delete right away removeCurrentOnlyChildren(currentChildren, remoteChildren, operations, registration); //Now figure out the merging strategy final Map<PathElement, Integer> currentIndexes = new HashMap<>(); int i = 0; for (PathElement element : currentChildren.keySet()) { currentIndexes.put(element, i++); } Map<Integer, PathElement> addedIndexes = new LinkedHashMap<Integer, PathElement>(); i = 0; int lastCurrent = -1; boolean differentOrder = false; boolean allAddsAtEnd = true; for (PathElement element : remoteChildren.keySet()) { Integer currentIndex = currentIndexes.get(element); if (currentIndex == null) { addedIndexes.put(i, element); if (allAddsAtEnd && i <= currentIndexes.size() - 1) { //Some of the adds are in the middle, requiring an insert or a remove + readd allAddsAtEnd = false; } } else { if (!differentOrder && currentIndex < lastCurrent) { //We can't do inserts, the models have changed too much differentOrder = true; } lastCurrent = currentIndex; } i++; } processOrderedChildModels(currentChildren, remoteChildren, addedIndexes, attemptInsert, differentOrder, allAddsAtEnd, operations, registration); } private void processOrderedChildModels(final Map<PathElement, Node> currentChildren, final Map<PathElement, Node> remoteChildren, Map<Integer, PathElement> addedIndexes, boolean attemptInsert, boolean differentOrder, boolean allAddsAtEnd, final OrderedOperationsCollection operations, final ImmutableManagementResourceRegistration registration) { if (!differentOrder && (addedIndexes.size() == 0 || allAddsAtEnd)) { //Just 'compare' everything for (Node current : currentChildren.values()) { Node remote = remoteChildren.get(current.element); compareExistsInBothModels(current, remote, operations, registration.getSubModel(PathAddress.pathAddress(current.element))); } if (addedIndexes.size() > 0) { //Add the new ones to the end for (PathElement element : addedIndexes.values()) { Node remote = remoteChildren.get(element); addChildRecursive(remote, operations, registration.getSubModel(PathAddress.pathAddress(element))); } } } else { //We had some inserts, add them in order boolean added = false; if (attemptInsert && !differentOrder) { added = true; //Do the insert int i = 0; for (Node remote : remoteChildren.values()) { if (addedIndexes.get(i) != null) { //insert the node remote.add.get(ADD_INDEX).set(i); ImmutableManagementResourceRegistration childReg = registration.getSubModel(PathAddress.pathAddress(remote.element)); if (i == 0) { DescriptionProvider desc = childReg.getOperationDescription(PathAddress.EMPTY_ADDRESS, ADD); if (!desc.getModelDescription(Locale.ENGLISH).hasDefined(REQUEST_PROPERTIES, ADD_INDEX)) { //Although the resource type supports ordering, the add handler was not set up to do an indexed add //so we give up and go to remove + re-add added = false; break; } } addChildRecursive(remote, operations, childReg); } else { //'compare' the nodes Node current = currentChildren.get(remote.element); compareExistsInBothModels(current, remote, operations, registration.getSubModel(PathAddress.pathAddress(current.element))); } i++; } } if (!added) { //Remove and re-add everything //We could do this more fine-grained, but for now let's just drop everything that has been added and readd for (Node current : currentChildren.values()) { removeChildRecursive(current, operations, registration.getSubModel(PathAddress.pathAddress(current.element)), true); } for (Node remote : remoteChildren.values()) { addChildRecursive(remote, operations, registration.getSubModel(PathAddress.pathAddress(remote.element))); } } } } private void processNonOrderedChildrenOfType(final ChildContext childContext, final String type, final OrderedOperationsCollection operations, final ImmutableManagementResourceRegistration registration) { final Map<PathElement, Node> remoteChildren = childContext.getRemoteChildrenOfType(type); final Map<PathElement, Node> currentChildren = childContext.getCurrentChildrenOfType(type); for (final Node remoteChild : remoteChildren.values()) { final Node currentChild = currentChildren == null ? null : currentChildren.remove(remoteChild.element); if (currentChild != null && remoteChild != null) { compareExistsInBothModels(currentChild, remoteChild, operations, registration.getSubModel(PathAddress.pathAddress(currentChild.element))); } else if (currentChild == null && remoteChild != null) { addChildRecursive(remoteChild, operations, registration.getSubModel(PathAddress.pathAddress(remoteChild.element))); } else if (currentChild != null && remoteChild == null) { removeChildRecursive(currentChild, operations, registration.getSubModel(PathAddress.pathAddress(currentChild.element)), false); } else { throw new IllegalStateException(); } } for (final Node currentChild : currentChildren.values()) { removeChildRecursive(currentChild, operations, registration.getSubModel(PathAddress.pathAddress(currentChild.element)), false); } } private void addChildRecursive(Node remote, OrderedOperationsCollection operations, ImmutableManagementResourceRegistration registration) { assert remote != null : "remote cannot be null"; // Just add all the remote operations if (remote.add != null) { operations.add(remote.add); } for (final ModelNode operation : remote.attributes.values()) { operations.add(operation); } for (final ModelNode operation : remote.operations) { operations.add(operation); } // for (final Map.Entry<String, Map<PathElement, Node>> childrenByType : remote.childrenByType.entrySet()) { for (final Node child : childrenByType.getValue().values()) { addChildRecursive(child, operations, registration.getSubModel(PathAddress.pathAddress(child.element))); } } } private void removeCurrentOnlyChildren(Map<PathElement, Node> currentChildren, Map<PathElement, Node> remoteChildren, final OrderedOperationsCollection operations, final ImmutableManagementResourceRegistration registration) { //Remove everything which exists in current and not in the remote List<PathElement> removedElements = new ArrayList<PathElement>(); for (Node node : currentChildren.values()) { if (!remoteChildren.containsKey(node.element)) { removedElements.add(node.element); } } for (PathElement removedElement : removedElements) { Node removedCurrent = currentChildren.remove(removedElement); removeChildRecursive(removedCurrent, operations, registration.getSubModel(PathAddress.pathAddress(removedElement)), false); } } private void compareExistsInBothModels(Node current, Node remote, OrderedOperationsCollection operations, ImmutableManagementResourceRegistration registration) { assert current != null : "current cannot be null"; assert remote != null : "remote cannot be null"; // If the current:add() and remote:add() don't match if (current.add != null && remote.add != null) { if (!current.add.equals(remote.add)) { Map<String, ModelNode> remoteAttributes = new HashMap<>(remote.attributes); boolean dropAndReadd = false; // Iterate through all local attribute names for (String attribute : registration.getAttributeNames(PathAddress.EMPTY_ADDRESS)) { final AttributeAccess access = registration.getAttributeAccess(PathAddress.EMPTY_ADDRESS, attribute); if (access.getStorageType() == AttributeAccess.Storage.CONFIGURATION) { boolean hasCurrent = current.add.hasDefined(attribute); boolean hasRemote = remote.add.hasDefined(attribute); if (access.getAccessType() == AttributeAccess.AccessType.READ_WRITE) { // Compare each attribute if (hasRemote) { // If they are not equals add the remote one if (!hasCurrent || !remote.add.get(attribute).equals(current.add.get(attribute))) { final ModelNode op = Operations.createWriteAttributeOperation(current.address.toModelNode(), attribute, remote.add.get(attribute)); if (remoteAttributes.containsKey(attribute)) { throw new IllegalStateException(); } remoteAttributes.put(attribute, op); } } else if (hasCurrent) { // If there is no remote equivalent undefine the operation final ModelNode op = Operations.createUndefineAttributeOperation(current.address.toModelNode(), attribute); if (remoteAttributes.containsKey(attribute)) { throw new IllegalStateException(); } remoteAttributes.put(attribute, op); } } else if (access.getAccessType() == AttributeAccess.AccessType.READ_ONLY) { ModelNode currentValue = hasCurrent ? current.add.get(attribute) : new ModelNode(); ModelNode removeValue = hasRemote ? remote.add.get(attribute) : new ModelNode(); if (!currentValue.equals(removeValue)) { //The adds differ in a read-only attribute's value. Since we cannot write to it, //we need to drop it and add it again dropAndReadd = true; break; } } } } if (dropAndReadd) { removeChildRecursive(current, operations, registration.getSubModel(PathAddress.EMPTY_ADDRESS), true); addChildRecursive(remote, operations, registration.getSubModel(PathAddress.EMPTY_ADDRESS)); } else { remote.attributes.putAll(remoteAttributes); } } // Process the attributes processAttributes(current, remote, operations, registration); // TODO process other operations maps, lists etc. // Process the children processChildren(current, remote, operations, registration); } } private void removeChildRecursive(Node current, OrderedOperationsCollection operations, ImmutableManagementResourceRegistration registration, boolean dropForReadd) { //The remove operations get processed in reverse order by the operations collection, so add the parent //remove before the child remove if (registration.getOperationHandler(PathAddress.EMPTY_ADDRESS, REMOVE) != null) { final ModelNode op = Operations.createRemoveOperation(current.address.toModelNode()); if (dropForReadd) { op.get(OPERATION_HEADERS, SYNC_REMOVED_FOR_READD).set(true); } operations.add(op); } for (final Map.Entry<String, Map<PathElement, Node>> childrenByType : current.childrenByType.entrySet()) { for (final Node child : childrenByType.getValue().values()) { removeChildRecursive(child, operations, registration.getSubModel(PathAddress.pathAddress(child.element)), dropForReadd); } } } private void process(Node rootNode, final List<ModelNode> operations, OrderedChildTypesAttachment orderedChildTypesAttachment) { for (final ModelNode operation : operations) { final String operationName = operation.get(OP).asString(); final PathAddress address = PathAddress.pathAddress(operation.require(OP_ADDR)); final Node node; if (address.size() == 0) { node = rootNode; } else { node = rootNode.getOrCreate(null, address.iterator(), PathAddress.EMPTY_ADDRESS, orderedChildTypesAttachment); } if (operationName.equals(ADD)) { node.add = operation; } else if (operationName.equals(WRITE_ATTRIBUTE_OPERATION)) { final String name = operation.get(NAME).asString(); node.attributes.put(name, operation); } else { node.operations.add(operation); } } } private static class Node { private final PathElement element; private final PathAddress address; private ModelNode add; private Map<String, ModelNode> attributes = new HashMap<>(); private final List<ModelNode> operations = new ArrayList<>(); private final Set<String> orderedChildTypes = new HashSet<>(); private final Map<String, Map<PathElement, Node>> childrenByType; private Node(PathElement element, PathAddress address) { this.element = element; this.address = address; this.childrenByType = element == null ? new TreeMap<>(ROOT_NODE_COMPARATOR) : // The root node uses a pre-defined order new LinkedHashMap<>(); } Node getOrCreate(final PathElement element, final Iterator<PathElement> i, PathAddress current, OrderedChildTypesAttachment orderedChildTypesAttachment) { if (i.hasNext()) { final PathElement next = i.next(); final PathAddress addr = current.append(next); Map<PathElement, Node> children = childrenByType.get(next.getKey()); if (children == null) { children = new LinkedHashMap<PathElement, SyncModelOperationHandler.Node>(); childrenByType.put(next.getKey(), children); } Node node = children.get(next); if (node == null) { node = new Node(next, addr); children.put(next, node); Set<String> orderedChildTypes = orderedChildTypesAttachment.getOrderedChildTypes(addr); if (orderedChildTypes != null) { node.orderedChildTypes.addAll(orderedChildTypes); } } return node.getOrCreate(next, i, addr, orderedChildTypesAttachment); } else if (element == null) { throw new IllegalStateException(); } else { if (address.equals(current)) { return this; } else { throw new IllegalStateException(current.toString()); } } } public String toString() { StringBuilder sb = new StringBuilder(); toString(sb, 0); return sb.toString(); } void toString(StringBuilder builder, int depth) { for (int i = 0; i < depth; i++) { builder.append(" "); } builder.append("Node: {").append(address).append("\n"); for (Map<PathElement, Node> children : childrenByType.values()) { for (Node child : children.values()) { child.toString(builder, depth + 1); } } for (int i = 0; i < depth; i++) { builder.append(" "); } builder.append("}\n"); } Set<String> createNewChildSet() { if (element == null) { return new TreeSet<>(ROOT_NODE_COMPARATOR); } else { return new HashSet<>(); } } } private static class ChildContext { private final Node current; private final Node remote; /** These can be inserted by doing an add with an index */ private final Set<String> orderedInsertCapableTypes; /** These can not be adjusted by doing an insert with an index, instead it will need to remove everything after the index and re-add */ private final Set<String> orderedNotInsertCapableTypes; /** These types are not ordered */ private final Set<String> nonOrderedTypes; private ChildContext( Node current, Node remote, Set<String> orderedInsertCapableTypes, Set<String> orderedNotInsertCapableTypes, Set<String> nonOrderedTypes) { this.current = current; this.remote = remote; this.orderedInsertCapableTypes = orderedInsertCapableTypes; this.orderedNotInsertCapableTypes = orderedNotInsertCapableTypes; this.nonOrderedTypes = nonOrderedTypes; } static ChildContext create(Node current, Node remote) { final Set<String> orderedInsertCapableTypes = getOrderedInsertCapable(current); final Set<String> orderedNotInsertCapableTypes = getOrderedNotInsertCapable(current, remote); Set<String> nonOrderedTypes = null; if (current != null) { nonOrderedTypes = current.createNewChildSet(); for (String type : current.childrenByType.keySet()) { if (!orderedInsertCapableTypes.contains(type) && !orderedNotInsertCapableTypes.contains(type)) { nonOrderedTypes.add(type); } } } if (remote != null) { if (nonOrderedTypes == null) { nonOrderedTypes = remote.createNewChildSet(); } for (String type : remote.childrenByType.keySet()) { if (!orderedInsertCapableTypes.contains(type) && !orderedNotInsertCapableTypes.contains(type)) { nonOrderedTypes.add(type); } } } return new ChildContext(current, remote, orderedInsertCapableTypes, orderedNotInsertCapableTypes, nonOrderedTypes); } private static Set<String> getOrderedInsertCapable(Node current) { if (current.orderedChildTypes.size() > 0) { return new HashSet<String>(current.orderedChildTypes); } else { return Collections.emptySet(); } } private static Set<String> getOrderedNotInsertCapable(Node current, Node remote) { if (remote.orderedChildTypes.size() > 0) { HashSet<String> orderedNotInsertCapable = null; for (String type : remote.orderedChildTypes) { if (!current.orderedChildTypes.contains(type)) { if (orderedNotInsertCapable == null) { orderedNotInsertCapable = new HashSet<String>(); } orderedNotInsertCapable.add(type); } } if (orderedNotInsertCapable != null) { return orderedNotInsertCapable; } } return Collections.emptySet(); } Map<PathElement, Node> getRemoteChildrenOfType(String type) { Map<PathElement, Node> map = remote.childrenByType.get(type); if (map != null) { return map; } return Collections.emptyMap(); } Map<PathElement, Node> getCurrentChildrenOfType(String type) { Map<PathElement, Node> map = current.childrenByType.get(type); if (map != null) { return map; } return Collections.emptyMap(); } } private static final class OrderedOperationsCollection { private final List<ModelNode> extensionAdds = new ArrayList<>(); private final List<ModelNode> nonExtensionAdds = new ArrayList<>(); private final List<ModelNode> extensionRemoves = new ArrayList<>(); private final List<ModelNode> nonExtensionRemoves = new ArrayList<>(); private final List<ModelNode> allOps = new ArrayList<>(); private final boolean booting; OrderedOperationsCollection(OperationContext context) { this.booting = context.isBooting(); } void add(ModelNode op) { final String name = op.require(OP).asString(); final PathAddress addr = PathAddress.pathAddress(op.require(OP_ADDR)); final String type = addr.size() == 0 ? "" : addr.getElement(0).getKey(); if (name.equals(ADD) || name.equals(WRITE_ATTRIBUTE_OPERATION) || name.equals(UNDEFINE_ATTRIBUTE_OPERATION)) { if (type.equals(EXTENSION)) { extensionAdds.add(op); } else if (type.equals(MANAGEMENT_CLIENT_CONTENT)) { //Only add this on boot, since it is a 'hard-coded' one //Otherwise, it will be added to the allOps further below which is used by SyncServerStateOperationHandler //which will drop/re-add the custom resource as needed if necessary if (booting) { nonExtensionAdds.add(op); } } else { nonExtensionAdds.add(op); } } else if (name.equals(REMOVE)) { if (type.equals(EXTENSION)) { extensionRemoves.add(op); } else { nonExtensionRemoves.add(op); } } else { assert false : "Unknown operation " + name; } allOps.add(op); } List<ModelNode> getReverseList() { //This is the opposite order. Due to how the steps get added, once run we will do them in the following order: // extension removes, extension adds, non-extension composite // The non-extension composite in turn will do removes first, and then adds final List<ModelNode> result = new ArrayList<>(); final ModelNode nonExtensionComposite = Util.createEmptyOperation(COMPOSITE, PathAddress.EMPTY_ADDRESS); final ModelNode nonExtensionSteps = nonExtensionComposite.get(STEPS).setEmptyList(); final ListIterator<ModelNode> it = nonExtensionRemoves.listIterator(nonExtensionRemoves.size()); while (it.hasPrevious()) { nonExtensionSteps.add(it.previous()); } for (ModelNode op : nonExtensionAdds) { nonExtensionSteps.add(op); } if (nonExtensionSteps.asList().size() > 0) { result.add(nonExtensionComposite); } result.addAll(extensionAdds); result.addAll(extensionRemoves); return result; } List<ModelNode> getAllOps() { return allOps; } } private static final Comparator<String> ROOT_NODE_COMPARATOR = new Comparator<String>() { private final Map<String, Integer> orderedChildTypes; { //The order here is important for the direct children of the root resource String[] orderedTypes = new String[] {EXTENSION, //Extensions need to be done before everything else (and separately WFCORE-323) SYSTEM_PROPERTY, //Everything might use system properties PATH, //A lot of later stuff might need paths CORE_SERVICE, PROFILE, //Used by server-group INTERFACE, //Used by socket-binding-group SOCKET_BINDING_GROUP, //Used by server-group; needs interface DEPLOYMENT, //Used by server-group DEPLOYMENT_OVERLAY, //Used by server-group MANAGEMENT_CLIENT_CONTENT, //Used by server-group SERVER_GROUP}; //Uses profile, socket-binding-group, deployment, deployment-overlay and management-client-content Map<String, Integer> map = new HashMap<>(); for (int i = 0 ; i < orderedTypes.length ; i++) { map.put(orderedTypes[i], i); } orderedChildTypes = Collections.unmodifiableMap(map); } @Override public int compare(String type1, String type2) { if (type1.equals(type2)) { return 0; } if (getIndex(type1) < getIndex(type2)) { return -1; } return 1; } private int getIndex(String type) { Integer i = orderedChildTypes.get(type); if (i != null) { return i; } return -1; } }; }