/* * JBoss, Home of Professional Open Source * Copyright 2014, JBoss Inc., and individual contributors as indicated * by the @authors tag. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jboss.as.controller; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT; import java.util.Set; import org.jboss.as.controller.OperationContext.Stage; import org.jboss.as.controller.logging.ControllerLogger; import org.jboss.as.controller.operations.common.Util; import org.jboss.as.controller.registry.AttributeAccess; import org.jboss.as.controller.registry.ImmutableManagementResourceRegistration; import org.jboss.as.controller.registry.Resource; import org.jboss.dmr.ModelNode; /** * @author Tomaz Cerar (c) 2014 Red Hat Inc. */ class ValidateModelStepHandler implements OperationStepHandler { private static volatile ValidateModelStepHandler INSTANCE; private final OperationStepHandler extraValidationStepHandler; private ValidateModelStepHandler(OperationStepHandler extraValidationStepHandler) { this.extraValidationStepHandler = extraValidationStepHandler; } static ValidateModelStepHandler getInstance(OperationStepHandler extraValidationStepHandler) { if (INSTANCE == null) { synchronized (ValidateModelStepHandler.class) { if (INSTANCE == null) { INSTANCE = new ValidateModelStepHandler(extraValidationStepHandler); } } } return INSTANCE; } @Override public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { final Resource resource = loadResource(context); if (resource == null) { return; } if (extraValidationStepHandler != null) { context.addStep(operation, extraValidationStepHandler, Stage.MODEL); } final ModelNode model = resource.getModel(); final ImmutableManagementResourceRegistration resourceRegistration = context.getResourceRegistration(); final Set<String> attributeNames = resourceRegistration.getAttributeNames(PathAddress.EMPTY_ADDRESS); for (final String attributeName : attributeNames) { final boolean has = model.hasDefined(attributeName); final AttributeAccess access = context.getResourceRegistration().getAttributeAccess(PathAddress.EMPTY_ADDRESS, attributeName); if (access.getStorageType() != AttributeAccess.Storage.CONFIGURATION){ continue; } final AttributeDefinition attr = access.getAttributeDefinition(); if (!has && isRequired(attr, model)) { attemptReadMissingAttributeValueFromHandler(context, access, attributeName, new ErrorHandler() { @Override public void throwError() throws OperationFailedException { throw new OperationFailedException(ControllerLogger.ROOT_LOGGER.required(attributeName)); }}); } if (!has) { continue; } if (attr.getRequires() != null) { for (final String required : attr.getRequires()) { if (!model.hasDefined(required)) { attemptReadMissingAttributeValueFromHandler(context, access, attributeName, new ErrorHandler() { @Override public void throwError() throws OperationFailedException { throw ControllerLogger.ROOT_LOGGER.requiredAttributeNotSet(required, attr.getName()); }}); } } } if (!isAllowed(attr, model)) { //TODO should really use attemptReadMissingAttributeValueFromHandler() to make this totally good, but the //overhead might be bigger than is worth at the moment since we would have to invoke the extra steps for //every single attribute not found (and not found should be the normal). String[] alts = attr.getAlternatives(); StringBuilder sb = null; if (alts != null) { for (String alt : alts) { if (model.hasDefined(alt)) { if (sb == null) { sb = new StringBuilder(); } else { sb.append(", "); } sb.append(alt); } } } throw new OperationFailedException(ControllerLogger.ROOT_LOGGER.invalidAttributeCombo(attributeName, sb)); } } context.completeStep(OperationContext.RollbackHandler.NOOP_ROLLBACK_HANDLER); } private void attemptReadMissingAttributeValueFromHandler(final OperationContext context, final AttributeAccess attributeAccess, final String attributeName, final ErrorHandler errorHandler) throws OperationFailedException { OperationStepHandler handler = attributeAccess.getReadHandler(); if (handler == null) { errorHandler.throwError(); } else { final ModelNode readAttr = Util.getReadAttributeOperation(context.getCurrentAddress(), attributeName); //Do a read-attribute as an immediate step final ModelNode resultHolder = new ModelNode(); context.addStep(resultHolder, readAttr, handler, Stage.MODEL, true); //Then check the read-attribute result in a later step and throw the error if it is not set context.addStep(new OperationStepHandler() { @Override public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { if (!resultHolder.isDefined() && !resultHolder.hasDefined(RESULT)) { errorHandler.throwError(); } } }, Stage.MODEL); } } private boolean isRequired(final AttributeDefinition def, final ModelNode model) { final boolean required = def.isRequired() && !def.isResourceOnly(); return required ? !hasAlternative(def.getAlternatives(), model) : required; } private boolean isAllowed(final AttributeDefinition def, final ModelNode model) { final String[] alternatives = def.getAlternatives(); if (alternatives != null) { for (final String alternative : alternatives) { if (model.hasDefined(alternative)) { return false; } } } return true; } private boolean hasAlternative(final String[] alternatives, ModelNode operationObject) { if (alternatives != null) { for (final String alternative : alternatives) { if (operationObject.hasDefined(alternative)) { return true; } } } return false; } private Resource loadResource(OperationContext context) { final PathAddress address = context.getCurrentAddress(); PathAddress current = PathAddress.EMPTY_ADDRESS; final ImmutableManagementResourceRegistration mrr = context.getRootResourceRegistration(); Resource resource = context.readResourceFromRoot(PathAddress.EMPTY_ADDRESS, false); for (PathElement element : address) { if (resource.isRuntime()){ return null; } current = current.append(element); final ImmutableManagementResourceRegistration subMrr = mrr.getSubModel(current); if (subMrr == null || subMrr.isRuntimeOnly() || subMrr.isRemote()) { return null; } if (!resource.hasChild(element)) { return null; } resource = context.readResourceFromRoot(current, false); } if (resource.isRuntime()) { return null; } return resource; } private interface ErrorHandler { void throwError() throws OperationFailedException; } }