/*
* JBoss, Home of Professional Open Source
* Copyright 2011 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @authors tag. All rights reserved.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* 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,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package org.jboss.as.domain.controller.operations.deployment;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CONTENT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOYMENT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FULL_REPLACE_DEPLOYMENT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.HASH;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RUNTIME_NAME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SERVER_GROUP;
import static org.jboss.as.domain.controller.operations.deployment.AbstractDeploymentHandler.createFailureException;
import static org.jboss.as.server.controller.resources.DeploymentAttributes.CONTENT_HASH;
import static org.jboss.as.server.controller.resources.DeploymentAttributes.ENABLED;
import java.io.IOException;
import java.util.Arrays;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.HashUtil;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationContext.ResultAction;
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.registry.Resource;
import org.jboss.as.domain.controller.logging.DomainControllerLogger;
import org.jboss.as.repository.ContentReference;
import org.jboss.as.repository.ContentRepository;
import org.jboss.as.repository.HostFileRepository;
import org.jboss.as.server.controller.resources.DeploymentAttributes;
import org.jboss.as.server.deployment.DeploymentHandlerUtils;
import org.jboss.as.server.deployment.ModelContentReference;
import org.jboss.dmr.ModelNode;
/**
* Handles replacement in the runtime of one deployment by another.
*
* @author Brian Stansberry (c) 2011 Red Hat Inc.
*/
public class DeploymentFullReplaceHandler implements OperationStepHandler {
public static final String OPERATION_NAME = FULL_REPLACE_DEPLOYMENT;
private final ContentRepository contentRepository;
private final HostFileRepository fileRepository;
/**
* Constructor for a master Host Controller
*/
public DeploymentFullReplaceHandler(final ContentRepository contentRepository) {
this.contentRepository = contentRepository;
this.fileRepository = null;
}
/**
* Constructor for a slave Host Controller
*/
public DeploymentFullReplaceHandler(final HostFileRepository fileRepository) {
this.contentRepository = null;
this.fileRepository = fileRepository;
}
public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
// Validate op. Store any corrected values back to the op before manipulating further
ModelNode correctedOperation = operation.clone();
for (AttributeDefinition def : DeploymentAttributes.FULL_REPLACE_DEPLOYMENT_ATTRIBUTES.values()) {
def.validateAndSet(operation, correctedOperation);
}
// Pull data from the op
final String name = DeploymentAttributes.NAME.resolveModelAttribute(context, correctedOperation).asString();
final PathElement deploymentPath = PathElement.pathElement(DEPLOYMENT, name);
final PathAddress address = PathAddress.pathAddress(deploymentPath);
String runtimeName = correctedOperation.hasDefined(RUNTIME_NAME)
? DeploymentAttributes.RUNTIME_NAME.resolveModelAttribute(context, correctedOperation).asString() : name;
// clone the content param, so we can modify it to our own content
ModelNode content = correctedOperation.require(CONTENT).clone();
// Throw a specific exception if the replaced deployment doesn't already exist
// BES 2013/10/30 -- this is pointless; the readResourceForUpdate call will throw
// an exception with an equally informative message if the deployment doesn't exist
// final Resource root = context.readResource(PathAddress.EMPTY_ADDRESS);
// boolean exists = root.hasChild(deploymentPath);
// if (!exists) {
// throw createFailureException(MESSAGES.noDeploymentContentWithName(name));
// }
final ModelNode deploymentModel = context.readResourceForUpdate(PathAddress.pathAddress(deploymentPath)).getModel();
// Keep track of hash we are replacing so we can drop it from the content repo if all is well
ModelNode replacedContent = deploymentModel.get(CONTENT).get(0);
final byte[] replacedHash = replacedContent.hasDefined(CONTENT_HASH.getName())
? CONTENT_HASH.resolveModelAttribute(context, replacedContent).asBytes() : null;
// Set up the new content attribute
final byte[] newHash;
ModelNode contentItemNode = content.require(0);
if (contentItemNode.hasDefined(HASH)) {
newHash = contentItemNode.require(HASH).asBytes();
if (contentRepository != null) {
// We are the master DC. Validate that we actually have this content.
if (!contentRepository.hasContent(newHash)) {
throw createFailureException(DomainControllerLogger.ROOT_LOGGER.noDeploymentContentWithHash(HashUtil.bytesToHexString(newHash)));
}
} else {
// We are a slave controller
// Ensure the local repo has the files
fileRepository.getDeploymentFiles(ModelContentReference.fromModelAddress(address, newHash));
}
} else if (DeploymentHandlerUtils.hasValidContentAdditionParameterDefined(contentItemNode)) {
if (contentRepository == null) {
// This is a slave DC. We can't handle this operation; it should have been fixed up on the master DC
throw createFailureException(DomainControllerLogger.ROOT_LOGGER.slaveCannotAcceptUploads());
}
try {
// Store and transform operation
newHash = DeploymentUploadUtil.storeContentAndTransformOperation(context, correctedOperation, contentRepository);
} catch (IOException e) {
throw createFailureException(e.toString());
}
// Replace the op-provided content node with one that has a hash
contentItemNode = new ModelNode();
contentItemNode.get(HASH).set(newHash);
content = new ModelNode();
content.add(contentItemNode);
} else {
// Unmanaged content, the user is responsible for replication
newHash = null;
}
// Store state to the model
deploymentModel.get(RUNTIME_NAME).set(runtimeName);
deploymentModel.get(CONTENT).set(content);
// Update server groups
final Resource root = context.readResourceForUpdate(PathAddress.EMPTY_ADDRESS);
if (root.hasChild(PathElement.pathElement(SERVER_GROUP))) {
ModelNode enabled = correctedOperation.get(ENABLED.getName());
for (final Resource.ResourceEntry serverGroupResource : root.getChildren(SERVER_GROUP)) {
Resource deploymentResource = serverGroupResource.getChild(deploymentPath);
if (deploymentResource != null) {
ModelNode groupDeploymentModel = deploymentResource.getModel();
groupDeploymentModel.get(RUNTIME_NAME).set(runtimeName);
if (enabled.isDefined()) {
groupDeploymentModel.get(ENABLED.getName()).set(enabled);
}
}
}
}
context.completeStep(new OperationContext.ResultHandler() {
@Override
public void handleResult(ResultAction resultAction, OperationContext context, ModelNode operation) {
if (resultAction == ResultAction.KEEP) {
if (replacedHash != null && (newHash == null || !Arrays.equals(replacedHash, newHash))) {
// The old content is no longer used; clean from repos
ContentReference reference = ModelContentReference.fromModelAddress(address, replacedHash);
if (contentRepository != null) {
contentRepository.removeContent(reference);
} else {
fileRepository.deleteDeployment(reference);
}
}
if (newHash != null && contentRepository != null) {
contentRepository.addContentReference(ModelContentReference.fromModelAddress(address, newHash));
}
} else if (newHash != null && (replacedHash == null || !Arrays.equals(replacedHash, newHash))) {
// Due to rollback, the new content isn't used; clean from repos
ContentReference reference = ModelContentReference.fromModelAddress(address, newHash);
if (contentRepository != null) {
contentRepository.removeContent(reference);
} else {
fileRepository.deleteDeployment(reference);
}
}
}
});
}
}