/* * JBoss, Home of Professional Open Source. * Copyright 2011, Red Hat Middleware LLC, 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.subsystem.test; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILURE_DESCRIPTION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REMOVE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringReader; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.ServiceLoader; import java.util.Set; import javax.xml.namespace.QName; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import org.jboss.as.controller.AttributeDefinition; import org.jboss.as.controller.CompositeOperationHandler; import org.jboss.as.controller.Extension; import org.jboss.as.controller.ModelVersion; import org.jboss.as.controller.NotificationDefinition; import org.jboss.as.controller.OperationDefinition; 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.ProcessType; import org.jboss.as.controller.ProxyController; import org.jboss.as.controller.ResourceDefinition; import org.jboss.as.controller.RunningMode; import org.jboss.as.controller.RunningModeControl; import org.jboss.as.controller.access.management.AccessConstraintDefinition; import org.jboss.as.controller.capability.RuntimeCapability; import org.jboss.as.controller.client.helpers.Operations; import org.jboss.as.controller.descriptions.DescriptionProvider; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; import org.jboss.as.controller.descriptions.OverrideDescriptionProvider; import org.jboss.as.controller.extension.ExtensionRegistry; import org.jboss.as.controller.extension.ExtensionRegistryType; import org.jboss.as.controller.extension.RuntimeHostControllerInfoAccessor; import org.jboss.as.controller.extension.SubsystemInformation; import org.jboss.as.controller.operations.common.Util; import org.jboss.as.controller.parsing.ExtensionParsingContext; import org.jboss.as.controller.persistence.ConfigurationPersister; import org.jboss.as.controller.registry.AliasEntry; import org.jboss.as.controller.registry.AttributeAccess; import org.jboss.as.controller.registry.ImmutableManagementResourceRegistration; import org.jboss.as.controller.registry.ManagementResourceRegistration; import org.jboss.as.controller.registry.NotificationEntry; import org.jboss.as.controller.registry.OperationEntry; import org.jboss.as.controller.registry.OperationEntry.Flag; import org.jboss.as.controller.registry.Resource; import org.jboss.as.controller.transform.ExtensionTransformerRegistration; import org.jboss.as.controller.transform.OperationTransformer.TransformedOperation; import org.jboss.as.model.test.ChildFirstClassLoaderBuilder; import org.jboss.as.model.test.EAPRepositoryReachableUtil; import org.jboss.as.model.test.ModelFixer; import org.jboss.as.model.test.ModelTestBootOperationsBuilder; import org.jboss.as.model.test.ModelTestControllerVersion; import org.jboss.as.model.test.ModelTestModelControllerService; import org.jboss.as.model.test.ModelTestOperationValidatorFilter; import org.jboss.as.model.test.ModelTestOperationValidatorFilter.Action; import org.jboss.as.model.test.ModelTestParser; import org.jboss.as.model.test.ModelTestUtils; import org.jboss.as.model.test.OperationFixer; import org.jboss.as.model.test.StringConfigurationPersister; import org.jboss.as.subsystem.bridge.impl.LegacyControllerKernelServicesProxy; import org.jboss.as.subsystem.bridge.local.ScopedKernelServicesBootstrap; import org.jboss.as.subsystem.test.ModelDescriptionValidator.ValidationConfiguration; import org.jboss.dmr.ModelNode; import org.jboss.modules.filter.ClassFilter; import org.jboss.staxmapper.XMLElementReader; import org.jboss.staxmapper.XMLMapper; import org.junit.Assert; import org.junit.Assume; import org.wildfly.legacy.test.spi.Version; /** * * @author <a href="kabir.khan@jboss.com">Kabir Khan</a> */ final class SubsystemTestDelegate { private static final String TEST_OLD_LEGACY = "jboss.test.transformers.subsystem.old"; private static final String TEST_NAMESPACE = "urn.org.jboss.test:1.0"; private static final ModelNode SUCCESS; static{ SUCCESS = new ModelNode(); SUCCESS.get(ModelDescriptionConstants.OUTCOME).set(ModelDescriptionConstants.SUCCESS); SUCCESS.get(ModelDescriptionConstants.RESULT); SUCCESS.protect(); } private final Class<?> testClass; private final List<KernelServices> kernelServices = new ArrayList<KernelServices>(); protected final String mainSubsystemName; private final Extension mainExtension; private final Comparator<PathAddress> removeOrderComparator; /** * ExtensionRegistry we use just for registering parsers. * The ModelControllerService uses a separate registry. This is done this way to allow multiple ModelControllerService * instantiations in the same test without having to re-initialize the parsers. */ private ExtensionRegistry extensionParsingRegistry; private ModelTestParser testParser; private boolean addedExtraParsers; private XMLMapper xmlMapper; /** * Creates a new delegate. * * @param testClass the test class * @param mainSubsystemName the name of the subsystem * @param mainExtension the extension to test * @param removeOrderComparator a comparator to sort addresses when removing the subsystem, {@code null} if order * doesn't matter */ SubsystemTestDelegate(final Class<?> testClass, final String mainSubsystemName, final Extension mainExtension, final Comparator<PathAddress> removeOrderComparator) { this.testClass = testClass; this.mainSubsystemName = mainSubsystemName; this.mainExtension = mainExtension; this.removeOrderComparator = removeOrderComparator; } String getMainSubsystemName() { return mainSubsystemName; } void initializeParser() throws Exception { //Initialize the parser xmlMapper = XMLMapper.Factory.create(); extensionParsingRegistry = new ExtensionRegistry(getProcessType(), new RunningModeControl(RunningMode.NORMAL), null, null, null, RuntimeHostControllerInfoAccessor.SERVER); testParser = new TestParser(mainSubsystemName, extensionParsingRegistry); xmlMapper.registerRootElement(new QName(TEST_NAMESPACE, "test"), testParser); mainExtension.initializeParsers(extensionParsingRegistry.getExtensionParsingContext("Test", xmlMapper)); addedExtraParsers = false; } void cleanup() throws Exception { for (KernelServices kernelServices : this.kernelServices) { try { kernelServices.shutdown(); } catch (Exception e) { //we don't care } } kernelServices.clear(); xmlMapper = null; extensionParsingRegistry = null; testParser = null; } Extension getMainExtension() { return mainExtension; } /** * Parse the subsystem xml and create the operations that will be passed into the controller * * @param subsystemXml the subsystem xml to be parsed * @return the created operations * @throws XMLStreamException if there is a parsing problem */ List<ModelNode> parse(String subsystemXml) throws XMLStreamException { return parse(null, subsystemXml); } /** * Parse the subsystem xml and create the operations that will be passed into the controller * * @param additionalParsers additional initialization that should be done to the parsers before initializing our extension. These parsers * will only be initialized the first time this method is called from within a test * @param subsystemXml the subsystem xml to be parsed * @return the created operations * @throws XMLStreamException if there is a parsing problem */ List<ModelNode> parse(AdditionalParsers additionalParsers, String subsystemXml) throws XMLStreamException { String xml = "<test xmlns=\"" + TEST_NAMESPACE + "\">" + subsystemXml + "</test>"; final XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(new StringReader(xml)); addAdditionalParsers(additionalParsers); final List<ModelNode> operationList = new ArrayList<ModelNode>(); xmlMapper.parseDocument(operationList, reader); return operationList; } /** * Output the model to xml * * @param model the model to marshall * @return the xml */ String outputModel(ModelNode model) throws Exception { StringConfigurationPersister persister = new StringConfigurationPersister(Collections.<ModelNode>emptyList(), testParser, true); // Use ProcessType.HOST_CONTROLLER for this ExtensionRegistry so we don't need to provide // a PathManager via the ExtensionContext. All we need the Extension to do here is register the xml writers ExtensionRegistry outputExtensionRegistry = new ExtensionRegistry(ProcessType.HOST_CONTROLLER, new RunningModeControl(RunningMode.NORMAL), null, null, null, RuntimeHostControllerInfoAccessor.SERVER); outputExtensionRegistry.setWriterRegistry(persister); Extension extension = mainExtension.getClass().newInstance(); extension.initialize(outputExtensionRegistry.getExtensionContext("Test", MOCK_RESOURCE_REG, ExtensionRegistryType.SLAVE)); ConfigurationPersister.PersistenceResource resource = persister.store(model, Collections.<PathAddress>emptySet()); resource.commit(); return persister.getMarshalled(); } /** * Initializes the controller and populates the subsystem model from the passed in xml. * * @param subsystemXml the subsystem xml to be parsed * @return the kernel services allowing access to the controller and service container * @deprecated Use {@link #createKernelServicesBuilder(AdditionalInitialization)} instead */ @Deprecated KernelServices installInController(String subsystemXml) throws Exception { return createKernelServicesBuilder(null) .setSubsystemXml(subsystemXml) .build(); } /** * Initializes the controller and populates the subsystem model from the passed in xml. * * @param additionalInit Additional initialization that should be done to the parsers, controller and service container before initializing our extension * @param subsystemXml the subsystem xml to be parsed * @deprecated Use {@link #createKernelServicesBuilder(AdditionalInitialization)} instead */ @Deprecated KernelServices installInController(AdditionalInitialization additionalInit, String subsystemXml) throws Exception { return createKernelServicesBuilder(additionalInit) .setSubsystemXml(subsystemXml) .build(); } /** * Create a new controller with the passed in operations. * * @param bootOperations the operations * @deprecated Use {@link #createKernelServicesBuilder(AdditionalInitialization)} instead */ @Deprecated KernelServices installInController(List<ModelNode> bootOperations) throws Exception { return createKernelServicesBuilder(null) .setBootOperations(bootOperations) .build(); } /** * Create a new controller with the passed in operations. * * @param additionalInit Additional initialization that should be done to the parsers, controller and service container before initializing our extension * @param bootOperations the operations * @deprecated Use {@link #createKernelServicesBuilder(AdditionalInitialization)} instead */ @Deprecated KernelServices installInController(AdditionalInitialization additionalInit, List<ModelNode> bootOperations) throws Exception { return createKernelServicesBuilder(additionalInit) .setBootOperations(bootOperations) .build(); } /** * Creates a new kernel services builder used to create a new controller containing the subsystem being tested * * @param additionalInit Additional initialization that should be done to the parsers, controller and service container before initializing our extension */ KernelServicesBuilder createKernelServicesBuilder(AdditionalInitialization additionalInit) { return new KernelServicesBuilderImpl(additionalInit); } /** * Gets the ProcessType to use when initializing the parsers. Defaults to {@link ProcessType#EMBEDDED_SERVER} * To tweak the process type when installing a controller, override {@link AdditionalInitialization} and pass in to * {@link #createKernelServicesBuilder(AdditionalInitialization)} instead. * * @return the process type */ ProcessType getProcessType() { return ProcessType.EMBEDDED_SERVER; } /** * Checks that the subystem resources can be removed, i.e. that people have registered * working 'remove' operations for every 'add' level. * * @param kernelServices the kernel services used to access the controller */ void assertRemoveSubsystemResources(KernelServices kernelServices) { assertRemoveSubsystemResources(kernelServices, null); } /** * Checks that the subystem resources can be removed, i.e. that people have registered * working 'remove' operations for every 'add' level. * * @param kernelServices the kernel services used to access the controller * @param ignoredChildAddresses child addresses that should not be removed, they are managed by one of the parent resources. * This set cannot contain the subsystem resource itself */ void assertRemoveSubsystemResources(KernelServices kernelServices, Set<PathAddress> ignoredChildAddresses) { if (ignoredChildAddresses == null) { ignoredChildAddresses = Collections.emptySet(); } else { PathAddress subsystem = PathAddress.pathAddress(PathElement.pathElement(SUBSYSTEM, mainSubsystemName)); Assert.assertFalse("Cannot exclude removal of subsystem itself", ignoredChildAddresses.contains(subsystem)); } Resource rootResource = ModelTestModelControllerService.grabRootResource(kernelServices); List<PathAddress> addresses = new ArrayList<PathAddress>(); PathAddress pathAddress = PathAddress.pathAddress(PathElement.pathElement(SUBSYSTEM, mainSubsystemName)); Resource subsystemResource = rootResource.getChild(pathAddress.getLastElement()); Assert.assertNotNull(subsystemResource); addresses.add(pathAddress); getAllChildAddressesForRemove(pathAddress, addresses, subsystemResource); // Remove ignored children removeIgnoredChildren(ignoredChildAddresses, addresses); // If the remove order comparator is not null, then sort the addresses if (removeOrderComparator != null) { Collections.sort(addresses, removeOrderComparator); } ModelNode composite = new ModelNode(); composite.get(OP).set(CompositeOperationHandler.NAME); composite.get(OP_ADDR).setEmptyList(); composite.get("rollback-on-runtime-failure").set(true); for (ListIterator<PathAddress> iterator = addresses.listIterator(addresses.size()); iterator.hasPrevious(); ) { PathAddress cur = iterator.previous(); ModelNode remove = new ModelNode(); remove.get(OP).set(REMOVE); remove.get(OP_ADDR).set(cur.toModelNode()); composite.get("steps").add(remove); } final ModelNode result = kernelServices.executeOperation(composite); Assert.assertTrue(result.get(FAILURE_DESCRIPTION).asString(), Operations.isSuccessfulOutcome(result)); ModelNode model = kernelServices.readWholeModel().get(SUBSYSTEM, mainSubsystemName); Assert.assertFalse("Subsystem resources were not removed " + model, model.isDefined()); } private void getAllChildAddressesForRemove(PathAddress address, List<PathAddress> addresses, Resource resource) { List<PathElement> childElements = new ArrayList<PathElement>(); for (String type : resource.getChildTypes()) { for (String childName : resource.getChildrenNames(type)) { PathElement element = PathElement.pathElement(type, childName); childElements.add(element); } } for (PathElement childElement : childElements) { // Ignore runtime resources if (!resource.getChild(childElement).isRuntime()) { addresses.add(address.append(childElement)); } } for (PathElement childElement : childElements) { // Ignore runtime resources final Resource childResource = resource.getChild(childElement); if (!childResource.isRuntime()) { getAllChildAddressesForRemove(address.append(childElement), addresses, childResource); } } } private void removeIgnoredChildren(final Collection<PathAddress> ignoredChildAddresses, final Collection<PathAddress> addresses) { // Remove all known ignored children addresses.removeAll(ignoredChildAddresses); // Checked for wildcards removals for (PathAddress ignoredChildAddress : ignoredChildAddresses) { final PathElement lastIgnoredElement = ignoredChildAddress.getLastElement(); if (lastIgnoredElement.isWildcard()) { // Check each address for (final Iterator<PathAddress> iterator = addresses.iterator(); iterator.hasNext(); ) { final PathAddress childAddress = iterator.next(); if (childAddress.size() == ignoredChildAddress.size()) { // Check the last element key for a match if (lastIgnoredElement.getKey().equals(childAddress.getLastElement().getKey())) { boolean match = true; // Check for matches on previous elements for (int i = 0; i < ignoredChildAddress.size() - 1; i++) { final PathElement e1 = ignoredChildAddress.getElement(i); final PathElement e2 = childAddress.getElement(i); if (!e1.equals(e2)) { match = false; break; } } if (match) { iterator.remove(); } } } } } } } /** * Dumps the target subsystem resource description to DMR format, needed by TransformerRegistry for non-standard subsystems * * @param kernelServices the kernel services for the started controller * @param modelVersion the target subsystem model version * @deprecated this might no longer be needed following refactoring of TransformerRegistry */ @Deprecated File generateLegacySubsystemResourceRegistrationDmr(KernelServices kernelServices, ModelVersion modelVersion) throws IOException { KernelServices legacy = kernelServices.getLegacyServices(modelVersion); //Generate the org.jboss.as.controller.transform.subsystem-version.dmr file - just use the format used by TransformerRegistry for now PathAddress pathAddress = PathAddress.pathAddress(PathElement.pathElement(SUBSYSTEM, mainSubsystemName)); ModelNode desc = ((KernelServicesInternal)legacy).readFullModelDescription(pathAddress.toModelNode()); File dmrFile = getDmrFile(kernelServices, modelVersion); try (PrintWriter pw = new PrintWriter(Files.newBufferedWriter(dmrFile.toPath(), StandardCharsets.UTF_8))){ desc.writeString(pw, false); //Leave this println - it only gets executed when people generate the legacy dmr files, and is useful to know where it has been written. System.out.println("Legacy resource definition dmr written to: " + dmrFile.getAbsolutePath()); return dmrFile; } } private File getDmrFile(KernelServices kernelServices, ModelVersion modelVersion) { File file = new File("target/test-classes").getAbsoluteFile(); Assert.assertTrue(file.exists()); for (String part : kernelServices.getTestClass().getPackage().getName().split("\\.")) { file = new File(file, part); if (!file.exists()) { file.mkdir(); } } return new File(file, mainSubsystemName + "-" + modelVersion.getMajor() + "." + modelVersion.getMinor() +"."+modelVersion.getMicro()+ ".dmr"); } /** * Checks that the transformed model is the same as the model built up in the legacy subsystem controller via the transformed operations, * and that the transformed model is valid according to the resource definition in the legacy subsystem controller. * * * * @param kernelServices the main kernel services * @param modelVersion the model version of the targeted legacy subsystem * @param legacyModelFixer use to touch up the model read from the legacy controller, use sparingly when the legacy model is just wrong. May be {@code null} * @param includeDefaults whether the legacy controller model and the transformed model should include default values for undefined attributes * @return the whole model of the legacy controller */ ModelNode checkSubsystemModelTransformation(KernelServices kernelServices, ModelVersion modelVersion, ModelFixer legacyModelFixer, boolean includeDefaults) throws IOException, OperationFailedException { ModelNode legacyReadResource = Util.createOperation(ModelDescriptionConstants.READ_RESOURCE_OPERATION, PathAddress.EMPTY_ADDRESS); legacyReadResource.get(ModelDescriptionConstants.RECURSIVE).set(true); legacyReadResource.get(ModelDescriptionConstants.INCLUDE_ALIASES).set(false); legacyReadResource.get(ModelDescriptionConstants.INCLUDE_RUNTIME).set(false); legacyReadResource.get(ModelDescriptionConstants.INCLUDE_DEFAULTS).set(includeDefaults); ModelNode legacyModel = ModelTestUtils.checkResultAndGetContents(kernelServices.executeOperation(modelVersion, kernelServices.transformOperation(modelVersion, legacyReadResource))); ModelNode legacySubsystem = legacyModel.require(SUBSYSTEM); legacySubsystem = legacySubsystem.require(mainSubsystemName); if (legacyModelFixer != null) { legacySubsystem = legacyModelFixer.fixModel(legacySubsystem); } //1) Check that the transformed model is the same as the whole model read from the legacy controller. //The transformed model is done via the resource transformers //The model in the legacy controller is built up via transformed operations ModelNode transformed = kernelServices.readTransformedModel(modelVersion, includeDefaults).get(SUBSYSTEM, mainSubsystemName); ModelTestUtils.compare(legacySubsystem, transformed, true); //2) Check that the transformed model is valid according to the resource definition in the legacy subsystem controller ResourceDefinition rd = getResourceDefinition(kernelServices, modelVersion); Assert.assertNotNull("Could not load legacy dmr for subsystem '" + mainSubsystemName + "' version: '" + modelVersion + "' please add it", rd); ManagementResourceRegistration rr = ManagementResourceRegistration.Factory.forProcessType(getProcessType()).createRegistration(rd); ModelTestUtils.checkModelAgainstDefinition(transformed, rr); return legacyModel; } private ResourceDefinition getResourceDefinition(KernelServices kernelServices, ModelVersion modelVersion) throws IOException { //Look for the file in the org.jboss.as.subsystem.test package - this is where we used to store them before the split ResourceDefinition rd = TransformationUtils.loadSubsystemDefinitionFromFile(this.getClass(), mainSubsystemName, modelVersion); if (rd == null) { //This is the 'new' post-split way. First check for a cached .dmr file. This which also allows people //to override the file for the very rare cases where the rd needed touching up (probably only the case for 7.1.x descriptions). File file = getDmrFile(kernelServices, modelVersion); if (!file.exists()) { generateLegacySubsystemResourceRegistrationDmr(kernelServices, modelVersion); } System.out.println("Using legacy resource definition dmr: " + file); rd = TransformationUtils.loadSubsystemDefinitionFromFile(kernelServices.getTestClass(), mainSubsystemName, modelVersion); } return rd; } void addAdditionalParsers(AdditionalParsers additionalParsers) { if (additionalParsers != null && !addedExtraParsers) { additionalParsers.addParsers(extensionParsingRegistry, xmlMapper); addedExtraParsers = true; } } private ExtensionRegistry cloneExtensionRegistry(AdditionalInitialization additionalInit) { final ExtensionRegistry clone = new ExtensionRegistry(additionalInit.getProcessType(), new RunningModeControl(additionalInit.getExtensionRegistryRunningMode()), null, null, null, RuntimeHostControllerInfoAccessor.SERVER); for (String extension : extensionParsingRegistry.getExtensionModuleNames()) { ExtensionParsingContext epc = clone.getExtensionParsingContext(extension, null); for (Map.Entry<String, SubsystemInformation> entry : extensionParsingRegistry.getAvailableSubsystems(extension).entrySet()) { for (String namespace : entry.getValue().getXMLNamespaces()) { epc.setSubsystemXmlMapping(entry.getKey(), namespace, (XMLElementReader) null); } } } return clone; } private void validateDescriptionProviders(AdditionalInitialization additionalInit, KernelServices kernelServices) { ValidationConfiguration arbitraryDescriptors = additionalInit.getModelValidationConfiguration(); ModelNode address = new ModelNode(); address.setEmptyList(); address.add("subsystem", mainSubsystemName); ModelNode op = new ModelNode(); op.get(OP).set("read-resource-description"); op.get(OP_ADDR).set(address); op.get("recursive").set(true); op.get("inherited").set(false); op.get("operations").set(true); op.get("notifications").set(true); op.get("include-aliases").set(true); ModelNode result = kernelServices.executeOperation(op); if (result.hasDefined(FAILURE_DESCRIPTION)) { throw new RuntimeException(result.get(FAILURE_DESCRIPTION).toString()); } ModelNode model = result.get(RESULT); //System.out.println(model); ModelDescriptionValidator validator = new ModelDescriptionValidator(address, model, arbitraryDescriptors); List<ModelDescriptionValidator.ValidationFailure> validationMessages = validator.validateResource(); if (validationMessages.size() > 0) { final StringBuilder builder = new StringBuilder("VALIDATION ERRORS IN MODEL:"); for (ModelDescriptionValidator.ValidationFailure failure : validationMessages) { builder.append(failure); builder.append("\n"); } if (arbitraryDescriptors != null) { Assert.fail("Failed due to validation errors in the model. Please fix :-) " + builder.toString()); } } } private class KernelServicesBuilderImpl implements KernelServicesBuilder, ModelTestBootOperationsBuilder.BootOperationParser { private final ModelTestBootOperationsBuilder bootOperationBuilder; private final AdditionalInitialization additionalInit; private Map<ModelVersion, LegacyKernelServiceInitializerImpl> legacyControllerInitializers = new HashMap<ModelVersion, LegacyKernelServiceInitializerImpl>(); public KernelServicesBuilderImpl(AdditionalInitialization additionalInit) { this.additionalInit = additionalInit == null ? new AdditionalInitialization() : additionalInit; bootOperationBuilder = new ModelTestBootOperationsBuilder(testClass, this); } @Override public KernelServicesBuilder setSubsystemXmlResource(String resource) throws IOException, XMLStreamException { bootOperationBuilder.setXmlResource(resource); return this; } @Override public KernelServicesBuilder setSubsystemXml(String subsystemXml) throws XMLStreamException { bootOperationBuilder.setXml(subsystemXml); return this; } public KernelServicesBuilder setBootOperations(List<ModelNode> bootOperations) { bootOperationBuilder.setBootOperations(bootOperations); return this; } @Override public KernelServicesBuilder setBootOperations(ModelNode... bootOperations) { bootOperationBuilder.setBootOperations(Arrays.asList(bootOperations)); return this; } public LegacyKernelServicesInitializer createLegacyKernelServicesBuilder(AdditionalInitialization additionalInit, ModelTestControllerVersion version, ModelVersion modelVersion) { boolean valid = version.hasValidLegacyController(); if (!valid) { valid = System.getProperties().containsKey(TEST_OLD_LEGACY); } Assume.assumeTrue("No legacy controller to test against", valid); //Ignore this test if it is eap if (version.isEap()) { Assume.assumeTrue(EAPRepositoryReachableUtil.isReachable()); } bootOperationBuilder.validateNotAlreadyBuilt(); if (legacyControllerInitializers.containsKey(modelVersion)) { throw new IllegalArgumentException("There is already a legacy controller for " + modelVersion); } if (additionalInit != null) { if (additionalInit.getRunningMode() != RunningMode.ADMIN_ONLY) { throw new IllegalArgumentException("The additional initialization must have a running mode of ADMIN_ONLY, it was " + additionalInit.getRunningMode()); } } LegacyKernelServiceInitializerImpl initializer = new LegacyKernelServiceInitializerImpl(additionalInit, version, modelVersion); legacyControllerInitializers.put(modelVersion, initializer); return initializer; } public KernelServices build() throws Exception { bootOperationBuilder.validateNotAlreadyBuilt(); List<ModelNode> bootOperations = bootOperationBuilder.build(); AbstractKernelServicesImpl kernelServices = AbstractKernelServicesImpl.create(testClass, mainSubsystemName, additionalInit, ModelTestOperationValidatorFilter.createValidateAll(), cloneExtensionRegistry(additionalInit), bootOperations, testParser, mainExtension, null, legacyControllerInitializers.size() > 0, true); SubsystemTestDelegate.this.kernelServices.add(kernelServices); validateDescriptionProviders(additionalInit, kernelServices); ImmutableManagementResourceRegistration subsystemReg = kernelServices.getRootRegistration().getSubModel(PathAddress.pathAddress(PathElement.pathElement(SUBSYSTEM, mainSubsystemName))); ModelTestUtils.validateModelDescriptions(PathAddress.EMPTY_ADDRESS, subsystemReg); if (!legacyControllerInitializers.isEmpty()) { //only load transformers if we are testing legacy controllers loadTransformers(kernelServices.getExtensionRegistry()); } for (Map.Entry<ModelVersion, LegacyKernelServiceInitializerImpl> entry : legacyControllerInitializers.entrySet()) { LegacyKernelServiceInitializerImpl legacyInitializer = entry.getValue(); List<ModelNode> transformedBootOperations = new ArrayList<ModelNode>(); for (ModelNode op : bootOperations) { TransformedOperation transformedOp = kernelServices.transformOperation(entry.getKey(), op); if (transformedOp.getTransformedOperation() != null) { //Since rejected operations now execute on the slave to determine if it has been ignored or not //we check the reject policy simulating a reject before adding it to the list of boot operations if (!transformedOp.rejectOperation(SUCCESS)) { transformedBootOperations.add(transformedOp.getTransformedOperation()); // } else { // System.out.println(transformedOp.getFailureDescription()); } } } LegacyControllerKernelServicesProxy legacyServices = legacyInitializer.install(kernelServices, transformedBootOperations); kernelServices.addLegacyKernelService(entry.getKey(), legacyServices); } return kernelServices; } private void loadTransformers(final ExtensionRegistry extensionRegistry) { ModelVersion version = extensionRegistry.getSubsystemInfo(getMainSubsystemName()).getManagementInterfaceVersion(); for (ExtensionTransformerRegistration registration : ServiceLoader.load(ExtensionTransformerRegistration.class)) { if (registration.getSubsystemName().equals(getMainSubsystemName())) { registration.registerTransformers(extensionRegistry.getTransformerRegistry() .createSubsystemTransformerRegistration(getMainSubsystemName(), version)); } } } @Override public List<ModelNode> parse(String subsystemXml) throws XMLStreamException { return SubsystemTestDelegate.this.parse(additionalInit, subsystemXml); } @Override public List<ModelNode> parseXml(String xml) throws Exception { ModelTestBootOperationsBuilder builder = new ModelTestBootOperationsBuilder(testClass, this); builder.setXml(xml); return builder.build(); } @Override public List<ModelNode> parseXmlResource(String xmlResource) throws Exception { ModelTestBootOperationsBuilder builder = new ModelTestBootOperationsBuilder(testClass, this); builder.setXmlResource(xmlResource); return builder.build(); } @Override public KernelServicesBuilder enableTransformerAttachmentGrabber() { return this; } } private class LegacyKernelServiceInitializerImpl implements LegacyKernelServicesInitializer { private final AdditionalInitialization additionalInit; private final ModelTestControllerVersion testControllerVersion; private String extensionClassName; private ModelVersion modelVersion; private ChildFirstClassLoaderBuilder classLoaderBuilder; private ModelTestOperationValidatorFilter.Builder operationValidationExcludeBuilder; private boolean persistXml = true; private boolean skipReverseCheck; private AdditionalInitialization reverseCheckConfig; private ModelFixer reverseCheckModelFixer; private OperationFixer reverseCheckOperationFixer = new OperationFixer() { @Override public ModelNode fixOperation(ModelNode operation) { return operation; } }; public LegacyKernelServiceInitializerImpl(AdditionalInitialization additionalInit, ModelTestControllerVersion version, ModelVersion modelVersion) { this.classLoaderBuilder = new ChildFirstClassLoaderBuilder(version.isEap()); this.additionalInit = additionalInit == null ? AdditionalInitialization.MANAGEMENT : additionalInit; this.testControllerVersion = version; this.modelVersion = modelVersion; } @Override public LegacyKernelServicesInitializer addOperationValidationExclude(String name, PathAddress pathAddress) { addOperationValidationConfig(name, pathAddress, Action.NOCHECK, null); return this; } @Override public LegacyKernelServicesInitializer addOperationValidationResolve(String name, PathAddress pathAddress) { addOperationValidationConfig(name, pathAddress, Action.RESOLVE, null); return this; } @Override public LegacyKernelServicesInitializer addOperationValidationFixer(String name, PathAddress pathAddress, OperationFixer operationFixer) { addOperationValidationConfig(name, pathAddress, null, operationFixer); return this; } private void addOperationValidationConfig(String name, PathAddress pathAddress, Action action, OperationFixer operationFixer) { if (!additionalInit.isValidateOperations()) { throw new IllegalStateException("The additional initialization used to create this builder has turned off operation validation. That is not compatible with calling this method"); } if (operationValidationExcludeBuilder == null) { operationValidationExcludeBuilder = ModelTestOperationValidatorFilter.createBuilder(); } operationValidationExcludeBuilder.addOperation(pathAddress, name, action, operationFixer); } @Override public LegacyKernelServicesInitializer setExtensionClassName(String extensionClassName) { this.extensionClassName = extensionClassName; return this; } @Override public LegacyKernelServicesInitializer addURL(URL url) { classLoaderBuilder.addURL(url); return this; } @Override public LegacyKernelServicesInitializer addSimpleResourceURL(String resource) throws MalformedURLException { classLoaderBuilder.addSimpleResourceURL(resource); return this; } @Override public LegacyKernelServicesInitializer addMavenResourceURL(String...artifactGavs) throws IOException, ClassNotFoundException { for (String artifactGav : artifactGavs) { classLoaderBuilder.addMavenResourceURL(artifactGav); } return this; } @Override public LegacyKernelServiceInitializerImpl addParentFirstClassPattern(String pattern) { classLoaderBuilder.addParentFirstClassPattern(pattern); return this; } @Override public LegacyKernelServiceInitializerImpl addChildFirstClassPattern(String pattern) { classLoaderBuilder.addChildFirstClassPattern(pattern); return this; } @Override public LegacyKernelServicesInitializer excludeFromParent(ClassFilter exclusionFilter) { classLoaderBuilder.excludeFromParent(exclusionFilter); return this; } @Override public LegacyKernelServicesInitializer addSingleChildFirstClass(Class<?>...classes) { classLoaderBuilder.addSingleChildFirstClass(classes); return this; } private LegacyControllerKernelServicesProxy install(KernelServices mainServices, List<ModelNode> bootOperations) throws Exception { if (!skipReverseCheck) { bootCurrentVersionWithLegacyBootOperations(bootOperations, mainServices); } classLoaderBuilder.addParentFirstClassPattern("org.jboss.as.subsystem.bridge.shared.*"); classLoaderBuilder.addMavenResourceURL("org.wildfly.core:wildfly-subsystem-test-framework:" + ModelTestControllerVersion.CurrentVersion.VERSION); classLoaderBuilder.addMavenResourceURL("org.wildfly.core:wildfly-model-test:" + ModelTestControllerVersion.CurrentVersion.VERSION); classLoaderBuilder.addMavenResourceURL("org.wildfly.legacy.test:wildfly-legacy-spi:" + Version.LEGACY_TEST_CONTROLLER_VERSION); if (testControllerVersion != ModelTestControllerVersion.MASTER && testControllerVersion.getTestControllerVersion() != null) { String groupId = testControllerVersion.getCoreMavenGroupId(); String serverArtifactId = testControllerVersion.getServerMavenArtifactId(); classLoaderBuilder.addRecursiveMavenResourceURL(groupId + ":" + serverArtifactId + ":" + testControllerVersion.getCoreVersion()); //TODO Even with this there are some workarounds needed in JGroupsSubsystemTransformerTestCase, InfinispanSubsystemTransformersTestCase and LoggingSubsystemTestCase //Don't load modules from the scoped classloader to avoid some funky stuff going on when initializing the JAXP redirect //The mentioned funky stuff works fine when running in Eclipse but fails when running the tests on the command-line classLoaderBuilder.addParentFirstClassPattern("__redirected.*"); classLoaderBuilder.addParentFirstClassPattern("org.jboss.modules.*"); classLoaderBuilder.addMavenResourceURL("org.wildfly.legacy.test:wildfly-legacy-subsystem-" + testControllerVersion.getTestControllerVersion() + ":" + Version.LEGACY_TEST_CONTROLLER_VERSION); } URLClassLoader legacyCl = classLoaderBuilder.build(); ScopedKernelServicesBootstrap scopedBootstrap = new ScopedKernelServicesBootstrap(legacyCl); return scopedBootstrap.createKernelServices(mainSubsystemName, extensionClassName != null ? extensionClassName : mainExtension.getClass().getName(), additionalInit, getOperationValidationFilter(), bootOperations, modelVersion, persistXml); } @Override public LegacyKernelServicesInitializer dontPersistXml() { persistXml = false; return this; } @Override public LegacyKernelServicesInitializer skipReverseControllerCheck() { skipReverseCheck = true; return this; } @Override public LegacyKernelServicesInitializer configureReverseControllerCheck(AdditionalInitialization additionalInit, ModelFixer modelFixer) { this.reverseCheckConfig = additionalInit; this.reverseCheckModelFixer = modelFixer; return this; } @Override public LegacyKernelServicesInitializer configureReverseControllerCheck(AdditionalInitialization additionalInit, ModelFixer modelFixer, OperationFixer fixer) { this.reverseCheckConfig = additionalInit; this.reverseCheckModelFixer = modelFixer; this.reverseCheckOperationFixer = fixer; return this; } private KernelServices bootCurrentVersionWithLegacyBootOperations(List<ModelNode> bootOperations, KernelServices mainServices) throws Exception { //Clone the boot operations to avoid any pollution installing them in the main controller List<ModelNode> clonedBootOperations = new ArrayList<ModelNode>(); for (ModelNode op : bootOperations) { ModelNode cloned = reverseCheckOperationFixer.fixOperation(op.clone()); if (cloned!=null){ clonedBootOperations.add(cloned); } } KernelServices reverseServices = createKernelServicesBuilder(reverseCheckConfig) .setBootOperations(clonedBootOperations) .build(); final Throwable bootError = reverseServices.getBootError(); if (bootError != null) { if (bootError instanceof Exception) { throw (Exception) bootError; } throw new Exception(bootError); } Assert.assertTrue(reverseServices.isSuccessfulBoot()); ModelNode reverseSubsystem = reverseServices.readWholeModel().get(SUBSYSTEM, getMainSubsystemName()); if (reverseCheckModelFixer != null) { reverseSubsystem = reverseCheckModelFixer.fixModel(reverseSubsystem); } ModelTestUtils.compare(mainServices.readWholeModel().get(SUBSYSTEM, getMainSubsystemName()), reverseSubsystem); return reverseServices; } private ModelTestOperationValidatorFilter getOperationValidationFilter() { if (operationValidationExcludeBuilder != null) { return operationValidationExcludeBuilder.build(); } if (additionalInit.isValidateOperations()) { return ModelTestOperationValidatorFilter.createValidateAll(); } else { return ModelTestOperationValidatorFilter.createValidateNone(); } } } @SuppressWarnings("deprecation") private final ManagementResourceRegistration MOCK_RESOURCE_REG = new ManagementResourceRegistration() { @Override public PathAddress getPathAddress() { return PathAddress.EMPTY_ADDRESS; } @Override public ImmutableManagementResourceRegistration getParent() { return null; } @Override public boolean isRuntimeOnly() { return false; } @Override public boolean isRemote() { return false; } @Override public OperationEntry getOperationEntry(PathAddress address, String operationName) { return null; } @Override public OperationStepHandler getOperationHandler(PathAddress address, String operationName) { return null; } @Override public DescriptionProvider getOperationDescription(PathAddress address, String operationName) { return null; } @Override public Set<Flag> getOperationFlags(PathAddress address, String operationName) { return null; } @Override public Set<String> getAttributeNames(PathAddress address) { return null; } @Override public AttributeAccess getAttributeAccess(PathAddress address, String attributeName) { return null; } @Override public Set<String> getChildNames(PathAddress address) { return null; } @Override public Set<PathElement> getChildAddresses(PathAddress address) { return null; } @Override public DescriptionProvider getModelDescription(PathAddress address) { return null; } @Override public Map<String, OperationEntry> getOperationDescriptions(PathAddress address, boolean inherited) { return null; } @Override public Map<String, NotificationEntry> getNotificationDescriptions(PathAddress address, boolean inherited) { return null; } @Override public ProxyController getProxyController(PathAddress address) { return null; } @Override public Set<ProxyController> getProxyControllers(PathAddress address) { return null; } @Override public ManagementResourceRegistration getOverrideModel(String name) { return null; } @Override public ManagementResourceRegistration getSubModel(PathAddress address) { if (address.size() == 0) { return MOCK_RESOURCE_REG; } else if (address.size() == 1) { PathElement pe = address.getElement(0); String key = pe.getKey(); if (pe.isWildcard() && (ModelDescriptionConstants.PROFILE.equals(key) || ModelDescriptionConstants.DEPLOYMENT.equals(key))) { return MOCK_RESOURCE_REG; } } return null; } @Override public List<AccessConstraintDefinition> getAccessConstraints() { return Collections.emptyList(); } @Override public ManagementResourceRegistration registerSubModel(ResourceDefinition resourceDefinition) { return MOCK_RESOURCE_REG; } @Override public void unregisterSubModel(PathElement address) { } @Override public boolean isAllowsOverride() { return true; } @Override public void setRuntimeOnly(boolean runtimeOnly) { } @Override public ManagementResourceRegistration registerOverrideModel(String name, OverrideDescriptionProvider descriptionProvider) { return MOCK_RESOURCE_REG; } @Override public void unregisterOverrideModel(String name) { } @Override public void registerOperationHandler(OperationDefinition definition, OperationStepHandler handler) { } @Override public void registerOperationHandler(OperationDefinition definition, OperationStepHandler handler, boolean inherited) { } @Override public void registerCapability(RuntimeCapability capability) { } @Override public void registerIncorporatingCapabilities(Set<RuntimeCapability> capabilities) { } @Override public Set<RuntimeCapability> getCapabilities() { return Collections.emptySet(); } @Override public Set<RuntimeCapability> getIncorporatingCapabilities() { return null; } @Override public void unregisterOperationHandler(String operationName) { } @Override public void registerReadWriteAttribute(AttributeDefinition definition, OperationStepHandler readHandler, OperationStepHandler writeHandler) { } @Override public void registerReadOnlyAttribute(AttributeDefinition definition, OperationStepHandler readHandler) { } @Override public void registerMetric(AttributeDefinition definition, OperationStepHandler metricHandler) { } @Override public void unregisterAttribute(String attributeName) { } @Override public void registerNotification(NotificationDefinition notification, boolean inherited) { // no-op } @Override public void registerNotification(NotificationDefinition notification) { // no-op } @Override public void unregisterNotification(String notificationType) { // no-op } @Override public boolean isOrderedChildResource() { return false; } @Override public Set<String> getOrderedChildTypes() { return Collections.emptySet(); } @Override public void registerProxyController(PathElement address, ProxyController proxyController) { } @Override public void unregisterProxyController(PathElement address) { } @Override public void registerAlias(PathElement address, AliasEntry alias) { } @Override public void unregisterAlias(PathElement address) { } @Override public AliasEntry getAliasEntry() { return null; } @Override public boolean isAlias() { return false; } }; }