/* * RHQ 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 as published by * the Free Software Foundation version 2 of the License. * * 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 for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.enterprise.agent.update; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; 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 java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; /** * Compares two files, an old one and a new one and if they differ, one will win and be kept as-is * in the new file's parent directory while the other one will get copied under a new name in * the new file's parent directory as a backup copy. * If the two files are identical, this task is a no-op and does nothing. * If backupextension is not specified, the file that is not to be kept will not be backed up. * * <copy-with-backup olddir="old" newdir="new" filename="file" keep="new" backupextension=".default" /> * * That will compare old/file to new/file and if they are different, the new one will be kept * as is in directory new and the old one will be copied into directory * 'new' but with a name of "file" appended with the backup extension ".default". * * The "old" file is always copied to the directory where the "new" file is located; the "keep" attribute * determines which file gets renamed with the backup extension and which keeps its file name intact. * * This allows us to tell which customized files from an old install should override the new install's * default files and which of the new default files should override any previous customizations made * to an old install. * * This loads in the content of both files to generate the MD5 - do not use this task on large binary * files. If we need to do this kind of task on files other than small text files, we need to refactor * the way the MD5's are calculated. * * @author John Mazzitelli */ public class CopyWithBackup extends Task { private File oldDirectory; private File newDirectory; private String filename; private String keep; // must be either "old" or "new" private String backupextension; private Boolean failonerror = Boolean.FALSE; public void setOlddir(File dir) { this.oldDirectory = dir; } public void setNewdir(File dir) { this.newDirectory = dir; } public void setFilename(String filename) { this.filename = filename; } public void setKeep(String keep) { this.keep = keep; } public void setBackupextension(String ext) { this.backupextension = ext; } public void setFailonerror(Boolean flag) { this.failonerror = flag; } /** * @see org.apache.tools.ant.Task#execute() */ @Override public void execute() throws BuildException { validateAttributes(); try { MessageDigest messageDigest; try { messageDigest = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { throw new BuildException(e); // should never occur; this would be bad - JRE has this builtin } File oldFile = new File(oldDirectory, filename); File newFile = new File(newDirectory, filename); log("* old file=" + oldFile, Project.MSG_DEBUG); log("* old file exists=" + oldFile.exists(), Project.MSG_DEBUG); log("* new file=" + newFile, Project.MSG_DEBUG); log("* new file exists=" + newFile.exists(), Project.MSG_DEBUG); log("* backup extension=" + backupextension, Project.MSG_DEBUG); log("* keep=" + keep, Project.MSG_DEBUG); // handle the special cases when one or both do not exist if (!oldFile.exists()) { if (!newFile.exists()) { log("old file and new file do not exist - nothing to be done"); return; } if (keep.equals("new")) { log("old file does not exist, new file does, keeping new file as-is"); } else { if (backupextension == null) { log("old file does not exist, new file does, removing new file"); if (!newFile.delete()) { throw new BuildException("Cannot remove new file [" + newFile + "]"); } } else { log("old file does not exist, new file does, backing up new file"); if (!newFile.renameTo(new File(newDirectory, filename + backupextension))) { throw new BuildException("Cannot backup new file [" + newFile + "]"); } } } } else if (!newFile.exists()) { if (keep.equals("new")) { if (backupextension == null) { log("old file exists, new file does not, do nothing"); } else { log("old file exists, new file does not, backup old"); copy(oldFile, new File(newDirectory, filename + backupextension)); } } else { log("old file exists, new file does not, copying old file"); copy(oldFile, newFile); // doesn't matter what backupextension is, there is no new file to care } } else { // both files exist - we need to compare them and do our thing if they are different byte[] oldMD5Bytes = messageDigest.digest(slurp(oldFile)); messageDigest.reset(); byte[] newMD5Bytes = messageDigest.digest(slurp(newFile)); if (MessageDigest.isEqual(oldMD5Bytes, newMD5Bytes)) { log("old file and new file are the same, nothing to do"); return; } if (keep.equals("new")) { if (backupextension == null) { log("files differ, keeping new, not backing up old"); } else { log("files differ, keeping new, backing up old"); copy(oldFile, new File(newDirectory, filename + backupextension)); } } else { if (backupextension == null) { log("files differ, keeping old, not backing up new"); newFile.delete(); copy(oldFile, newFile); } else { log("files differ, keeping old, backing up new"); newFile.renameTo(new File(newDirectory, filename + backupextension)); copy(oldFile, newFile); } } } } catch (BuildException e) { if (failonerror.booleanValue()) { throw e; } else { log("got a failure but will not exit: " + e); } } return; } private byte[] slurp(File file) throws BuildException { try { ByteArrayOutputStream out = new ByteArrayOutputStream(); copy(new FileInputStream(file), out); return out.toByteArray(); } catch (Exception e) { throw new BuildException("Stream data cannot be slurped", e); } } private void copy(File src, File dest) throws BuildException { if (dest.exists()) { throw new BuildException("Cannot copy [" + src + "] to [" + dest + "] because the latter exists"); } try { copy(new FileInputStream(src), new FileOutputStream(dest)); } catch (Exception e) { throw new BuildException("Cannot copy [" + src + "] to [" + dest + "]", e); } } private long copy(InputStream input, OutputStream output) throws RuntimeException { long numBytesCopied = 0; int bufferSize = 32768; try { // make sure we buffer the input input = new BufferedInputStream(input, bufferSize); byte[] buffer = new byte[bufferSize]; for (int bytesRead = input.read(buffer); bytesRead != -1; bytesRead = input.read(buffer)) { output.write(buffer, 0, bytesRead); numBytesCopied += bytesRead; } output.flush(); } catch (Exception e) { throw new BuildException("Stream data cannot be copied", e); } finally { try { output.close(); } catch (IOException ioe) { } try { input.close(); } catch (IOException ioe) { } } return numBytesCopied; } private void validateAttributes() throws BuildException { if (filename == null) { throw new BuildException("Must specify 'filename'"); } if (oldDirectory == null) { throw new BuildException("Must specify 'olddir' directory"); } if (newDirectory == null) { throw new BuildException("Must specify 'newdir' directory"); } else if (!newDirectory.exists()) { throw new BuildException("'newdir' directory must exist: " + newDirectory); } else if (!newDirectory.isDirectory()) { throw new BuildException("'newdir' must be a directory: " + newDirectory); } if (keep == null) { throw new BuildException("Must specify 'keep' as either 'old' or 'new'"); } else if (!"old".equals(keep) && !"new".equals(keep)) { throw new BuildException("'keep' must be one of: [old, new]"); } // an empty extension is the same as not specifying it at all if (backupextension != null && backupextension.trim().length() == 0) { backupextension = null; } } }