/*
|
* CDDL HEADER START
|
*
|
* The contents of this file are subject to the terms of the
|
* Common Development and Distribution License, Version 1.0 only
|
* (the "License"). You may not use this file except in compliance
|
* with the License.
|
*
|
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
|
* or http://forgerock.org/license/CDDLv1.0.html.
|
* See the License for the specific language governing permissions
|
* and limitations under the License.
|
*
|
* When distributing Covered Code, include this CDDL HEADER in each
|
* file and include the License file at legal-notices/CDDLv1_0.txt.
|
* If applicable, add the following below this CDDL HEADER, with the
|
* fields enclosed by brackets "[]" replaced with your own identifying
|
* information:
|
* Portions Copyright [yyyy] [name of copyright owner]
|
*
|
* CDDL HEADER END
|
*
|
*
|
* Copyright 2006-2008 Sun Microsystems, Inc.
|
* Portions Copyright 2012-2014 ForgeRock AS.
|
*/
|
package org.opends.quicksetup.util;
|
|
import java.io.*;
|
|
import org.forgerock.i18n.LocalizableMessage;
|
import org.forgerock.i18n.slf4j.LocalizedLogger;
|
import org.opends.quicksetup.*;
|
import org.opends.server.util.StaticUtils;
|
|
import static org.opends.messages.QuickSetupMessages.*;
|
|
/**
|
* Utility class for use by applications containing methods for managing
|
* file system files. This class handles application notifications for
|
* interesting events.
|
*/
|
public class FileManager {
|
|
/**
|
* Describes the approach taken to deleting a file or directory.
|
*/
|
public enum DeletionPolicy {
|
|
/**
|
* Delete the file or directory immediately.
|
*/
|
DELETE_IMMEDIATELY,
|
|
/**
|
* Mark the file or directory for deletion after the JVM has exited.
|
*/
|
DELETE_ON_EXIT,
|
|
/**
|
* First try to delete the file immediately. If the deletion was
|
* unsuccessful mark the file for deleteion when the JVM has
|
* existed.
|
*/
|
DELETE_ON_EXIT_IF_UNSUCCESSFUL
|
|
}
|
|
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
|
|
private Application application = null;
|
|
/**
|
* Creates a new file manager.
|
*/
|
public FileManager() {
|
// do nothing;
|
}
|
|
/**
|
* Creates a new file manager.
|
* @param app Application managing files to which progress notifications
|
* will be sent
|
*/
|
public FileManager(Application app) {
|
this.application = app;
|
}
|
|
/**
|
* Recursively copies any files or directories appearing in
|
* <code>source</code> or a subdirectory of <code>source</code>
|
* to the corresponding directory under <code>target</code>. Files
|
* in under <code>source</code> are not copied to <code>target</code>
|
* if a file by the same name already exists in <code>target</code>.
|
*
|
* @param source source directory
|
* @param target target directory
|
* @throws ApplicationException if there is a problem copying files
|
*/
|
public void synchronize(File source, File target)
|
throws ApplicationException
|
{
|
if (source != null && target != null) {
|
String[] sourceFileNames = source.list();
|
if (sourceFileNames != null) {
|
for (String sourceFileName : sourceFileNames) {
|
File sourceFile = new File(source, sourceFileName);
|
copyRecursively(sourceFile, target, null, false);
|
}
|
}
|
}
|
}
|
|
/**
|
* Renames the source file to the target file. If the target file exists
|
* it is first deleted. The rename and delete operation return values
|
* are checked for success and if unsuccessful, this method throws an
|
* exception.
|
*
|
* @param fileToRename The file to rename.
|
* @param target The file to which <code>fileToRename</code> will be
|
* moved.
|
* @throws ApplicationException If a problem occurs while attempting to rename
|
* the file. On the Windows platform, this typically
|
* indicates that the file is in use by this or another
|
* application.
|
*/
|
public void rename(File fileToRename, File target)
|
throws ApplicationException {
|
if (fileToRename != null && target != null) {
|
synchronized (target) {
|
if (target.exists()) {
|
if (!target.delete()) {
|
throw new ApplicationException(
|
ReturnCode.FILE_SYSTEM_ACCESS_ERROR,
|
INFO_ERROR_DELETING_FILE.get(Utils.getPath(target)), null);
|
}
|
}
|
}
|
if (!fileToRename.renameTo(target)) {
|
throw new ApplicationException(
|
ReturnCode.FILE_SYSTEM_ACCESS_ERROR,
|
INFO_ERROR_RENAMING_FILE.get(Utils.getPath(fileToRename),
|
Utils.getPath(target)), null);
|
}
|
}
|
}
|
|
|
/**
|
* Move a file.
|
* @param object File to move
|
* @param newParent File representing new parent directory
|
* @throws ApplicationException if something goes wrong
|
*/
|
public void move(File object, File newParent)
|
throws ApplicationException
|
{
|
move(object, newParent, null);
|
}
|
|
/**
|
* Move a file.
|
* @param object File to move
|
* @param newParent File representing new parent directory
|
* @param filter that will be asked whether or not the operation should be
|
* performed
|
* @throws ApplicationException if something goes wrong
|
*/
|
public void move(File object, File newParent, FileFilter filter)
|
throws ApplicationException
|
{
|
// TODO: application notification
|
if (filter == null || filter.accept(object)) {
|
new MoveOperation(object, newParent).apply();
|
}
|
}
|
|
/**
|
* Deletes a single file or directory.
|
* @param object File to delete
|
* @throws ApplicationException if something goes wrong
|
*/
|
public void delete(File object)
|
throws ApplicationException
|
{
|
delete(object, null);
|
}
|
|
/**
|
* Deletes a single file or directory.
|
* @param object File to delete
|
* @param filter that will be asked whether or not the operation should be
|
* performed
|
* @throws ApplicationException if something goes wrong
|
*/
|
public void delete(File object, FileFilter filter)
|
throws ApplicationException
|
{
|
if (filter == null || filter.accept(object)) {
|
new DeleteOperation(object, DeletionPolicy.DELETE_IMMEDIATELY).apply();
|
}
|
}
|
|
/**
|
* Deletes the children of a directory.
|
*
|
* @param parentDir the directory whose children is deleted
|
* @throws ApplicationException if there is a problem deleting children
|
*/
|
public void deleteChildren(File parentDir) throws ApplicationException {
|
if (parentDir != null && parentDir.exists() && parentDir.isDirectory()) {
|
File[] children = parentDir.listFiles();
|
if (children != null) {
|
for (File child : children) {
|
deleteRecursively(child);
|
}
|
}
|
}
|
}
|
|
/**
|
* Deletes everything below the specified file.
|
*
|
* @param file the path to be deleted.
|
* @throws org.opends.quicksetup.ApplicationException if something goes wrong.
|
*/
|
public void deleteRecursively(File file) throws ApplicationException {
|
deleteRecursively(file, null,
|
FileManager.DeletionPolicy.DELETE_IMMEDIATELY);
|
}
|
|
/**
|
* Deletes everything below the specified file.
|
*
|
* @param file the path to be deleted.
|
* @param filter the filter of the files to know if the file can be deleted
|
* directly or not.
|
* @param deletePolicy describes how deletions are to be made
|
* JVM exits rather than deleting the files immediately.
|
* @throws ApplicationException if something goes wrong.
|
*/
|
public void deleteRecursively(File file, FileFilter filter,
|
DeletionPolicy deletePolicy)
|
throws ApplicationException {
|
operateRecursively(new DeleteOperation(file, deletePolicy), filter);
|
}
|
|
/**
|
* Copies everything below the specified file.
|
*
|
* @param objectFile the file to be copied.
|
* @param destDir the directory to copy the file to
|
* @return File representing the destination
|
* @throws ApplicationException if something goes wrong.
|
*/
|
public File copy(File objectFile, File destDir)
|
throws ApplicationException
|
{
|
CopyOperation co = new CopyOperation(objectFile, destDir, false);
|
co.apply();
|
return co.getDestination();
|
}
|
|
/**
|
* Copies everything below the specified file.
|
*
|
* @param objectFile the file to be copied.
|
* @param destDir the directory to copy the file to
|
* @param overwrite overwrite destination files.
|
* @return File representing the destination
|
* @throws ApplicationException if something goes wrong.
|
*/
|
public File copy(File objectFile, File destDir, boolean overwrite)
|
throws ApplicationException
|
{
|
CopyOperation co = new CopyOperation(objectFile, destDir, overwrite);
|
co.apply();
|
return co.getDestination();
|
}
|
|
/**
|
* Copies everything below the specified file.
|
*
|
* @param objectFile the file to be copied.
|
* @param destDir the directory to copy the file to
|
* @throws ApplicationException if something goes wrong.
|
*/
|
public void copyRecursively(File objectFile, File destDir)
|
throws ApplicationException
|
{
|
copyRecursively(objectFile, destDir, null);
|
}
|
|
/**
|
* Copies everything below the specified file.
|
*
|
* @param objectFile the file to be copied.
|
* @param destDir the directory to copy the file to
|
* @param filter the filter of the files to know if the file can be copied
|
* directly or not.
|
* @throws ApplicationException if something goes wrong.
|
*/
|
public void copyRecursively(File objectFile, File destDir, FileFilter filter)
|
throws ApplicationException {
|
copyRecursively(objectFile, destDir, filter, false);
|
}
|
|
/**
|
* Copies everything below the specified file.
|
*
|
* @param objectFile the file to be copied.
|
* @param destDir the directory to copy the file to
|
* @param filter the filter of the files to know if the file can be copied
|
* directly or not.
|
* @param overwrite overwrite destination files.
|
* @throws ApplicationException if something goes wrong.
|
*/
|
public void copyRecursively(File objectFile, File destDir,
|
FileFilter filter, boolean overwrite)
|
throws ApplicationException {
|
operateRecursively(new CopyOperation(objectFile, destDir, overwrite),
|
filter);
|
}
|
|
/**
|
* Determines whether or not two files differ in content.
|
*
|
* @param f1 file to compare
|
* @param f2 file to compare
|
* @return boolean where true indicates that two files differ
|
* @throws IOException if there is a problem reading the files' conents
|
*/
|
public boolean filesDiffer(File f1, File f2) throws IOException {
|
boolean differ = false;
|
FileReader fr1 = new FileReader(f1);
|
FileReader fr2 = new FileReader(f2);
|
try {
|
boolean done = false;
|
while (!differ && !done) {
|
int c1 = fr1.read();
|
int c2 = fr2.read();
|
differ = c1 != c2;
|
done = c1 == -1 || c2 == -1;
|
}
|
} finally {
|
fr1.close();
|
fr2.close();
|
}
|
return differ;
|
}
|
|
private void operateRecursively(FileOperation op, FileFilter filter)
|
throws ApplicationException {
|
File file = op.getObjectFile();
|
if (file.exists()) {
|
if (file.isFile()) {
|
if (filter != null) {
|
if (filter.accept(file)) {
|
op.apply();
|
}
|
} else {
|
op.apply();
|
}
|
} else {
|
File[] children = file.listFiles();
|
if (children != null) {
|
for (File aChildren : children) {
|
FileOperation newOp = op.copyForChild(aChildren);
|
operateRecursively(newOp, filter);
|
}
|
}
|
if (filter != null) {
|
if (filter.accept(file)) {
|
op.apply();
|
}
|
} else {
|
op.apply();
|
}
|
}
|
} else {
|
// Just tell that the file/directory does not exist.
|
if (application != null) {
|
application.notifyListeners(application.getFormattedWarning(
|
INFO_FILE_DOES_NOT_EXIST.get(file)));
|
}
|
logger.info(LocalizableMessage.raw("file '" + file + "' does not exist"));
|
}
|
}
|
|
/**
|
* A file operation.
|
*/
|
private abstract class FileOperation {
|
|
private File objectFile = null;
|
|
/**
|
* Creates a new file operation.
|
* @param objectFile to be operated on
|
*/
|
public FileOperation(File objectFile) {
|
this.objectFile = objectFile;
|
}
|
|
/**
|
* Gets the file to be operated on.
|
* @return File to be operated on
|
*/
|
protected File getObjectFile() {
|
return objectFile;
|
}
|
|
/**
|
* Make a copy of this class for the child file.
|
* @param child to act as the new file object
|
* @return FileOperation as the same type as this class
|
*/
|
abstract public FileOperation copyForChild(File child);
|
|
/**
|
* Execute this operation.
|
* @throws ApplicationException if there is a problem.
|
*/
|
abstract public void apply() throws ApplicationException;
|
|
}
|
|
/**
|
* A copy operation.
|
*/
|
private class CopyOperation extends FileOperation {
|
|
private File destination;
|
|
private boolean overwrite;
|
|
/**
|
* Create a new copy operation.
|
* @param objectFile to copy
|
* @param destDir to copy to
|
* @param overwrite if true copy should overwrite any existing file
|
*/
|
public CopyOperation(File objectFile, File destDir, boolean overwrite) {
|
super(objectFile);
|
this.destination = new File(destDir, objectFile.getName());
|
this.overwrite = overwrite;
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public FileOperation copyForChild(File child) {
|
return new CopyOperation(child, destination, overwrite);
|
}
|
|
/**
|
* Returns the destination file that is the result of copying
|
* <code>objectFile</code> to <code>destDir</code>.
|
* @return The destination file.
|
*/
|
public File getDestination() {
|
return this.destination;
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public void apply() throws ApplicationException {
|
File objectFile = getObjectFile();
|
if (objectFile.isDirectory()) {
|
if (!destination.exists()) {
|
destination.mkdirs();
|
}
|
} else {
|
|
// If overwriting and the destination exists then kill it
|
if (destination.exists() && overwrite) {
|
deleteRecursively(destination);
|
}
|
|
if (!destination.exists()) {
|
if (Utils.insureParentsExist(destination)) {
|
if ((application != null) && application.isVerbose()) {
|
application.notifyListeners(application.getFormattedWithPoints(
|
INFO_PROGRESS_COPYING_FILE.get(
|
objectFile.getAbsolutePath(),
|
destination.getAbsolutePath())));
|
}
|
logger.info(LocalizableMessage.raw("copying file '" +
|
objectFile.getAbsolutePath() + "' to '" +
|
destination.getAbsolutePath() + "'"));
|
FileInputStream fis = null;
|
FileOutputStream fos = null;
|
try {
|
fis = new FileInputStream(objectFile);
|
fos = new FileOutputStream(destination);
|
byte[] buf = new byte[1024];
|
int i;
|
while ((i = fis.read(buf)) != -1) {
|
fos.write(buf, 0, i);
|
}
|
if (destination.exists()) {
|
// TODO: set the file's permissions. This is made easier in
|
// Java 1.6 but until then use the TestUtilities methods
|
if (Utils.isUnix()) {
|
String permissions =
|
Utils.getFileSystemPermissions(objectFile);
|
Utils.setPermissionsUnix(
|
Utils.getPath(destination),
|
permissions);
|
}
|
}
|
|
if ((application != null) && application.isVerbose()) {
|
application.notifyListeners(
|
application.getFormattedDoneWithLineBreak());
|
}
|
|
} catch (Exception e) {
|
LocalizableMessage errMsg = INFO_ERROR_COPYING_FILE.get(
|
objectFile.getAbsolutePath(),
|
destination.getAbsolutePath());
|
throw new ApplicationException(
|
ReturnCode.FILE_SYSTEM_ACCESS_ERROR,
|
errMsg, null);
|
} finally {
|
StaticUtils.close(fis, fos);
|
}
|
} else {
|
LocalizableMessage errMsg = INFO_ERROR_COPYING_FILE.get(
|
objectFile.getAbsolutePath(),
|
destination.getAbsolutePath());
|
throw new ApplicationException(
|
ReturnCode.FILE_SYSTEM_ACCESS_ERROR,
|
errMsg, null);
|
}
|
} else {
|
logger.info(LocalizableMessage.raw("Ignoring file '" +
|
objectFile.getAbsolutePath() + "' since '" +
|
destination.getAbsolutePath() + "' already exists"));
|
if ((application != null) && application.isVerbose()) {
|
application.notifyListeners(
|
INFO_INFO_IGNORING_FILE.get(
|
objectFile.getAbsolutePath(),
|
destination.getAbsolutePath()));
|
application.notifyListeners(application.getLineBreak());
|
}
|
}
|
}
|
}
|
|
}
|
|
/**
|
* A delete operation.
|
*/
|
private class DeleteOperation extends FileOperation {
|
|
private DeletionPolicy deletionPolicy;
|
|
/**
|
* Creates a delete operation.
|
* @param objectFile to delete
|
* @param deletionPolicy describing how files will be deleted
|
* is to take place after this program exists. This is useful
|
* for cleaning up files that are currently in use.
|
*/
|
public DeleteOperation(File objectFile, DeletionPolicy deletionPolicy) {
|
super(objectFile);
|
this.deletionPolicy = deletionPolicy;
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public FileOperation copyForChild(File child) {
|
return new DeleteOperation(child, deletionPolicy);
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public void apply() throws ApplicationException {
|
File file = getObjectFile();
|
boolean isFile = file.isFile();
|
|
if ((application != null) && application.isVerbose()) {
|
if (isFile) {
|
application.notifyListeners(application.getFormattedWithPoints(
|
INFO_PROGRESS_DELETING_FILE.get(file.getAbsolutePath())));
|
} else {
|
application.notifyListeners(application.getFormattedWithPoints(
|
INFO_PROGRESS_DELETING_DIRECTORY.get(
|
file.getAbsolutePath())));
|
}
|
}
|
logger.info(LocalizableMessage.raw("deleting " +
|
(isFile ? " file " : " directory ") +
|
file.getAbsolutePath()));
|
|
boolean delete = false;
|
/*
|
* Sometimes the server keeps some locks on the files.
|
* TODO: remove this code once stop-ds returns properly when server
|
* is stopped.
|
*/
|
int nTries = 5;
|
for (int i = 0; i < nTries && !delete; i++) {
|
if (DeletionPolicy.DELETE_ON_EXIT.equals(deletionPolicy)) {
|
file.deleteOnExit();
|
delete = true;
|
} else {
|
delete = file.delete();
|
if (!delete && DeletionPolicy.DELETE_ON_EXIT_IF_UNSUCCESSFUL.
|
equals(deletionPolicy)) {
|
file.deleteOnExit();
|
delete = true;
|
}
|
}
|
if (!delete) {
|
try {
|
Thread.sleep(1000);
|
}
|
catch (Exception ex) {
|
// do nothing;
|
}
|
}
|
}
|
|
if (!delete) {
|
LocalizableMessage errMsg;
|
if (isFile) {
|
errMsg = INFO_ERROR_DELETING_FILE.get(file.getAbsolutePath());
|
} else {
|
errMsg = INFO_ERROR_DELETING_DIRECTORY.get(file.getAbsolutePath());
|
}
|
throw new ApplicationException(
|
ReturnCode.FILE_SYSTEM_ACCESS_ERROR,
|
errMsg, null);
|
}
|
|
if ((application != null) && application.isVerbose()) {
|
application.notifyListeners(
|
application.getFormattedDoneWithLineBreak());
|
}
|
}
|
}
|
|
/**
|
* A delete operation.
|
*/
|
private class MoveOperation extends FileOperation {
|
|
File destination = null;
|
|
/**
|
* Creates a delete operation.
|
* @param objectFile to delete
|
* @param newParent Filr where <code>objectFile</code> will be copied.
|
*/
|
public MoveOperation(File objectFile, File newParent) {
|
super(objectFile);
|
this.destination = new File(newParent, objectFile.getName());
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public FileOperation copyForChild(File child) {
|
return new MoveOperation(child, destination);
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public void apply() throws ApplicationException {
|
File objectFile = getObjectFile();
|
if (destination.exists()) {
|
deleteRecursively(destination);
|
}
|
if (!objectFile.renameTo(destination)) {
|
throw ApplicationException.createFileSystemException(
|
INFO_ERROR_FAILED_MOVING_FILE.get(Utils.getPath(objectFile),
|
Utils.getPath(destination)),
|
null);
|
}
|
}
|
}
|
|
}
|