/*
* Jopr Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation, and/or the GNU Lesser
* General Public License, version 2.1, also as published by the Free
* Software Foundation.
*
* This program 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 General Public License and the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* and the GNU Lesser General Public License along with this program;
* if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package com.jboss.jbossnetwork.product.jbpm.handlers;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.jbpm.graph.exe.ExecutionContext;
/**
* JBPM process handler that is responsible for copying new files from a patch into the AS instance. The previous file
* will be renamed with a timestamp and .old extension to facilitate rolling back the patch.
*
* @author Jason Dobies
*/
public class BackupAndReplaceFileActionHandler extends BaseHandler {
private static final String TRANSITION_ORIGINAL_FILE_NOT_FOUND = "originalFileNotFound";
/**
* The location of the new file, which will be copied over the original file.
*/
private String replacementFileLocation;
/**
* The location of the file which will be replaced.
*/
private String originalFileLocation;
/**
* The location the original file will be backed up to. If this is not specified, we will generate a value for this
* of the format: originalFileLocation + ".#{timestamp}.old".
*/
private String backupFileLocation;
/**
* The location that the replacement file will be written to. This enables us to support renaming the file as it is
* replaced (one such use case would be a new version of a JAR being pushed out). See JBNADM-618.
*/
private String destinationFileLocation;
public void run(ExecutionContext executionContext) {
try {
// Verify we can work on the original file
try {
HandlerUtils.checkFilenameExists(originalFileLocation);
} catch (ActionHandlerException e) {
skip(executionContext, e, "File not replaced, no changes were made in this step.",
TRANSITION_ORIGINAL_FILE_NOT_FOUND);
return;
}
HandlerUtils.checkFilenameIsAFile(originalFileLocation);
HandlerUtils.checkFilenameIsWriteable(originalFileLocation);
// Verify we can backup the old file
HandlerUtils.checkFilenameDoesNotExist(backupFileLocation);
// Verify the new file to use is ok
HandlerUtils.checkFilenameExists(replacementFileLocation);
HandlerUtils.checkFilenameIsAFile(replacementFileLocation);
HandlerUtils.checkFilenameIsWriteable(replacementFileLocation);
// Support necessary for a file name change in the same step
updateDestinationFileLocation();
// If we get this far, all of the verifications have passed, so do the rename
rename(originalFileLocation, backupFileLocation);
} catch (Throwable e) {
error(executionContext, e, getErrorDescription() + " " + MESSAGE_NO_CHANGES, TRANSITION_ERROR);
return;
}
try {
rename(replacementFileLocation, destinationFileLocation);
} catch (Throwable e) {
error(executionContext, e, getErrorDescription() + " Changes were made in this step. "
+ "To return to the previous state you should rename ["
+ HandlerUtils.formatPath(getBackupFileLocation()) + "] to be ["
+ HandlerUtils.formatPath(getOriginalFileLocation()) + "].", TRANSITION_ERROR);
return;
}
complete(executionContext, "Successfully replaced [" + HandlerUtils.formatPath(getOriginalFileLocation())
+ "]. Original file was backed up to [" + HandlerUtils.formatPath(getBackupFileLocation()) + "].");
}
public String getDescription() {
return "Backup and replace [" + HandlerUtils.formatPath(originalFileLocation) + "].";
}
public void substituteVariables(ExecutionContext executionContext) throws ActionHandlerException {
setReplacementFileLocation(substituteVariable(replacementFileLocation, executionContext));
setOriginalFileLocation(substituteVariable(originalFileLocation, executionContext));
setBackupFileLocation(substituteVariable(backupFileLocation, executionContext));
setDestinationFileLocation(substituteVariable(destinationFileLocation, executionContext));
}
protected void checkProperties() throws ActionHandlerException {
HandlerUtils.checkIsSet("originalFileLocation", originalFileLocation);
HandlerUtils.checkIsSet("replacementFileLocation", replacementFileLocation);
HandlerUtils.checkIsSet("backupFileLocation", backupFileLocation);
HandlerUtils.checkIsSet("destinationFileLocation", destinationFileLocation);
}
public void setPropertyDefaults() {
if (getBackupFileLocation() == null) {
setBackupFileLocation(getOriginalFileLocation() + ".#{timestamp}.old");
}
if (getDestinationFileLocation() == null) {
setDestinationFileLocation(getOriginalFileLocation());
}
}
/**
* If the file is being renamed as it is being replaced, this method will detect and set the attributes to support
* it.
*
* @throws ActionHandlerException if the original file does not provide enough path information or if the new file
* name generated in this method will overwrite an existing file (unintended).
*/
private void updateDestinationFileLocation() throws ActionHandlerException {
File replacementFile = new File(replacementFileLocation);
String replacementFilename = replacementFile.getName();
File originalFile = new File(originalFileLocation);
String originalFilename = originalFile.getName();
// Checks for the case of the file being renamed. This implementation implies that the files are located in
// two different directories. This will be the case in practice, as the patch files are unzipped to a
// temporary location.
if (!replacementFilename.equalsIgnoreCase(originalFilename)) {
// This file name can't be relative since we need its parent in the next step
HandlerUtils.checkFilenameIsAbsolute(originalFileLocation);
//check that there is a parent
File parent = HandlerUtils.checkAndReturnParent(originalFileLocation);
File destinationFile = new File(parent, replacementFilename);
setDestinationFileLocation(destinationFile.getPath());
// Make sure the newly generated file name isn't going to clash with an existing file
HandlerUtils.checkFilenameDoesNotExist(destinationFileLocation);
}
}
/**
* Renames the file indicated in the from attribute to the name in the to attribute.
*
* @param from file being renamed
* @param to new name
*
* @throws ActionHandlerException if the rename fails
* @throws IOException if the file call to rename fails and we fall back to explicitly using input/output
* streams to perform a copy/delete implementation of rename
*/
private void rename(String from, String to) throws ActionHandlerException, IOException {
File fromFile = new File(from);
File toFile = new File(to);
if (!fromFile.renameTo(toFile)) {
// There is an issue with the rename call when the temp directory is on a different file system from
// the AS instance being patched. The following is to counter that. See JBNADM-927.
InputStream in = null;
OutputStream out = null;
try {
in = new FileInputStream(from);
out = new FileOutputStream(to);
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
if (toFile.exists()) {
fromFile.delete();
} else {
throw new ActionHandlerException("Could not write from [" + HandlerUtils.formatPath(from)
+ "] to [" + HandlerUtils.formatPath(to) + "].");
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
private String getErrorDescription() {
String desc = "Trying to back-up [" + HandlerUtils.formatPath(getOriginalFileLocation()) + "] to ["
+ HandlerUtils.formatPath(getBackupFileLocation()) + "], then replace it with file from ["
+ HandlerUtils.formatPath(getReplacementFileLocation()) + "]";
File replacementFile = new File(replacementFileLocation);
File originalFile = new File(originalFileLocation);
if (!replacementFile.getName().equalsIgnoreCase(originalFile.getName())) {
desc = desc + ", maintaining the replacement filename.";
} else {
desc = desc + ".";
}
return desc;
}
public String getReplacementFileLocation() {
return replacementFileLocation;
}
public void setReplacementFileLocation(String replacementFileLocation) {
this.replacementFileLocation = replacementFileLocation;
}
public String getOriginalFileLocation() {
return originalFileLocation;
}
public void setOriginalFileLocation(String originalFileLocation) {
this.originalFileLocation = originalFileLocation;
}
public String getBackupFileLocation() {
return backupFileLocation;
}
public void setBackupFileLocation(String backupFileLocation) {
this.backupFileLocation = backupFileLocation;
}
public String getDestinationFileLocation() {
return destinationFileLocation;
}
public void setDestinationFileLocation(String destinationFileLocation) {
this.destinationFileLocation = destinationFileLocation;
}
}