/* * JBoss, Home of Professional Open Source. * Copyright 2012, 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.test.integration.domain; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ACCESS_TYPE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ALTERNATIVES; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ATTRIBUTES; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.BOOT_TIME; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CHILDREN; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CHILD_TYPE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.EXPRESSIONS_ALLOWED; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.HOST; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.INCLUDE_DEFAULTS; 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.OP_ADDR; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PATH; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_CHILDREN_NAMES_OPERATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_RESOURCE_DESCRIPTION_OPERATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_RESOURCE_OPERATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RELATIVE_TO; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REQUIRES; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SERVER; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.STORAGE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SYSTEM_PROPERTY; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.VALUE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.VALUE_TYPE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.WRITE_ATTRIBUTE_OPERATION; import static org.jboss.as.test.integration.domain.management.util.DomainTestUtils.createOperation; import static org.jboss.as.test.integration.domain.management.util.DomainTestUtils.executeForResult; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; import org.jboss.as.test.integration.domain.management.util.WildFlyManagedConfiguration; import org.jboss.dmr.ValueExpression; import org.jboss.logging.Logger; import org.junit.Assert; import org.jboss.as.controller.PathAddress; import org.jboss.as.controller.PathElement; import org.jboss.as.test.integration.domain.management.util.DomainLifecycleUtil; import org.jboss.as.test.integration.management.util.MgmtOperationException; import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelType; import org.jboss.dmr.Property; import org.junit.After; import org.junit.Before; import org.junit.Test; /** * Smoke test of expression support. * * @author Brian Stansberry (c) 2012 Red Hat Inc. */ public class ExpressionSupportSmokeTestCase extends BuildConfigurationTestBase { private static final Logger LOGGER = Logger.getLogger(ExpressionSupportSmokeTestCase.class); private static final Set<ModelType> COMPLEX_TYPES = Collections.unmodifiableSet(EnumSet.of(ModelType.LIST, ModelType.OBJECT, ModelType.PROPERTY)); private DomainLifecycleUtil domainMasterLifecycleUtil; private int conflicts; private int noSimple; private int noSimpleCollection; private int noComplexList; private int noObject; private int noComplexProperty; private int supportedUndefined; private int simple; private int simpleCollection; private int complexList; private int object; private int complexProperty; private final boolean immediateValidation = Boolean.getBoolean("immediate.expression.validation"); private final boolean logHandling = Boolean.getBoolean("expression.logging"); /** * Launch a master HC in --admin-only. Iterate through all resources, converting any writable attribute that * support expressions and has a value or a default value to an expression (if it isn't already one), using the * value/default value as the expression default (so setting a system property isn't required). Then reload out of * --admin-only and confirm that the host's servers start correctly. Finally, read the resources from the host * and confirm that the expected values are there. * * @throws Exception if there is one */ @Test public void test() throws Exception { // Add some extra resources to get some more coverage addTestResources(); Map<PathAddress, Map<String, ModelNode>> expectedValues = new HashMap<PathAddress, Map<String, ModelNode>>(); setExpressions(PathAddress.EMPTY_ADDRESS, "master", expectedValues); LOGGER.trace("Update statistics:"); LOGGER.trace("=================="); LOGGER.trace("conflicts: " + conflicts); LOGGER.trace("no expression simple: " + noSimple); LOGGER.trace("no expression simple collection: " + noSimpleCollection); LOGGER.trace("no expression complex list: " + noComplexList); LOGGER.trace("no expression object: " + noObject); LOGGER.trace("no expression complex property: " + noComplexProperty); LOGGER.trace("supported but undefined: " + supportedUndefined); LOGGER.trace("simple: " + simple); LOGGER.trace("simple collection: " + simpleCollection); LOGGER.trace("complex list: " + complexList); LOGGER.trace("object: " + object); LOGGER.trace("complex property: " + complexProperty); // restart back to normal mode ModelNode op = new ModelNode(); op.get(OP_ADDR).add(HOST, "master"); op.get(OP).set("reload"); op.get("admin-only").set(false); domainMasterLifecycleUtil.executeAwaitConnectionClosed(op); // Try to reconnect to the hc domainMasterLifecycleUtil.connect(); // check that the servers are up domainMasterLifecycleUtil.awaitServers(System.currentTimeMillis()); validateExpectedValues(PathAddress.EMPTY_ADDRESS, expectedValues, "master"); } @Before public void setUp() throws IOException { final WildFlyManagedConfiguration config = createConfiguration("domain.xml", "host.xml", getClass().getSimpleName()); config.setAdminOnly(true); // Trigger the servers to fail on boot if there are runtime errors String hostProps = config.getHostCommandLineProperties(); hostProps = hostProps == null ? "" : hostProps; config.setHostCommandLineProperties(hostProps + "\n-Djboss.unsupported.fail-boot-on-runtime-failure=true"); domainMasterLifecycleUtil = new DomainLifecycleUtil(config); // domainMasterLifecycleUtil.getConfiguration().addHostCommandLineProperty("-agentlib:jdwp=transport=dt_socket,address=8787,server=y,suspend=y"); domainMasterLifecycleUtil.start(); // Start conflicts = noSimple = noSimpleCollection = noComplexList = noComplexProperty = noObject = noComplexProperty = supportedUndefined = simple = simpleCollection = object = complexProperty = complexList = 0; } @After public void tearDown() { if (domainMasterLifecycleUtil != null) { domainMasterLifecycleUtil.stop(); } } private void addTestResources() throws IOException, MgmtOperationException { PathAddress spAddr = PathAddress.pathAddress(PathElement.pathElement(SYSTEM_PROPERTY, "domain-test")); ModelNode op = createOperation(ADD, spAddr); op.get(VALUE).set("test"); op.get(BOOT_TIME).set(true); executeForResult(op, domainMasterLifecycleUtil.getDomainClient()); PathAddress hostSpAddr = PathAddress.pathAddress(PathElement.pathElement(HOST, "master"), PathElement.pathElement(SYSTEM_PROPERTY, "host-test")); op.get(OP_ADDR).set(hostSpAddr.toModelNode()); executeForResult(op, domainMasterLifecycleUtil.getDomainClient()); PathAddress pathAddr = PathAddress.pathAddress(PathElement.pathElement(PATH, "domain-path-test")); op = createOperation(ADD, pathAddr); op.get(RELATIVE_TO).set("jboss.home.dir"); op.get(PATH).set("test"); executeForResult(op, domainMasterLifecycleUtil.getDomainClient()); PathAddress hostPathAddr = PathAddress.pathAddress(PathElement.pathElement(HOST, "master"), PathElement.pathElement(PATH, "host-path-test")); op.get(OP_ADDR).set(hostPathAddr.toModelNode()); executeForResult(op, domainMasterLifecycleUtil.getDomainClient()); } private void setExpressions(PathAddress address, String hostName, Map<PathAddress, Map<String, ModelNode>> expectedValues) throws IOException, MgmtOperationException { ModelNode description = readResourceDescription(address); ModelNode resource = readResource(address, true, false); ModelNode resourceNoDefaults = readResource(address, false, false); Map<String, ModelNode> expressionAttrs = new HashMap<String, ModelNode>(); Map<String, ModelNode> otherAttrs = new HashMap<String, ModelNode>(); Map<String, ModelNode> expectedAttrs = new HashMap<String, ModelNode>(); organizeAttributes(address, description, resource, resourceNoDefaults, expressionAttrs, otherAttrs, expectedAttrs); for (Map.Entry<String, ModelNode> entry : expressionAttrs.entrySet()) { writeAttribute(address, entry.getKey(), entry.getValue()); } // Set the other attrs as well just to exercise the write-attribute handlers for (Map.Entry<String, ModelNode> entry : otherAttrs.entrySet()) { writeAttribute(address, entry.getKey(), entry.getValue()); } if (expectedAttrs.size() > 0 && immediateValidation) { // Validate that our write-attribute calls resulted in the expected values in the model ModelNode modifiedResource = readResource(address, true, true); for (Map.Entry<String, ModelNode> entry : expectedAttrs.entrySet()) { ModelNode expectedValue = entry.getValue(); ModelNode modVal = modifiedResource.get(entry.getKey()); validateAttributeValue(address, entry.getKey(), expectedValue, modVal); } } // Store the modified values for confirmation after HC reload expectedValues.put(address, expectedAttrs); // Recurse into children, being careful about what processes we are touching boolean isHost = address.size() == 1 && HOST.equals(address.getLastElement().getKey()); for (Property descProp : description.get(CHILDREN).asPropertyList()) { String childType = descProp.getName(); if (isHost && SERVER.equals(childType)) { continue; } boolean hostChild = address.size() == 0 && HOST.equals(childType); List<String> children = readChildrenNames(address, childType); for (String child : children) { if (!hostChild || hostName.equals(child)) { setExpressions(address.append(PathElement.pathElement(childType, child)), hostName, expectedValues); } } } } private void organizeAttributes(PathAddress address, ModelNode description, ModelNode resource, ModelNode resourceNoDefaults, Map<String, ModelNode> expressionAttrs, Map<String, ModelNode> otherAttrs, Map<String, ModelNode> expectedAttrs) { ModelNode attributeDescriptions = description.get(ATTRIBUTES); for (Property descProp : attributeDescriptions.asPropertyList()) { String attrName = descProp.getName(); ModelNode attrDesc = descProp.getValue(); if (isAttributeExcluded(address, attrName, attrDesc, resourceNoDefaults)) { continue; } ModelNode noDefaultValue = resourceNoDefaults.get(attrName); if (!noDefaultValue.isDefined()) { // We need to see if it's legal to set this attribute, or whether it's undefined // because an alternative attribute is defined or a required attribute is not defined. Set<String> base = new HashSet<String>(); base.add(attrName); if (attrDesc.hasDefined(REQUIRES)) { for (ModelNode node : attrDesc.get(REQUIRES).asList()) { base.add(node.asString()); } } boolean conflict = false; for (String baseAttr : base) { if (!resource.hasDefined(baseAttr)) { conflict = true; break; } ModelNode baseAttrAlts = attributeDescriptions.get(baseAttr, ALTERNATIVES); if (baseAttrAlts.isDefined()) { for (ModelNode alt : baseAttrAlts.asList()) { String altName = alt.asString(); if (resourceNoDefaults.hasDefined(alt.asString()) || expressionAttrs.containsKey(altName) || otherAttrs.containsKey(altName)) { conflict = true; break; } } } } if (conflict) { conflicts++; logHandling("Skipping conflicted attribute " + attrName + " at " + address.toModelNode().asString()); continue; } } ModelNode attrValue = resource.get(attrName); ModelType attrType = attrValue.getType(); if (attrDesc.get(EXPRESSIONS_ALLOWED).asBoolean(false)) { // If it's defined and not an expression, use the current value to create an expression if (attrType != ModelType.UNDEFINED && attrType != ModelType.EXPRESSION) { // Deal with complex types specially if (COMPLEX_TYPES.contains(attrType)) { ModelNode valueType = attrDesc.get(VALUE_TYPE); if (valueType.getType() == ModelType.TYPE) { // Simple collection whose elements support expressions handleSimpleCollection(address, attrName, attrValue, valueType.asType(), expressionAttrs, otherAttrs, expectedAttrs); } else if (valueType.isDefined()) { handleComplexCollection(address, attrName, attrValue, attrType, valueType, expressionAttrs, otherAttrs, expectedAttrs); } else { noSimple++; logNoExpressions(address, attrName); otherAttrs.put(attrName, attrValue); expectedAttrs.put(attrName, attrValue); } } else { if (attrType == ModelType.STRING) { checkForUnconvertedExpression(address, attrName, attrValue); } String expression = "${exp.test:" + attrValue.asString() + "}"; expressionAttrs.put(attrName, new ModelNode(expression)); expectedAttrs.put(attrName, new ModelNode().set(new ValueExpression(expression))); simple++; logHandling("Added expression to simple attribute " + attrName + " at " + address.toModelNode().asString()); } } else { if (attrType != ModelType.EXPRESSION) { supportedUndefined++; logHandling("Expression supported but value undefined on simple attribute " + attrName + " at " + address.toModelNode().asString()); } else { simple++; logHandling("Already found an expression on simple attribute " + attrName + " at " + address.toModelNode().asString()); } otherAttrs.put(attrName, attrValue); expectedAttrs.put(attrName, attrValue); } } else if (COMPLEX_TYPES.contains(attrType) && attrDesc.get(VALUE_TYPE).getType() != ModelType.TYPE && attrDesc.get(VALUE_TYPE).isDefined()) { handleComplexCollection(address, attrName, attrValue, attrType, attrDesc.get(VALUE_TYPE), expressionAttrs, otherAttrs, expectedAttrs); } else /*if (!attrDesc.hasDefined(DEPRECATED))*/ { noSimple++; logNoExpressions(address, attrName); otherAttrs.put(attrName, attrValue); expectedAttrs.put(attrName, attrValue); } } } private boolean isAttributeExcluded(PathAddress address, String attrName, ModelNode attrDesc, ModelNode resourceNoDefaults) { if (!attrDesc.get(ACCESS_TYPE).isDefined() || !attrDesc.get(ACCESS_TYPE).asString().equalsIgnoreCase("read-write")) { return true; } if (attrDesc.get(STORAGE).isDefined() && !attrDesc.get(STORAGE).asString().equalsIgnoreCase("configuration")) { return true; } if (attrDesc.get(ModelDescriptionConstants.DEPRECATED).isDefined()){ return true; } // Special cases if ("default-web-module".equals(attrName)) { if (address.size() > 1) { PathElement subPe = address.getElement(address.size() - 2); if ("subsystem".equals(subPe.getKey()) && "web".equals(subPe.getValue()) && "virtual-server".equals(address.getLastElement().getKey())) { // This is not allowed if "enable-welcome-root" is "true", which is overly complex to validate // so skip it return true; } } } else if ("policy-modules".equals(attrName) || "login-modules".equals(attrName)) { if (address.size() > 2) { PathElement subPe = address.getElement(address.size() - 3); if ("subsystem".equals(subPe.getKey()) && "security".equals(subPe.getValue()) && "security-domain".equals(address.getElement(address.size() - 2).getKey())) { // This is a kind of alias that shows child resources as a list. Validating it // after reload breaks because the real child resources get changed. It's deprecated, so // we could exclude all deprecated attributes, but for now I'd rather be specific return true; } } } else if ("virtual-nodes".equals(attrName)) { if (address.size() > 3) { PathElement subPe = address.getElement(address.size() - 3); PathElement containerPe = address.getElement(address.size() - 2); PathElement distPe = address.getElement(address.size()-1); if ("subsystem".equals(subPe.getKey()) && "infinispan".equals(subPe.getValue()) && "cache-container".equals(containerPe.getKey()) && "distributed-cache".equals(distPe.getKey())) { // This is a distributed cache attribute in Infinispan which has been deprecated return true; } } } else if (address.size() > 0 && "transactions".equals(address.getLastElement().getValue()) && "subsystem".equals(address.getLastElement().getKey())) { if (attrName.contains("jdbc")) { // Ignore jdbc store attributes unless jdbc store is enabled return !resourceNoDefaults.hasDefined("use-jdbc-store") || !resourceNoDefaults.get("use-jdbc-store").asBoolean(); } else if (attrName.contains("journal")) { // Ignore journal store attributes unless journal store is enabled return !resourceNoDefaults.hasDefined("use-journal-store") || !resourceNoDefaults.get("use-journal-store").asBoolean(); } } return false; } private void logNoExpressions(PathAddress address, String attrName) { if (logHandling) { logHandling("No expression support for attribute " + attrName + " at " + address.toModelNode().asString()); } } private void logHandling(String msg) { if (logHandling) { LOGGER.trace(msg); } } private void handleSimpleCollection(PathAddress address, String attrName, ModelNode attrValue, ModelType valueType, Map<String, ModelNode> expressionAttrs, Map<String, ModelNode> otherAttrs, Map<String, ModelNode> expectedAttrs) { if (COMPLEX_TYPES.contains(valueType)) { // Too complicated noSimpleCollection++; logNoExpressions(address, attrName); otherAttrs.put(attrName, attrValue); } else { boolean hasExpression = false; ModelNode updated = new ModelNode(); ModelNode expected = new ModelNode(); for (ModelNode item : attrValue.asList()) { ModelType itemType = item.getType(); if (itemType == ModelType.PROPERTY) { Property prop = item.asProperty(); ModelNode propVal = prop.getValue(); ModelType propValType = propVal.getType(); if (propVal.isDefined() && propValType != ModelType.EXPRESSION) { // Convert property value to expression if (propValType == ModelType.STRING) { checkForUnconvertedExpression(address, attrName, propVal); } String expression = "${exp.test:" + propVal.asString() + "}"; updated.get(prop.getName()).set(expression); expected.get(prop.getName()).set(new ModelNode().set(new ValueExpression(expression))); hasExpression = true; } else { updated.get(prop.getName()).set(propVal); expected.get(prop.getName()).set(propVal); } } else if (item.isDefined() && itemType != ModelType.EXPRESSION) { // Convert item to expression if (itemType == ModelType.STRING) { checkForUnconvertedExpression(address, attrName, item); } String expression = "${exp.test:" + item.asString() + "}"; updated.add(expression); expected.add(new ModelNode().set(new ValueExpression(expression))); hasExpression = true; } else { updated.add(item); expected.add(item); } } if (hasExpression) { simpleCollection++; logHandling("Added expression to SIMPLE " + attrValue.getType() + " attribute " + attrName + " at " + address.toModelNode().asString()); expressionAttrs.put(attrName, updated); expectedAttrs.put(attrName, expected); } else { // We didn't change anything noSimpleCollection++; logNoExpressions(address, attrName); otherAttrs.put(attrName, attrValue); expectedAttrs.put(attrName, attrValue); } } } private void handleComplexCollection(PathAddress address, String attrName, ModelNode attrValue, ModelType attrType, ModelNode valueTypeDesc, Map<String, ModelNode> expressionAttrs, Map<String, ModelNode> otherAttrs, Map<String, ModelNode> expectedAttrs) { switch (attrType) { case LIST: handleComplexList(address, attrName, attrValue, valueTypeDesc, expressionAttrs, otherAttrs, expectedAttrs); break; case OBJECT: handleComplexObject(address, attrName, attrValue, valueTypeDesc, expressionAttrs, otherAttrs, expectedAttrs); break; case PROPERTY: handleComplexProperty(address, attrName, attrValue, valueTypeDesc, expressionAttrs, otherAttrs, expectedAttrs); break; default: throw new IllegalArgumentException(attrType.toString()); } } private void handleComplexList(PathAddress address, String attrName, ModelNode attrValue, ModelNode valueTypeDesc, Map<String, ModelNode> expressionAttrs, Map<String, ModelNode> otherAttrs, Map<String, ModelNode> expectedAttrs) { ModelNode updatedList = new ModelNode().setEmptyList(); ModelNode expectedList = new ModelNode().setEmptyList(); boolean changed = false; for (ModelNode item : attrValue.asList()) { ModelNode updated = new ModelNode(); ModelNode toExpect = new ModelNode(); handleComplexItem(address, attrName, item, valueTypeDesc, updated, toExpect); changed |= !updated.equals(item); updatedList.add(updated); expectedList.add(toExpect); } if (changed) { complexList++; logHandling("Added expression to COMPLEX LIST attribute " + attrName + " at " + address.toModelNode().asString()); expressionAttrs.put(attrName, updatedList); expectedAttrs.put(attrName, expectedList); } else { noComplexList++; logNoExpressions(address, attrName); otherAttrs.put(attrName, attrValue); } } private void handleComplexObject(PathAddress address, String attrName, ModelNode attrValue, ModelNode valueTypeDesc, Map<String, ModelNode> expressionAttrs, Map<String, ModelNode> otherAttrs, Map<String, ModelNode> expectedAttrs) { ModelNode updated = new ModelNode(); ModelNode toExpect = new ModelNode(); handleComplexItem(address, attrName, attrValue, valueTypeDesc, updated, toExpect); if (!updated.equals(attrValue)) { object++; logHandling("Added expression to OBJECT attribute " + attrName + " at " + address.toModelNode().asString()); expressionAttrs.put(attrName, updated); expectedAttrs.put(attrName, toExpect); } else { noObject++; logNoExpressions(address, attrName); otherAttrs.put(attrName, attrValue); } } private void handleComplexProperty(PathAddress address, String attrName, ModelNode attrValue, ModelNode valueTypeDesc, Map<String, ModelNode> expressionAttrs, Map<String, ModelNode> otherAttrs, Map<String, ModelNode> expectedAttrs) { Property prop = attrValue.asProperty(); ModelNode propVal = prop.getValue(); ModelNode updatedPropVal = new ModelNode(); ModelNode propValToExpect = new ModelNode(); handleComplexItem(address, attrName, propVal, valueTypeDesc, updatedPropVal, propValToExpect); if (!updatedPropVal.equals(propVal)) { complexProperty++; ModelNode updatedProp = new ModelNode().set(prop.getName(), updatedPropVal); logHandling("Added expression to COMPLEX PROPERTY attribute " + attrName + " at " + address.toModelNode().asString()); expressionAttrs.put(attrName, updatedProp); expectedAttrs.put(attrName, new ModelNode().set(prop.getName(), propValToExpect)); } else { noComplexProperty++; logNoExpressions(address, attrName); otherAttrs.put(attrName, attrValue); } } private void handleComplexItem(PathAddress address, String attrName, ModelNode item, ModelNode valueTypeDesc, ModelNode updatedItem, ModelNode itemToExpect) { // Hack to deal with time unit processing Set<String> keys = valueTypeDesc.keys(); boolean timeAttr = keys.size() == 2 && keys.contains("time") && keys.contains("unit"); boolean changed = false; for (Property fieldProp : valueTypeDesc.asPropertyList()) { String fieldName = fieldProp.getName(); if (!item.has(fieldName)) { continue; } boolean timeunit = timeAttr && "unit".equals(fieldName); ModelNode fieldDesc = fieldProp.getValue(); ModelNode fieldValue = item.get(fieldName); ModelType valueType = fieldValue.getType(); if (valueType == ModelType.UNDEFINED || valueType == ModelType.EXPRESSION || COMPLEX_TYPES.contains(valueType) // too complex || !fieldDesc.get(EXPRESSIONS_ALLOWED).asBoolean(false)) { updatedItem.get(fieldName).set(fieldValue); itemToExpect.get(fieldName).set(fieldValue); } else { if (valueType == ModelType.STRING) { checkForUnconvertedExpression(address, attrName, item); } String valueString = timeunit ? fieldValue.asString().toLowerCase() : fieldValue.asString(); String expression = "${exp.test:" + valueString + "}"; updatedItem.get(fieldName).set(expression); itemToExpect.get(fieldName).set(new ModelNode().set(new ValueExpression(expression))); changed = true; } } if (!changed) { // Use unchanged 'item' updatedItem.set(item); itemToExpect.set(item); } } private ModelNode readResourceDescription(PathAddress address) throws IOException, MgmtOperationException { ModelNode op = createOperation(READ_RESOURCE_DESCRIPTION_OPERATION, address); return executeForResult(op, domainMasterLifecycleUtil.getDomainClient()); } private ModelNode readResource(PathAddress address, boolean defaults, boolean failIfMissing) throws IOException, MgmtOperationException { try { ModelNode op = createOperation(READ_RESOURCE_OPERATION, address); op.get(INCLUDE_DEFAULTS).set(defaults); return executeForResult(op, domainMasterLifecycleUtil.getDomainClient()); } catch (MgmtOperationException e) { if (failIfMissing) { throw e; } return new ModelNode(); } } private void checkForUnconvertedExpression(PathAddress address, String attrName, ModelNode attrValue) { String text = attrValue.asString(); int start = text.indexOf("${"); if (start > -1) { if (text.indexOf("}") > start) { Assert.fail(address + " attribute " + attrName + " is storing an unconverted expression: " + text); } } } private void writeAttribute(PathAddress address, String attrName, ModelNode value) throws IOException, MgmtOperationException { ModelNode op = createOperation(WRITE_ATTRIBUTE_OPERATION, address); op.get(NAME).set(attrName); op.get(VALUE).set(value); executeForResult(op, domainMasterLifecycleUtil.getDomainClient()); } private List<String> readChildrenNames(PathAddress address, String childType) throws IOException, MgmtOperationException { ModelNode op = createOperation(READ_CHILDREN_NAMES_OPERATION, address); op.get(CHILD_TYPE).set(childType); ModelNode opResult = executeForResult(op, domainMasterLifecycleUtil.getDomainClient()); List<String> result = new ArrayList<String>(); for (ModelNode child : opResult.asList()) { result.add(child.asString()); } return result; } private void validateExpectedValues(PathAddress address, Map<PathAddress, Map<String, ModelNode>> expectedValues, String hostName) throws IOException, MgmtOperationException { Map<String, ModelNode> expectedModel = expectedValues.get(address); if (expectedModel != null && isValidatable(address)) { ModelNode resource = readResource(address, true, true); for (Map.Entry<String, ModelNode> entry : expectedModel.entrySet()) { String attrName = entry.getKey(); ModelNode expectedValue = entry.getValue(); ModelNode modVal = resource.get(entry.getKey()); validateAttributeValue(address, attrName, expectedValue, modVal); } } ModelNode description = readResourceDescription(address); // Recurse into children, being careful about what processes we are touching boolean isHost = address.size() == 1 && HOST.equals(address.getLastElement().getKey()); for (Property descProp : description.get(CHILDREN).asPropertyList()) { String childType = descProp.getName(); if (isHost && SERVER.equals(childType)) { continue; } boolean hostChild = address.size() == 0 && HOST.equals(childType); List<String> children = readChildrenNames(address, childType); for (String child : children) { if (!hostChild || hostName.equals(child)) { validateExpectedValues(address.append(PathElement.pathElement(childType, child)), expectedValues, hostName); } } } } private void validateAttributeValue(PathAddress address, String attrName, ModelNode expectedValue, ModelNode modelValue) { switch (expectedValue.getType()) { case EXPRESSION: { Assert.assertEquals(address + " attribute " + attrName + " value " + modelValue + " is an unconverted expression", expectedValue, modelValue); break; } case INT: case LONG: Assert.assertTrue(address + " attribute " + attrName + " is a valid type", modelValue.getType() == ModelType.INT || modelValue.getType() == ModelType.LONG); Assert.assertEquals(address + " -- " + attrName, expectedValue.asLong(), modelValue.asLong()); break; default: { Assert.assertEquals(address + " -- " + attrName, expectedValue, modelValue); } } } private boolean isValidatable(PathAddress address) { boolean result = true; if (address.size() > 1 && address.getLastElement().getKey().equals("bootstrap-context") && address.getLastElement().getValue().equals("default") && address.getElement(address.size() - 2).getKey().equals("subsystem") && address.getElement(address.size() - 2).getValue().equals("jca")) { // JCA subsystem doesn't persist this resource result = false; } return result; } }