package org.jboss.as.ejb3.subsystem; import static org.jboss.as.controller.capability.RuntimeCapability.buildDynamicCapabilityName; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CHILD_TYPE; 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.READ_CHILDREN_NAMES_OPERATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_RESOURCE_OPERATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT; import java.io.IOException; import java.util.Collections; import java.util.List; import org.jboss.as.controller.AttributeDefinition; import org.jboss.as.controller.ModelVersion; import org.jboss.as.controller.OperationFailedException; import org.jboss.as.controller.PathAddress; import org.jboss.as.controller.PathElement; import org.jboss.as.controller.operations.common.Util; import org.jboss.as.model.test.FailedOperationTransformationConfig; import org.jboss.as.model.test.ModelTestControllerVersion; import org.jboss.as.model.test.ModelTestUtils; import org.jboss.as.subsystem.test.AbstractSubsystemBaseTest; import org.jboss.as.subsystem.test.AdditionalInitialization; import org.jboss.as.subsystem.test.KernelServices; import org.jboss.as.subsystem.test.KernelServicesBuilder; import org.jboss.dmr.ModelNode; import org.junit.Assert; import org.junit.Test; /** * Test cases for transformers used in the EJB3 subsystem. * * @author <a href="tomasz.cerar@redhat.com"> Tomasz Cerar</a> * @author Richard Achmatowicz (c) 2015 Red Hat Inc. */ public class Ejb3TransformersTestCase extends AbstractSubsystemBaseTest { private static final String LEGACY_EJB_CLIENT_ARTIFACT = "org.jboss:jboss-ejb-client:2.1.2.Final"; private static String formatSubsystemArtifact(ModelTestControllerVersion version) { return formatArtifact("org.wildfly:wildfly-ejb3:%s", version); } private static String formatLegacySubsystemArtifact(ModelTestControllerVersion version) { return formatArtifact("org.jboss.as:jboss-as-ejb3:%s", version); } private static String formatArtifact(String pattern, ModelTestControllerVersion version) { return String.format(pattern, version.getMavenGavVersion()); } public Ejb3TransformersTestCase() { super(EJB3Extension.SUBSYSTEM_NAME, new EJB3Extension()); } @Override protected String getSubsystemXml() throws IOException { return readResource("subsystem.xml"); } @Override protected String getSubsystemXsdPath() throws Exception { return "schema/wildfly-ejb3_5_0.xsd"; } @Override protected String[] getSubsystemTemplatePaths() throws IOException { return new String[] { "/subsystem-templates/ejb3.xml" }; } @Test @Override public void testSchemaOfSubsystemTemplates() throws Exception { super.testSchemaOfSubsystemTemplates(); } @Test public void testTransformerEAP620() throws Exception { ModelTestControllerVersion controller = ModelTestControllerVersion.EAP_6_2_0; testTransformation(ModelVersion.create(1, 2, 1), controller, formatLegacySubsystemArtifact(controller), formatArtifact("org.jboss.as:jboss-as-threads:%s", controller)); } @Test public void testTransformerEAP630() throws Exception { ModelTestControllerVersion controller = ModelTestControllerVersion.EAP_6_3_0; testTransformation(ModelVersion.create(1, 2, 1), controller, formatLegacySubsystemArtifact(controller), formatArtifact("org.jboss.as:jboss-as-threads:%s", controller)); } @Test public void testTransformerEAP640() throws Exception { ModelTestControllerVersion controller = ModelTestControllerVersion.EAP_6_4_0; testTransformation(ModelVersion.create(1, 3, 0), controller, formatLegacySubsystemArtifact(controller), formatArtifact("org.jboss.as:jboss-as-threads:%s", controller)); } /** * Tests transformation of model from current version into specified version * The xml used should parse on legacy servers without rejections. * * @param model * @param controller * @param mavenResourceURLs * @throws Exception */ private void testTransformation(ModelVersion model, ModelTestControllerVersion controller, String ... mavenResourceURLs) throws Exception { // create builder for current subsystem version KernelServicesBuilder builder = createKernelServicesBuilder(AdditionalInitialization.MANAGEMENT) .setSubsystemXmlResource("subsystem-ejb3-transform.xml"); // initialize the legacy services and add required jars builder.createLegacyKernelServicesBuilder(null, controller, model). addMavenResourceURL(mavenResourceURLs). skipReverseControllerCheck(); KernelServices services = builder.build(); Assert.assertTrue(services.isSuccessfulBoot()); Assert.assertTrue(services.getLegacyServices(model).isSuccessfulBoot()); // check that both versions of the legacy model are the same and valid checkSubsystemModelTransformation(services, model, null); } @Test public void testRejectionsEAP620() throws Exception { ModelTestControllerVersion controller = ModelTestControllerVersion.EAP_6_2_0; this.testRejections(ModelVersion.create(1, 2, 1), controller, formatLegacySubsystemArtifact(controller), formatArtifact("org.jboss.as:jboss-as-threads:%s", controller), LEGACY_EJB_CLIENT_ARTIFACT); } @Test public void testRejectionsEAP630() throws Exception { ModelTestControllerVersion controller = ModelTestControllerVersion.EAP_6_3_0; this.testRejections(ModelVersion.create(1, 2, 1), controller, formatLegacySubsystemArtifact(controller), formatArtifact("org.jboss.as:jboss-as-threads:%s", controller), LEGACY_EJB_CLIENT_ARTIFACT); } @Test public void testRejectionsEAP640() throws Exception { ModelTestControllerVersion controller = ModelTestControllerVersion.EAP_6_4_0; this.testRejections(ModelVersion.create(1, 3, 0), controller, formatLegacySubsystemArtifact(controller), formatArtifact("org.jboss.as:jboss-as-threads:%s", controller), LEGACY_EJB_CLIENT_ARTIFACT); } private void testRejections(ModelVersion model, ModelTestControllerVersion controller, String ... mavenResourceURLs) throws Exception { // create builder for current subsystem version KernelServicesBuilder builder = createKernelServicesBuilder(this.createAdditionalInitialization().withCapabilities(buildDynamicCapabilityName("org.wildfly.security.security-domain", "ApplicationDomain"))); // initialize the legacy services and add required jars builder.createLegacyKernelServicesBuilder(null, controller, model) .addMavenResourceURL(mavenResourceURLs) .dontPersistXml(); KernelServices services = builder.build(); Assert.assertTrue(services.isSuccessfulBoot()); KernelServices legacyServices = services.getLegacyServices(model); Assert.assertNotNull(legacyServices); Assert.assertTrue(legacyServices.isSuccessfulBoot()); List<ModelNode> operations = builder.parseXmlResource("subsystem-ejb3-transform-reject.xml"); ModelTestUtils.checkFailedTransformedBootOperations(services, model, operations, createFailedOperationTransformationConfig(services, model)); } private static FailedOperationTransformationConfig createFailedOperationTransformationConfig(KernelServices services, ModelVersion version) { FailedOperationTransformationConfig config = new FailedOperationTransformationConfig(); PathAddress subsystemAddress = PathAddress.pathAddress(EJB3Extension.SUBSYSTEM_PATH); if (EJB3Model.VERSION_1_2_1.matches(version)) { // create a chained config to apply multiple transformation configs to each one of a collection of attributes FailedOperationTransformationConfig.ChainedConfig chainedSubsystemConfig = FailedOperationTransformationConfig.ChainedConfig.createBuilder( /*EJB3SubsystemRootResourceDefinition.DEFAULT_SFSB_PASSIVATION_DISABLED_CACHE,*/ EJB3SubsystemRootResourceDefinition.DISABLE_DEFAULT_EJB_PERMISSIONS) .addConfig(new FailedOperationTransformationConfig.NewAttributesConfig( /*EJB3SubsystemRootResourceDefinition.DEFAULT_SFSB_PASSIVATION_DISABLED_CACHE,*/ EJB3SubsystemRootResourceDefinition.LOG_EJB_EXCEPTIONS, EJB3SubsystemRootResourceDefinition.ALLOW_EJB_NAME_REGEX, EJB3SubsystemRootResourceDefinition.ENABLE_GRACEFUL_TXN_SHUTDOWN)) .addConfig(new CorrectFalseToTrue(EJB3SubsystemRootResourceDefinition.DISABLE_DEFAULT_EJB_PERMISSIONS)) .build(); // discard new attributes default-sfsb-passivation-disabled-cache, disable-default-ejb-permissions config.addFailedAttribute(subsystemAddress, chainedSubsystemConfig); // make sure that we have a file-data-store matching the custom-data-store attribute value final PathAddress timerServiceAddr = subsystemAddress.append(EJB3SubsystemModel.TIMER_SERVICE_PATH); final PathAddress badFileStoreAddr = timerServiceAddr.append(PathElement.pathElement(EJB3SubsystemModel.FILE_DATA_STORE, "file-data-store-rename-to-default")); final PathAddress newFileStoreAddr = timerServiceAddr.append(PathElement.pathElement(EJB3SubsystemModel.FILE_DATA_STORE, "file-data-store")); config.addFailedAttribute(badFileStoreAddr, new ChangeAddressConfig(services, badFileStoreAddr, newFileStoreAddr)); //Add a config to remove the extra file-data-store. This is against the fixed address from ChangeAddressConfig RemoveExtraFileStoreConfig removeExtraFileStoreConfig = new RemoveExtraFileStoreConfig(services, timerServiceAddr); config.addFailedAttribute(newFileStoreAddr, removeExtraFileStoreConfig); // reject the resource /subsystem=ejb3/service=timer-service/file-data-store=file-data-store-rejected since we already have a file-data-store config.addFailedAttribute(subsystemAddress.append(EJB3SubsystemModel.TIMER_SERVICE_PATH, PathElement.pathElement(EJB3SubsystemModel.FILE_DATA_STORE, "file-data-store-rejected")), FailedOperationTransformationConfig.REJECTED_RESOURCE); // reject the resource /subsystem=ejb3/service=timer-service/database-data-store=* PathAddress databaseDataStore = subsystemAddress.append(EJB3SubsystemModel.TIMER_SERVICE_PATH, EJB3SubsystemModel.DATABASE_DATA_STORE_PATH); config.addFailedAttribute(databaseDataStore, FailedOperationTransformationConfig.REJECTED_RESOURCE); // reject the resource /subsystem=ejb3/mdb-delivery-group=delivery-group-name config.addFailedAttribute(subsystemAddress.append(PathElement.pathElement(EJB3SubsystemModel.MDB_DELIVERY_GROUP, "delivery-group-name")), FailedOperationTransformationConfig.REJECTED_RESOURCE); // reject the resource /subsystem=ejb3/remoting-profile=profile and its children PathAddress remotingProfileAddress = subsystemAddress.append(PathElement.pathElement(EJB3SubsystemModel.REMOTING_PROFILE, "profile")); PathAddress ejbReceiverAddress = remotingProfileAddress.append(PathElement.pathElement(EJB3SubsystemModel.REMOTING_EJB_RECEIVER, "receiver")); PathAddress channelCreationOptionsAddress = ejbReceiverAddress.append(PathElement.pathElement(EJB3SubsystemModel.CHANNEL_CREATION_OPTIONS)); config.addFailedAttribute(remotingProfileAddress, FailedOperationTransformationConfig.REJECTED_RESOURCE); config.addFailedAttribute(ejbReceiverAddress, FailedOperationTransformationConfig.REJECTED_RESOURCE); config.addFailedAttribute(channelCreationOptionsAddress, FailedOperationTransformationConfig.REJECTED_RESOURCE); // reject the attribute 'cluster' from resource /subsystem=ejb3/service=remote config.addFailedAttribute(subsystemAddress.append(EJB3SubsystemModel.REMOTE_SERVICE_PATH), new FailedOperationTransformationConfig.NewAttributesConfig(EJB3RemoteResourceDefinition.CLIENT_MAPPINGS_CLUSTER_NAME)); // reject the resource /subsystem=ejb3/application-security-domain=domain config.addFailedAttribute(subsystemAddress.append(PathElement.pathElement(EJB3SubsystemModel.APPLICATION_SECURITY_DOMAIN, "domain")), FailedOperationTransformationConfig.REJECTED_RESOURCE); // reject the resource /subsystem=ejb3/service=identity config.addFailedAttribute(subsystemAddress.append(EJB3SubsystemModel.IDENTITY_PATH), FailedOperationTransformationConfig.REJECTED_RESOURCE); //Special handling for this test!!!! //Don't transform the resulting composite, instead rather transform the individual steps config.setDontTransformComposite(); //Remove the extra file-data-store entries so that our transformers can work config.setCallback(() -> removeExtraFileStoreConfig.removeExtraFileDataStore()); } if (EJB3Model.VERSION_1_3_0.matches(version)) { // create a chained config to apply multiple transformation configs to each one of a collection of attributes FailedOperationTransformationConfig.ChainedConfig chainedConfig = FailedOperationTransformationConfig.ChainedConfig.createBuilder( /*EJB3SubsystemRootResourceDefinition.DEFAULT_SFSB_PASSIVATION_DISABLED_CACHE,*/ EJB3SubsystemRootResourceDefinition.DISABLE_DEFAULT_EJB_PERMISSIONS) .addConfig(new FailedOperationTransformationConfig.NewAttributesConfig( /*EJB3SubsystemRootResourceDefinition.DEFAULT_SFSB_PASSIVATION_DISABLED_CACHE,*/ EJB3SubsystemRootResourceDefinition.LOG_EJB_EXCEPTIONS, EJB3SubsystemRootResourceDefinition.ALLOW_EJB_NAME_REGEX, EJB3SubsystemRootResourceDefinition.ENABLE_GRACEFUL_TXN_SHUTDOWN)) .addConfig(new CorrectFalseToTrue(EJB3SubsystemRootResourceDefinition.DISABLE_DEFAULT_EJB_PERMISSIONS)) .build(); // discard new attributes default-sfsb-passivation-disabled-cache, disable-default-ejb-permissions config.addFailedAttribute(subsystemAddress, chainedConfig); // reject the attributes allow execution, refresh interval from resource /subsystem=ejb3/service=timer-service/database-data-store=* PathAddress databaseDataStore = subsystemAddress.append(EJB3SubsystemModel.TIMER_SERVICE_PATH, EJB3SubsystemModel.DATABASE_DATA_STORE_PATH); // config.addFailedAttribute(databaseDataStore, new FailedOperationTransformationConfig.NewAttributesConfig(DatabaseDataStoreResourceDefinition.ALLOW_EXECUTION, DatabaseDataStoreResourceDefinition.REFRESH_INTERVAL)); config.addFailedAttribute(databaseDataStore, FailedOperationTransformationConfig.REJECTED_RESOURCE); // reject the resource /subsystem=ejb3/mdb-delivery-group=delivery-group-name config.addFailedAttribute(subsystemAddress.append(PathElement.pathElement(EJB3SubsystemModel.MDB_DELIVERY_GROUP, "delivery-group-name")), FailedOperationTransformationConfig.REJECTED_RESOURCE); // reject the resource /subsystem=ejb3/remoting-profile=profile and its children PathAddress remotingProfileAddress = subsystemAddress.append(PathElement.pathElement(EJB3SubsystemModel.REMOTING_PROFILE, "profile")); PathAddress ejbReceiverAddress = remotingProfileAddress.append(PathElement.pathElement(EJB3SubsystemModel.REMOTING_EJB_RECEIVER, "receiver")); PathAddress channelCreationOptionsAddress = ejbReceiverAddress.append(PathElement.pathElement(EJB3SubsystemModel.CHANNEL_CREATION_OPTIONS)); config.addFailedAttribute(remotingProfileAddress, FailedOperationTransformationConfig.REJECTED_RESOURCE); config.addFailedAttribute(ejbReceiverAddress, FailedOperationTransformationConfig.REJECTED_RESOURCE); config.addFailedAttribute(channelCreationOptionsAddress, FailedOperationTransformationConfig.REJECTED_RESOURCE); // reject the attribute 'cluster' from resource /subsystem=ejb3/service=remote config.addFailedAttribute(subsystemAddress.append(EJB3SubsystemModel.REMOTE_SERVICE_PATH), new FailedOperationTransformationConfig.NewAttributesConfig(EJB3RemoteResourceDefinition.CLIENT_MAPPINGS_CLUSTER_NAME)); // reject the resource /subsystem=ejb3/application-security-domain=domain config.addFailedAttribute(subsystemAddress.append(PathElement.pathElement(EJB3SubsystemModel.APPLICATION_SECURITY_DOMAIN, "domain")), FailedOperationTransformationConfig.REJECTED_RESOURCE); // reject the resource /subsystem=ejb3/service=identity config.addFailedAttribute(subsystemAddress.append(EJB3SubsystemModel.IDENTITY_PATH), FailedOperationTransformationConfig.REJECTED_RESOURCE); } return config; } private static class CorrectFalseToTrue extends FailedOperationTransformationConfig.AttributesPathAddressConfig<CorrectFalseToTrue>{ public CorrectFalseToTrue(AttributeDefinition...defs) { super(convert(defs)); } @Override protected boolean isAttributeWritable(String attributeName) { return true; } @Override protected boolean checkValue(String attrName, ModelNode attribute, boolean isWriteAttribute) { return attribute.asString().equals("true"); } @Override protected ModelNode correctValue(ModelNode toResolve, boolean isWriteAttribute) { return new ModelNode(false); } } private abstract static class BasePathAddressConfig implements FailedOperationTransformationConfig.PathAddressConfig { @Override public boolean expectDiscarded(ModelNode operation) { //The reject simply forwards on the original operation to make it fail return false; } @Override public List<ModelNode> createWriteAttributeOperations(ModelNode operation) { return Collections.emptyList(); } @Override public boolean expectFailedWriteAttributeOperation(ModelNode operation) { throw new IllegalStateException("Should not get called"); } @Override public ModelNode correctWriteAttributeOperation(ModelNode operation) { throw new IllegalStateException("Should not get called"); } } private static class ChangeAddressConfig extends BasePathAddressConfig { KernelServices services; private final PathAddress badAddress; private final PathAddress newAddress; private ChangeAddressConfig(KernelServices services, PathAddress badAddress, PathAddress newAddress) { this.services = services; this.badAddress = badAddress; this.newAddress = newAddress; } @Override public boolean expectFailed(ModelNode operation) { return isBadAddress(operation); } @Override public boolean canCorrectMore(ModelNode operation) { return isBadAddress(operation); } @Override public ModelNode correctOperation(ModelNode operation) { //As part of this we also need to update the main model, since the transformer will look at the //values already in the model in order to know what to reject. We basically move the //resource found at badAddress to newAddress try { ModelNode ds = services.executeForResult(Util.createEmptyOperation(READ_RESOURCE_OPERATION, badAddress)); ModelTestUtils.checkOutcome(services.executeOperation(Util.createRemoveOperation(badAddress))); ds.get(OP).set(ADD); ds.get(OP_ADDR).set(newAddress.toModelNode()); ModelTestUtils.checkOutcome(services.executeOperation((ds))); } catch (OperationFailedException e) { throw new RuntimeException(e); } //Now fix up the operation as normal ModelNode op = operation.clone(); op.get(OP_ADDR).set(newAddress.toModelNode()); return op; } private boolean isBadAddress(ModelNode operation) { return PathAddress.pathAddress(operation.require(OP_ADDR)).equals(badAddress); } } private static class RemoveExtraFileStoreConfig extends BasePathAddressConfig { private final KernelServices kernelServices; private final PathAddress timerServiceAddress; private final PathAddress rejectedFileDataStoreAddress; private ModelNode removedResourceModel; public RemoveExtraFileStoreConfig(KernelServices kernelServices, PathAddress timerServiceAddress) { this.kernelServices = kernelServices; this.timerServiceAddress = timerServiceAddress; rejectedFileDataStoreAddress = timerServiceAddress.append(EJB3SubsystemModel.FILE_DATA_STORE_PATH.getKey(), "file-data-store-rejected"); } @Override public boolean expectFailed(ModelNode operation) { return hasTooManyFileStores(); } @Override public boolean canCorrectMore(ModelNode operation) { return hasTooManyFileStores(); } private boolean hasTooManyFileStores() { ModelNode op = Util.createOperation(READ_CHILDREN_NAMES_OPERATION, timerServiceAddress); op.get(CHILD_TYPE).set(EJB3SubsystemModel.FILE_DATA_STORE_PATH.getKey()); ModelNode result = ModelTestUtils.checkOutcome(kernelServices.executeOperation(op)).get(RESULT); List<ModelNode> list = result.asList(); return list.size() > 1; } @Override public ModelNode correctOperation(ModelNode operation) { //Here we don't actually correct the operation, but we remove the extra file-data-store which causes the //rejection ModelNode rr = Util.createEmptyOperation(READ_RESOURCE_OPERATION, rejectedFileDataStoreAddress); removedResourceModel = ModelTestUtils.checkOutcome(kernelServices.executeOperation(rr)).get(RESULT); removeExtraFileDataStore(); return operation; } void removeExtraFileDataStore() { ModelNode remove = Util.createRemoveOperation(rejectedFileDataStoreAddress); ModelTestUtils.checkOutcome(kernelServices.executeOperation(remove)); } @Override public void operationDone(ModelNode operation) { if (removedResourceModel != null) { //Re-add the removed resource, since we have more checks in the config for file-data-store=file-data-store-rejected ModelNode add = Util.createAddOperation( timerServiceAddress.append(EJB3SubsystemModel.FILE_DATA_STORE_PATH.getKey(), "file-data-store-rejected")); for (String key : removedResourceModel.keys()) { add.get(key).set(removedResourceModel.get(key)); } ModelNode result = ModelTestUtils.checkOutcome(kernelServices.executeOperation(add)).get(RESULT); } } } }