/* * JBoss, Home of Professional Open Source. * Copyright 2011, 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.controller; import java.util.ArrayList; import java.util.Arrays; import java.util.Locale; import java.util.ResourceBundle; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; import org.jboss.as.controller.descriptions.ResourceDescriptionResolver; import org.jboss.as.controller.operations.validation.ObjectTypeValidator; import org.jboss.as.controller.operations.validation.ParameterValidator; import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelType; /** * {@link AttributeDefinition} for attributes of type {@link ModelType#OBJECT} that aren't simple maps, but * rather a set fixed keys where each key may be associated with a value of a different type. * * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a> * @author Richard Achmatowicz (c) 2012 RedHat Inc. * * @see MapAttributeDefinition */ public class ObjectTypeAttributeDefinition extends SimpleAttributeDefinition { private final AttributeDefinition[] valueTypes; private final String suffix; protected ObjectTypeAttributeDefinition(Builder builder) { this(builder, builder.suffix, builder.valueTypes); } protected ObjectTypeAttributeDefinition(AbstractAttributeDefinitionBuilder<?, ? extends ObjectTypeAttributeDefinition> builder, final String suffix, final AttributeDefinition[] valueTypes) { super(builder); this.valueTypes = valueTypes; this.suffix = suffix == null ? "" : suffix; } @Override protected ModelNode convertParameterExpressions(ModelNode parameter) { ModelNode result = parameter; if (parameter.isDefined()) { boolean changeMade = false; ModelNode updated = new ModelNode().setEmptyObject(); for (AttributeDefinition ad : valueTypes) { String fieldName = ad.getName(); if (parameter.has(fieldName)) { ModelNode orig = parameter.get(fieldName); if (!orig.isDefined()) { updated.get(fieldName); // establish undefined } else { ModelNode converted = ad.convertParameterExpressions(orig); changeMade |= !orig.equals(converted); updated.get(fieldName).set(converted); } } } if (changeMade) { result = updated; } } return result; } AttributeDefinition[] getValueTypes() { return valueTypes; } @Override public ModelNode parse(final String value, final XMLStreamReader reader) throws XMLStreamException { throw new UnsupportedOperationException(); } @Override public void addCapabilityRequirements(OperationContext context, ModelNode attributeValue) { if (attributeValue.isDefined()) { for (AttributeDefinition fieldType : valueTypes) { if (attributeValue.hasDefined(fieldType.getName())) { fieldType.addCapabilityRequirements(context, attributeValue.get(fieldType.getName())); } } } } @Override public void removeCapabilityRequirements(OperationContext context, ModelNode attributeValue) { if (attributeValue.isDefined()) { for (AttributeDefinition fieldType : valueTypes) { if (attributeValue.hasDefined(fieldType.getName())) { fieldType.removeCapabilityRequirements(context, attributeValue.get(fieldType.getName())); } } } } @Override public ModelNode addResourceAttributeDescription(ResourceBundle bundle, String prefix, ModelNode resourceDescription) { final ModelNode result = super.addResourceAttributeDescription(bundle, prefix, resourceDescription); addValueTypeDescription(result, prefix, bundle, false, null, null); return result; } @Override public ModelNode addOperationParameterDescription(final ModelNode resourceDescription, final String operationName, final ResourceDescriptionResolver resolver, final Locale locale, final ResourceBundle bundle) { final ModelNode result = super.addOperationParameterDescription(resourceDescription, operationName, resolver, locale, bundle); addValueTypeDescription(result, getName(), bundle, true, resolver, locale); return result; } @Override public ModelNode addOperationReplyDescription(final ModelNode resourceDescription, final String operationName, final ResourceDescriptionResolver resolver, final Locale locale, final ResourceBundle bundle) { final ModelNode result = super.addOperationReplyDescription(resourceDescription, operationName, resolver, locale, bundle); addValueTypeDescription(result, getName(), bundle, true, resolver, locale); return result; } @Override public ModelNode addResourceAttributeDescription(final ModelNode resourceDescription, final ResourceDescriptionResolver resolver, final Locale locale, final ResourceBundle bundle) { final ModelNode result = super.addResourceAttributeDescription(resourceDescription, resolver, locale, bundle); addValueTypeDescription(result, getName(), bundle, false, resolver, locale); return result; } @Override public ModelNode addOperationParameterDescription(ResourceBundle bundle, String prefix, ModelNode operationDescription) { final ModelNode result = super.addOperationParameterDescription(bundle, prefix, operationDescription); addValueTypeDescription(result, prefix, bundle, true, null, null); return result; } /** * Overrides the superclass implementation to allow the AttributeDefinition for each field in the * object to in turn resolve that field. * * {@inheritDoc} */ @Override public ModelNode resolveValue(ExpressionResolver resolver, ModelNode value) throws OperationFailedException { // Pass non-OBJECT values through the superclass so it can reject weird values and, in the odd chance // that's how this object is set up, turn undefined into a default list value. ModelNode superResult = value.getType() == ModelType.OBJECT ? value : super.resolveValue(resolver, value); // If it's not an OBJECT (almost certainly UNDEFINED), then nothing more we can do if (superResult.getType() != ModelType.OBJECT) { return superResult; } // Resolve each field. // Don't mess with the original value ModelNode clone = superResult == value ? value.clone() : superResult; ModelNode result = new ModelNode(); for (AttributeDefinition field : valueTypes) { String fieldName = field.getName(); if (clone.has(fieldName)) { result.get(fieldName).set(field.resolveValue(resolver, clone.get(fieldName))); } else { // Input doesn't have a child for this field. // Don't create one in the output unless the AD produces a default value. // TBH this doesn't make a ton of sense, since any object should have // all of its fields, just some may be undefined. But doing it this // way may avoid breaking some code that is incorrectly checking node.has("xxx") // instead of node.hasDefined("xxx") ModelNode val = field.resolveValue(resolver, new ModelNode()); if (val.isDefined()) { result.get(fieldName).set(val); } } } // Validate the entire object getValidator().validateResolvedParameter(getName(), result); return result; } /** * * @deprecated use #addValueTypeDescription(ModelNode, String, ResourceBundle, boolean, ResourceDescriptionResolver, Locale) */ @Deprecated protected void addValueTypeDescription(final ModelNode node, final String prefix, final ResourceBundle bundle, final ResourceDescriptionResolver resolver, final Locale locale) { addValueTypeDescription(node, prefix, bundle, false, resolver, locale); } protected void addValueTypeDescription(final ModelNode node, final String prefix, final ResourceBundle bundle, boolean forOperation, final ResourceDescriptionResolver resolver, final Locale locale) { for (AttributeDefinition valueType : valueTypes) { if (forOperation && valueType.isResourceOnly()) { continue; //WFCORE-597 } // get the value type description of the attribute final ModelNode valueTypeDesc = valueType.getNoTextDescription(false); if(valueTypeDesc.has(ModelDescriptionConstants.ATTRIBUTE_GROUP)) { valueTypeDesc.remove(ModelDescriptionConstants.ATTRIBUTE_GROUP); } final String p; boolean prefixUnusable = prefix == null || prefix.isEmpty() ; boolean suffixUnusable = suffix == null || suffix.isEmpty() ; if (prefixUnusable && !suffixUnusable) { p = suffix; } else if (!prefixUnusable && suffixUnusable) { p = prefix; } else { p = String.format("%s.%s", prefix, suffix); } // get the text description of the attribute if (resolver != null) { final String key = String.format("%s.%s", p, valueType.getName()); valueTypeDesc.get(ModelDescriptionConstants.DESCRIPTION).set(resolver.getResourceAttributeDescription(key, locale, bundle)); } else { valueTypeDesc.get(ModelDescriptionConstants.DESCRIPTION).set(valueType.getAttributeTextDescription(bundle, p)); } // set it as one of our value types, and return the value final ModelNode childType = node.get(ModelDescriptionConstants.VALUE_TYPE, valueType.getName()).set(valueTypeDesc); // if it is of type OBJECT itself (add its nested descriptions) // seeing that OBJECT represents a grouping, use prefix+"."+suffix for naming the entries if (valueType instanceof ObjectTypeAttributeDefinition) { ObjectTypeAttributeDefinition.class.cast(valueType).addValueTypeDescription(childType, p, bundle, forOperation, resolver, locale); } // if it is of type LIST, and its value type // seeing that LIST represents a grouping, use prefix+"."+suffix for naming the entries if (valueType instanceof SimpleListAttributeDefinition) { SimpleListAttributeDefinition.class.cast(valueType).addValueTypeDescription(childType, p, bundle); } else if (valueType instanceof MapAttributeDefinition) { MapAttributeDefinition.class.cast(valueType).addValueTypeDescription(childType, bundle); } else if (valueType instanceof PrimitiveListAttributeDefinition) { PrimitiveListAttributeDefinition.class.cast(valueType).addValueTypeDescription(childType, bundle); } else if (valueType instanceof ObjectListAttributeDefinition) { ObjectListAttributeDefinition.class.cast(valueType).addValueTypeDescription(childType, p, bundle, false, resolver, locale); } } } public static Builder create(final String name, final AttributeDefinition... valueTypes){ return new Builder(name, valueTypes); } @Override protected void addAllowedValuesToDescription(ModelNode result, ParameterValidator validator) { //Don't add allowed values for object types, since they simply enumerate the fields given in the value type } public static final class Builder extends AbstractAttributeDefinitionBuilder<Builder, ObjectTypeAttributeDefinition> { private String suffix; private final AttributeDefinition[] valueTypes; public Builder(final String name, final AttributeDefinition... valueTypes) { super(name, ModelType.OBJECT, true); this.valueTypes = valueTypes; setAttributeParser(AttributeParser.OBJECT_PARSER); setAttributeMarshaller(AttributeMarshaller.ATTRIBUTE_OBJECT); } public static Builder of(final String name, final AttributeDefinition... valueTypes) { return new Builder(name, valueTypes); } public static Builder of(final String name, final AttributeDefinition[] valueTypes, final AttributeDefinition[] moreValueTypes) { ArrayList<AttributeDefinition> list = new ArrayList<>(Arrays.asList(valueTypes)); list.addAll(Arrays.asList(moreValueTypes)); AttributeDefinition[] allValueTypes = new AttributeDefinition[list.size()]; list.toArray(allValueTypes); return new Builder(name, allValueTypes); } public ObjectTypeAttributeDefinition build() { if (validator == null) { validator = new ObjectTypeValidator(allowNull, valueTypes); } return new ObjectTypeAttributeDefinition(this); } public Builder setSuffix(final String suffix) { this.suffix = suffix; return this; } /* -------------------------- added for binary compatibility for running compatibilty tests */ @Override public Builder setAllowNull(boolean allowNull) { return super.setAllowNull(allowNull); } } }