/*
* 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.server.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.DEPLOYMENT_DEPLOYED_NOTIFICATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOYMENT_UNDEPLOYED_NOTIFICATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.NAME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RUNTIME_NAME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SERVER_BOOTING;
import static org.jboss.as.server.controller.resources.DeploymentAttributes.CONTENT_HASH;
import static org.jboss.as.server.controller.resources.DeploymentAttributes.OWNER;
import static org.jboss.as.server.deployment.DeploymentHandlerUtils.getContents;
import static org.jboss.msc.service.ServiceController.Mode.REMOVE;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.atomic.AtomicBoolean;
import org.jboss.as.controller.OperationContext;
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.RunningMode;
import org.jboss.as.controller.notification.Notification;
import org.jboss.as.controller.registry.ImmutableManagementResourceRegistration;
import org.jboss.as.controller.registry.ManagementResourceRegistration;
import org.jboss.as.controller.registry.Resource;
import org.jboss.as.controller.registry.Resource.ResourceEntry;
import org.jboss.as.controller.services.path.PathManager;
import org.jboss.as.controller.services.path.PathManagerService;
import org.jboss.as.repository.ContentReference;
import org.jboss.as.repository.ContentRepository;
import org.jboss.as.server.ServerEnvironment;
import org.jboss.as.server.controller.resources.DeploymentAttributes;
import org.jboss.as.server.deploymentoverlay.DeploymentOverlayIndex;
import org.jboss.as.server.logging.ServerLogger;
import org.jboss.as.server.services.security.AbstractVaultReader;
import org.jboss.dmr.ModelNode;
import org.jboss.msc.service.AbstractServiceListener;
import org.jboss.msc.service.ServiceController;
import org.jboss.msc.service.ServiceName;
import org.jboss.msc.service.ServiceRegistry;
import org.jboss.msc.service.ServiceTarget;
import org.jboss.vfs.VirtualFile;
/**
* Utility methods used by operation handlers involved with deployment.
* <p/>
* This class is part of the runtime operation and should not have any reference to dmr.
*
* @author Brian Stansberry (c) 2011 Red Hat Inc.
*/
public class DeploymentHandlerUtil {
static class ContentItem {
// either hash or <path, relativeTo, isArchive>
private final byte[] hash;
private final String path;
private final String relativeTo;
private final boolean isArchive;
ContentItem(final byte[] hash) {
this(hash, true);
}
ContentItem(final byte[] hash, boolean isArchive) {
assert hash != null : "hash is null";
this.hash = hash;
this.isArchive = isArchive;
this.path = null;
this.relativeTo = null;
}
ContentItem(final String path, final String relativeTo, final boolean isArchive) {
assert path != null : "path is null";
this.path = path;
this.relativeTo = relativeTo;
this.isArchive = isArchive;
this.hash = null;
}
byte[] getHash() {
return hash;
}
}
private static final String MANAGED_CONTENT = "managed-exploded";
private DeploymentHandlerUtil() {
}
public static void deploy(final OperationContext context, final ModelNode operation, final String deploymentUnitName, final String managementName, final AbstractVaultReader vaultReader, final ContentItem... contents) throws OperationFailedException {
assert contents != null : "contents is null";
if (context.isNormalServer()) {
//Checking for duplicate runtime name
PathAddress deploymentsAddress = context.getCurrentAddress().getParent();
Resource deploymentsParentResource = context.readResourceFromRoot(deploymentsAddress);
for(ResourceEntry deployment : deploymentsParentResource.getChildren(DEPLOYMENT)) {
if(!managementName.equals(deployment.getName())) {
ModelNode deploymentModel = deployment.getModel();
if(deploymentUnitName.equals(DeploymentAttributes.RUNTIME_NAME.resolveModelAttribute(context, deploymentModel).asString())
&& DeploymentAttributes.ENABLED.resolveModelAttribute(context, deploymentModel).asBoolean()) {
throw ServerLogger.ROOT_LOGGER.runtimeNameMustBeUnique(managementName, deploymentUnitName);
}
}
}
final Resource deployment = context.readResourceForUpdate(PathAddress.EMPTY_ADDRESS);
final ImmutableManagementResourceRegistration registration = context.getResourceRegistration();
final ManagementResourceRegistration mutableRegistration = context.getResourceRegistrationForUpdate();
DeploymentResourceSupport.cleanup(deployment);
ModelNode notificationData = new ModelNode();
notificationData.get(NAME).set(managementName);
notificationData.get(SERVER_BOOTING).set(context.isBooting());
if (operation.hasDefined(OWNER.getName())) {
try {
notificationData.get(OWNER.getName()).set(OWNER.resolveModelAttribute(context, operation));
} catch (OperationFailedException ex) {//No resolvable owner we won't set one
}
}
notificationData.get(DEPLOYMENT).set(deploymentUnitName);
context.emit(new Notification(DEPLOYMENT_DEPLOYED_NOTIFICATION, context.getCurrentAddress(), ServerLogger.ROOT_LOGGER.deploymentDeployedNotification(managementName, deploymentUnitName), notificationData));
context.addStep(new OperationStepHandler() {
public void execute(OperationContext context, ModelNode operation) {
final ServiceName deploymentUnitServiceName = Services.deploymentUnitName(deploymentUnitName);
final ServiceRegistry serviceRegistry = context.getServiceRegistry(true);
final ServiceController<?> deploymentController = serviceRegistry.getService(deploymentUnitServiceName);
if (deploymentController != null) {
deploymentController.setMode(ServiceController.Mode.ACTIVE);
context.completeStep(new OperationContext.RollbackHandler() {
@Override
public void handleRollback(OperationContext context, ModelNode operation) {
deploymentController.setMode(ServiceController.Mode.NEVER);
}
});
} else {
doDeploy(context, deploymentUnitName, managementName, deployment, registration, mutableRegistration, vaultReader, contents);
context.completeStep(new OperationContext.ResultHandler() {
@Override
public void handleResult(OperationContext.ResultAction resultAction, OperationContext context, ModelNode operation) {
if(resultAction == OperationContext.ResultAction.ROLLBACK) {
if (context.hasFailureDescription()) {
ServerLogger.ROOT_LOGGER.deploymentRolledBack(deploymentUnitName, getFormattedFailureDescription(context));
} else {
ServerLogger.ROOT_LOGGER.deploymentRolledBackWithNoMessage(deploymentUnitName);
}
} else {
ServerLogger.ROOT_LOGGER.deploymentDeployed(managementName, deploymentUnitName);
}
}
});
}
}
}, OperationContext.Stage.RUNTIME);
}
}
public static void doDeploy(final OperationContext context, final String deploymentUnitName, final String managementName,
final Resource deploymentResource, final ImmutableManagementResourceRegistration registration,
final ManagementResourceRegistration mutableRegistration, final AbstractVaultReader vaultReader, final ContentItem... contents) {
final ServiceName deploymentUnitServiceName = Services.deploymentUnitName(deploymentUnitName);
final ServiceTarget serviceTarget = context.getServiceTarget();
final ServiceController<?> contentService;
// TODO: overlay service
final ServiceName contentsServiceName = deploymentUnitServiceName.append("contents");
boolean isExplodedContent = false;
if (contents[0].hash != null) {
if (contents[0].isArchive) {
contentService = ContentServitor.addService(serviceTarget, contentsServiceName, contents[0].hash);
} else {
isExplodedContent = true;
contentService = ManagedExplodedContentServitor.addService(serviceTarget, contentsServiceName, managementName, contents[0].hash);
}
}
else {
final String path = contents[0].path;
final String relativeTo = contents[0].relativeTo;
contentService = PathContentServitor.addService(serviceTarget, contentsServiceName, path, relativeTo);
}
DeploymentOverlayIndex overlays = DeploymentOverlayIndex.createDeploymentOverlayIndex(context);
final RootDeploymentUnitService service = new RootDeploymentUnitService(deploymentUnitName, managementName, null,
registration, mutableRegistration, deploymentResource, context.getCapabilityServiceSupport(), vaultReader, overlays,
isExplodedContent);
final ServiceController<DeploymentUnit> deploymentUnitController = serviceTarget.addService(deploymentUnitServiceName, service)
.addDependency(Services.JBOSS_DEPLOYMENT_CHAINS, DeployerChains.class, service.getDeployerChainsInjector())
.addDependency(DeploymentMountProvider.SERVICE_NAME, DeploymentMountProvider.class, service.getServerDeploymentRepositoryInjector())
.addDependency(PathManagerService.SERVICE_NAME, PathManager.class, service.getPathManagerInjector())
.addDependency(contentsServiceName, VirtualFile.class, service.getContentsInjector())
.setInitialMode(ServiceController.Mode.ACTIVE)
.install();
contentService.addListener(new AbstractServiceListener<Object>() {
@Override
public void transition(final ServiceController<?> controller, final ServiceController.Transition transition) {
if (transition == ServiceController.Transition.REMOVING_to_REMOVED) {
deploymentUnitController.setMode(REMOVE);
}
}
});
}
public static void redeploy(final OperationContext context, final String deploymentUnitName,
final String managementName, final AbstractVaultReader vaultReader, final ContentItem... contents) throws OperationFailedException {
assert contents != null : "contents is null";
if (context.isNormalServer()) {
//
final Resource deployment = context.readResourceForUpdate(PathAddress.EMPTY_ADDRESS);
final ImmutableManagementResourceRegistration registration = context.getResourceRegistration();
final ManagementResourceRegistration mutableRegistration = context.getResourceRegistrationForUpdate();
DeploymentResourceSupport.cleanup(deployment);
context.addStep(new OperationStepHandler() {
public void execute(final OperationContext context, ModelNode operation) throws OperationFailedException {
final ServiceName deploymentUnitServiceName = Services.deploymentUnitName(deploymentUnitName);
context.removeService(deploymentUnitServiceName);
context.removeService(deploymentUnitServiceName.append("contents"));
final AtomicBoolean logged = new AtomicBoolean(false);
context.addStep(new OperationStepHandler() {
@Override
public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
doDeploy(context, deploymentUnitName, managementName, deployment, registration, mutableRegistration, vaultReader, contents);
context.completeStep(new OperationContext.ResultHandler() {
@Override
public void handleResult(OperationContext.ResultAction resultAction, OperationContext context, ModelNode operation) {
if (resultAction == OperationContext.ResultAction.ROLLBACK) {
if (context.hasFailureDescription()) {
ServerLogger.ROOT_LOGGER.redeployRolledBack(deploymentUnitName, getFormattedFailureDescription(context));
logged.set(true);
} else {
ServerLogger.ROOT_LOGGER.redeployRolledBackWithNoMessage(deploymentUnitName);
logged.set(true);
}
} else {
ServerLogger.ROOT_LOGGER.deploymentRedeployed(deploymentUnitName);
}
}
});
}
}, OperationContext.Stage.RUNTIME, true);
context.completeStep(new OperationContext.RollbackHandler() {
@Override
public void handleRollback(OperationContext context, ModelNode operation) {
doDeploy(context, deploymentUnitName, managementName, deployment, registration, mutableRegistration, vaultReader, contents);
if (!logged.get()) {
if (context.hasFailureDescription()) {
ServerLogger.ROOT_LOGGER.undeploymentRolledBack(deploymentUnitName, context.getFailureDescription().asString());
} else {
ServerLogger.ROOT_LOGGER.undeploymentRolledBackWithNoMessage(deploymentUnitName);
}
}
}
});
}
}, OperationContext.Stage.RUNTIME);
}
}
public static void replace(final OperationContext context, final ModelNode originalDeployment, final String deploymentUnitName, final String managementName,
final String replacedDeploymentUnitName, final AbstractVaultReader vaultReader, final ContentItem... contents) throws OperationFailedException {
assert contents != null : "contents is null";
if (context.isNormalServer()) {
//
final PathElement path = PathElement.pathElement(DEPLOYMENT, managementName);
final Resource deployment = context.readResourceForUpdate(PathAddress.EMPTY_ADDRESS.append(path));
final ImmutableManagementResourceRegistration registration = context.getResourceRegistration().getSubModel(PathAddress.EMPTY_ADDRESS.append(path));
final ManagementResourceRegistration mutableRegistration = context.getResourceRegistrationForUpdate().getSubModel(PathAddress.EMPTY_ADDRESS.append(path));
DeploymentResourceSupport.cleanup(deployment);
context.addStep(new OperationStepHandler() {
public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
final ServiceName replacedDeploymentUnitServiceName = Services.deploymentUnitName(replacedDeploymentUnitName);
final ServiceName replacedContentsServiceName = replacedDeploymentUnitServiceName.append("contents");
context.removeService(replacedContentsServiceName);
context.removeService(replacedDeploymentUnitServiceName);
doDeploy(context, deploymentUnitName, managementName, deployment, registration, mutableRegistration, vaultReader, contents);
context.completeStep(new OperationContext.ResultHandler() {
@Override
public void handleResult(OperationContext.ResultAction resultAction, OperationContext context, ModelNode operation) {
if (resultAction == OperationContext.ResultAction.ROLLBACK) {
DeploymentResourceSupport.cleanup(deployment);
final String name = originalDeployment.require(NAME).asString();
final String runtimeName = originalDeployment.require(RUNTIME_NAME).asString();
final DeploymentHandlerUtil.ContentItem[] contents = getContents(originalDeployment.require(CONTENT));
doDeploy(context, runtimeName, name, deployment, registration, mutableRegistration, vaultReader, contents);
if (context.hasFailureDescription()) {
ServerLogger.ROOT_LOGGER.replaceRolledBack(replacedDeploymentUnitName, deploymentUnitName, getFormattedFailureDescription(context));
} else {
ServerLogger.ROOT_LOGGER.replaceRolledBackWithNoMessage(replacedDeploymentUnitName, deploymentUnitName);
}
} else {
ServerLogger.ROOT_LOGGER.deploymentReplaced(replacedDeploymentUnitName, deploymentUnitName);
}
}
});
}
}, OperationContext.Stage.RUNTIME);
}
}
public static void undeploy(final OperationContext context, final ModelNode operation, final String managementName, final String runtimeName, final AbstractVaultReader vaultReader) {
if (context.isNormalServer()) {
// WFCORE-1577 -- the resource we want may not be at the op address if this is called for full-replace-deployment
PathAddress resourceAddress = context.getCurrentAddress().size() == 0 ? PathAddress.pathAddress(DEPLOYMENT, managementName) : PathAddress.EMPTY_ADDRESS;
final Resource deployment = context.readResourceForUpdate(resourceAddress);
final ImmutableManagementResourceRegistration registration = context.getResourceRegistration().getSubModel(resourceAddress);
final ManagementResourceRegistration mutableRegistration = context.getResourceRegistrationForUpdate().getSubModel(resourceAddress);
DeploymentResourceSupport.cleanup(deployment);
ModelNode notificationData = new ModelNode();
notificationData.get(NAME).set(managementName);
notificationData.get(SERVER_BOOTING).set(context.isBooting());
if (operation.hasDefined(OWNER.getName())) {
try {
notificationData.get(OWNER.getName()).set(OWNER.resolveModelAttribute(context, operation));
} catch (OperationFailedException ex) {//No resolvable owner we won't set one
}
}
notificationData.get(DEPLOYMENT).set(runtimeName);
PathAddress pathAddress = context.getCurrentAddress().size() == 0 ? PathAddress.pathAddress(DEPLOYMENT, managementName) : context.getCurrentAddress();
context.emit(new Notification(DEPLOYMENT_UNDEPLOYED_NOTIFICATION, pathAddress, ServerLogger.ROOT_LOGGER.deploymentUndeployedNotification(managementName, runtimeName), notificationData));
context.addStep(new OperationStepHandler() {
@Override
public void execute(OperationContext context, ModelNode operation) {
final ServiceName deploymentUnitServiceName = Services.deploymentUnitName(runtimeName);
context.removeService(deploymentUnitServiceName);
context.removeService(deploymentUnitServiceName.append("contents"));
context.completeStep(new OperationContext.ResultHandler() {
@Override
public void handleResult(OperationContext.ResultAction resultAction, OperationContext context, ModelNode operation) {
if(resultAction == OperationContext.ResultAction.ROLLBACK) {
final ModelNode model = context.readResource(PathAddress.EMPTY_ADDRESS).getModel();
final DeploymentHandlerUtil.ContentItem[] contents = getContents(model.require(CONTENT));
doDeploy(context, runtimeName, managementName, deployment, registration, mutableRegistration, vaultReader, contents);
if (context.hasFailureDescription()) {
ServerLogger.ROOT_LOGGER.undeploymentRolledBack(runtimeName, getFormattedFailureDescription(context));
} else {
ServerLogger.ROOT_LOGGER.undeploymentRolledBackWithNoMessage(runtimeName);
}
} else {
ServerLogger.ROOT_LOGGER.deploymentUndeployed(managementName, runtimeName);
}
}
});
}
}, OperationContext.Stage.RUNTIME);
}
}
public static boolean isManaged(ModelNode contentItem) {
return !contentItem.hasDefined(DeploymentAttributes.CONTENT_PATH.getName());
}
public static boolean isArchive(ModelNode contentItem) {
return contentItem.get(DeploymentAttributes.CONTENT_ARCHIVE.getName()).asBoolean(true);
}
public static ModelNode getContentItem(Resource resource) {
return resource.getModel().get(DeploymentAttributes.CONTENT_RESOURCE.getName()).get(0);
}
static Path getExplodedDeploymentRoot(ServerEnvironment serverEnvironment, String deploymentManagementName) {
return Paths.get(serverEnvironment.getServerDataDir().getAbsolutePath()).resolve(MANAGED_CONTENT).resolve(deploymentManagementName);
}
private static String getFormattedFailureDescription(OperationContext context) {
ModelNode failureDescNode = context.getFailureDescription();
String failureDesc = failureDescNode.toString();
// // Strip the wrapping {} from ModelType.OBJECT types
// if (failureDescNode.getType() == ModelType.OBJECT && failureDesc.length() > 2
// && failureDesc.charAt(0) == '{' && failureDesc.charAt(failureDesc.length() - 1) == '}') {
// failureDesc = failureDesc.substring(1, failureDesc.length() - 1);
// }
if (failureDesc.contains("\n") && failureDesc.charAt(0) != '\n') {
failureDesc = "\n" + failureDesc;
}
return failureDesc;
}
public static byte[] addFromHash(ContentRepository contentRepository, ModelNode contentItemNode, String deploymentName, PathAddress address, OperationContext context) throws OperationFailedException {
byte[] hash = contentItemNode.require(CONTENT_HASH.getName()).asBytes();
ContentReference reference = ModelContentReference.fromModelAddress(address, hash);
if (!contentRepository.syncContent(reference)) {
if (context.isBooting()) {
if (context.getRunningMode() == RunningMode.ADMIN_ONLY) {
// The deployment content is missing, which would be a fatal boot error if we were going to actually
// install services. In ADMIN-ONLY mode we allow it to give the admin a chance to correct the problem
ServerLogger.ROOT_LOGGER.reportAdminOnlyMissingDeploymentContent(reference.getHexHash(), deploymentName);
} else {
throw ServerLogger.ROOT_LOGGER.noSuchDeploymentContentAtBoot(reference.getHexHash(), deploymentName);
}
} else {
throw ServerLogger.ROOT_LOGGER.noSuchDeploymentContent(reference.getHexHash());
}
}
return hash;
}
}