/*
* The contents of this file are subject to the terms of the Common Development and
* Distribution License (the License). You may not use this file except in compliance with the
* License.
*
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions Copyright [year] [name of copyright owner]".
*
* Copyright 2006-2008 Sun Microsystems, Inc.
* Portions Copyright 2012-2016 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 static org.opends.messages.QuickSetupMessages.*;
import static com.forgerock.opendj.util.OperatingSystem.isUnix;
/**
* 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 deletion when the JVM has existed.
*/
DELETE_ON_EXIT_IF_UNSUCCESSFUL
}
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
private Application application;
/** 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
* source or a subdirectory of source
* to the corresponding directory under target. Files
* in under source are not copied to target
* if a file by the same name already exists in target.
*
* @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 fileToRename 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() && !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 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 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 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' contents
*/
public boolean filesDiffer(File f1, File f2) throws IOException {
boolean differ = false;
try (FileReader fr1 = new FileReader(f1);
FileReader fr2 = new FileReader(f2))
{
boolean done = false;
while (!differ && !done) {
int c1 = fr1.read();
int c2 = fr2.read();
differ = c1 != c2;
done = c1 == -1 || c2 == -1;
}
}
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;
/**
* 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
*/
public abstract FileOperation copyForChild(File child);
/**
* Execute this operation.
* @throws ApplicationException if there is a problem.
*/
public abstract 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;
}
@Override
public FileOperation copyForChild(File child) {
return new CopyOperation(child, destination, overwrite);
}
/**
* Returns the destination file that is the result of copying
* objectFile to destDir.
* @return The destination file.
*/
public File getDestination() {
return this.destination;
}
@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.ensureParentsExist(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() + "'"));
try (FileInputStream fis = new FileInputStream(objectFile);
FileOutputStream 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() && isUnix()) {
// TODO: set the file's permissions. This is made easier in
// Java 1.6 but until then use the TestUtilities methods
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);
}
} 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;
}
@Override
public FileOperation copyForChild(File child) {
return new DeleteOperation(child, deletionPolicy);
}
@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;
/**
* Creates a delete operation.
* @param objectFile to delete
* @param newParent File where objectFile will be copied.
*/
public MoveOperation(File objectFile, File newParent) {
super(objectFile);
this.destination = new File(newParent, objectFile.getName());
}
@Override
public FileOperation copyForChild(File child) {
return new MoveOperation(child, destination);
}
@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);
}
}
}
}