001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2006-2008 Sun Microsystems, Inc. 015 * Portions Copyright 2012-2016 ForgeRock AS. 016 */ 017package org.opends.quicksetup.util; 018 019import java.io.*; 020 021import org.forgerock.i18n.LocalizableMessage; 022import org.forgerock.i18n.slf4j.LocalizedLogger; 023import org.opends.quicksetup.*; 024 025import static org.opends.messages.QuickSetupMessages.*; 026import static com.forgerock.opendj.util.OperatingSystem.isUnix; 027 028/** 029 * Utility class for use by applications containing methods for managing 030 * file system files. This class handles application notifications for 031 * interesting events. 032 */ 033public class FileManager { 034 /** Describes the approach taken to deleting a file or directory. */ 035 public enum DeletionPolicy { 036 /** Delete the file or directory immediately. */ 037 DELETE_IMMEDIATELY, 038 /** Mark the file or directory for deletion after the JVM has exited. */ 039 DELETE_ON_EXIT, 040 /** 041 * First try to delete the file immediately. If the deletion was 042 * unsuccessful mark the file for deletion when the JVM has existed. 043 */ 044 DELETE_ON_EXIT_IF_UNSUCCESSFUL 045 } 046 047 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 048 049 private Application application; 050 051 /** Creates a new file manager. */ 052 public FileManager() { 053 // do nothing; 054 } 055 056 /** 057 * Creates a new file manager. 058 * @param app Application managing files to which progress notifications 059 * will be sent 060 */ 061 public FileManager(Application app) { 062 this.application = app; 063 } 064 065 /** 066 * Recursively copies any files or directories appearing in 067 * <code>source</code> or a subdirectory of <code>source</code> 068 * to the corresponding directory under <code>target</code>. Files 069 * in under <code>source</code> are not copied to <code>target</code> 070 * if a file by the same name already exists in <code>target</code>. 071 * 072 * @param source source directory 073 * @param target target directory 074 * @throws ApplicationException if there is a problem copying files 075 */ 076 public void synchronize(File source, File target) 077 throws ApplicationException 078 { 079 if (source != null && target != null) { 080 String[] sourceFileNames = source.list(); 081 if (sourceFileNames != null) { 082 for (String sourceFileName : sourceFileNames) { 083 File sourceFile = new File(source, sourceFileName); 084 copyRecursively(sourceFile, target, null, false); 085 } 086 } 087 } 088 } 089 090 /** 091 * Renames the source file to the target file. If the target file exists 092 * it is first deleted. The rename and delete operation return values 093 * are checked for success and if unsuccessful, this method throws an exception. 094 * 095 * @param fileToRename The file to rename. 096 * @param target The file to which <code>fileToRename</code> will be moved. 097 * @throws ApplicationException If a problem occurs while attempting to rename 098 * the file. On the Windows platform, this typically 099 * indicates that the file is in use by this or another 100 * application. 101 */ 102 public void rename(File fileToRename, File target) 103 throws ApplicationException { 104 if (fileToRename != null && target != null) { 105 synchronized (target) { 106 if (target.exists() && !target.delete()) 107 { 108 throw new ApplicationException( 109 ReturnCode.FILE_SYSTEM_ACCESS_ERROR, 110 INFO_ERROR_DELETING_FILE.get(Utils.getPath(target)), null); 111 } 112 } 113 if (!fileToRename.renameTo(target)) { 114 throw new ApplicationException( 115 ReturnCode.FILE_SYSTEM_ACCESS_ERROR, 116 INFO_ERROR_RENAMING_FILE.get(Utils.getPath(fileToRename), 117 Utils.getPath(target)), null); 118 } 119 } 120 } 121 122 /** 123 * Move a file. 124 * @param object File to move 125 * @param newParent File representing new parent directory 126 * @throws ApplicationException if something goes wrong 127 */ 128 public void move(File object, File newParent) throws ApplicationException 129 { 130 move(object, newParent, null); 131 } 132 133 /** 134 * Move a file. 135 * @param object File to move 136 * @param newParent File representing new parent directory 137 * @param filter that will be asked whether the operation should be performed 138 * @throws ApplicationException if something goes wrong 139 */ 140 public void move(File object, File newParent, FileFilter filter) 141 throws ApplicationException 142 { 143 // TODO: application notification 144 if (filter == null || filter.accept(object)) { 145 new MoveOperation(object, newParent).apply(); 146 } 147 } 148 149 /** 150 * Deletes a single file or directory. 151 * @param object File to delete 152 * @throws ApplicationException if something goes wrong 153 */ 154 public void delete(File object) 155 throws ApplicationException 156 { 157 delete(object, null); 158 } 159 160 /** 161 * Deletes a single file or directory. 162 * @param object File to delete 163 * @param filter that will be asked whether the operation should be performed 164 * @throws ApplicationException if something goes wrong 165 */ 166 public void delete(File object, FileFilter filter) 167 throws ApplicationException 168 { 169 if (filter == null || filter.accept(object)) { 170 new DeleteOperation(object, DeletionPolicy.DELETE_IMMEDIATELY).apply(); 171 } 172 } 173 174 /** 175 * Deletes the children of a directory. 176 * 177 * @param parentDir the directory whose children is deleted 178 * @throws ApplicationException if there is a problem deleting children 179 */ 180 public void deleteChildren(File parentDir) throws ApplicationException { 181 if (parentDir != null && parentDir.exists() && parentDir.isDirectory()) { 182 File[] children = parentDir.listFiles(); 183 if (children != null) { 184 for (File child : children) { 185 deleteRecursively(child); 186 } 187 } 188 } 189 } 190 191 /** 192 * Deletes everything below the specified file. 193 * 194 * @param file the path to be deleted. 195 * @throws org.opends.quicksetup.ApplicationException if something goes wrong. 196 */ 197 public void deleteRecursively(File file) throws ApplicationException { 198 deleteRecursively(file, null, 199 FileManager.DeletionPolicy.DELETE_IMMEDIATELY); 200 } 201 202 /** 203 * Deletes everything below the specified file. 204 * 205 * @param file the path to be deleted. 206 * @param filter the filter of the files to know if the file can be deleted 207 * directly or not. 208 * @param deletePolicy describes how deletions are to be made 209 * JVM exits rather than deleting the files immediately. 210 * @throws ApplicationException if something goes wrong. 211 */ 212 public void deleteRecursively(File file, FileFilter filter, 213 DeletionPolicy deletePolicy) 214 throws ApplicationException { 215 operateRecursively(new DeleteOperation(file, deletePolicy), filter); 216 } 217 218 /** 219 * Copies everything below the specified file. 220 * 221 * @param objectFile the file to be copied. 222 * @param destDir the directory to copy the file to 223 * @return File representing the destination 224 * @throws ApplicationException if something goes wrong. 225 */ 226 public File copy(File objectFile, File destDir) 227 throws ApplicationException 228 { 229 CopyOperation co = new CopyOperation(objectFile, destDir, false); 230 co.apply(); 231 return co.getDestination(); 232 } 233 234 /** 235 * Copies everything below the specified file. 236 * 237 * @param objectFile the file to be copied. 238 * @param destDir the directory to copy the file to 239 * @param overwrite overwrite destination files. 240 * @return File representing the destination 241 * @throws ApplicationException if something goes wrong. 242 */ 243 public File copy(File objectFile, File destDir, boolean overwrite) 244 throws ApplicationException 245 { 246 CopyOperation co = new CopyOperation(objectFile, destDir, overwrite); 247 co.apply(); 248 return co.getDestination(); 249 } 250 251 /** 252 * Copies everything below the specified file. 253 * 254 * @param objectFile the file to be copied. 255 * @param destDir the directory to copy the file to 256 * @throws ApplicationException if something goes wrong. 257 */ 258 public void copyRecursively(File objectFile, File destDir) 259 throws ApplicationException 260 { 261 copyRecursively(objectFile, destDir, null); 262 } 263 264 /** 265 * Copies everything below the specified file. 266 * 267 * @param objectFile the file to be copied. 268 * @param destDir the directory to copy the file to 269 * @param filter the filter of the files to know if the file can be copied 270 * directly or not. 271 * @throws ApplicationException if something goes wrong. 272 */ 273 public void copyRecursively(File objectFile, File destDir, FileFilter filter) 274 throws ApplicationException { 275 copyRecursively(objectFile, destDir, filter, false); 276 } 277 278 /** 279 * Copies everything below the specified file. 280 * 281 * @param objectFile the file to be copied. 282 * @param destDir the directory to copy the file to 283 * @param filter the filter of the files to know if the file can be copied 284 * directly or not. 285 * @param overwrite overwrite destination files. 286 * @throws ApplicationException if something goes wrong. 287 */ 288 public void copyRecursively(File objectFile, File destDir, 289 FileFilter filter, boolean overwrite) 290 throws ApplicationException { 291 operateRecursively(new CopyOperation(objectFile, destDir, overwrite), filter); 292 } 293 294 /** 295 * Determines whether two files differ in content. 296 * 297 * @param f1 file to compare 298 * @param f2 file to compare 299 * @return boolean where true indicates that two files differ 300 * @throws IOException if there is a problem reading the files' contents 301 */ 302 public boolean filesDiffer(File f1, File f2) throws IOException { 303 boolean differ = false; 304 try (FileReader fr1 = new FileReader(f1); 305 FileReader fr2 = new FileReader(f2)) 306 { 307 boolean done = false; 308 while (!differ && !done) { 309 int c1 = fr1.read(); 310 int c2 = fr2.read(); 311 differ = c1 != c2; 312 done = c1 == -1 || c2 == -1; 313 } 314 } 315 return differ; 316 } 317 318 private void operateRecursively(FileOperation op, FileFilter filter) 319 throws ApplicationException { 320 File file = op.getObjectFile(); 321 if (file.exists()) { 322 if (file.isFile()) { 323 if (filter != null) { 324 if (filter.accept(file)) { 325 op.apply(); 326 } 327 } else { 328 op.apply(); 329 } 330 } else { 331 File[] children = file.listFiles(); 332 if (children != null) { 333 for (File aChildren : children) { 334 FileOperation newOp = op.copyForChild(aChildren); 335 operateRecursively(newOp, filter); 336 } 337 } 338 if (filter != null) { 339 if (filter.accept(file)) { 340 op.apply(); 341 } 342 } else { 343 op.apply(); 344 } 345 } 346 } else { 347 // Just tell that the file/directory does not exist. 348 if (application != null) { 349 application.notifyListeners(application.getFormattedWarning( 350 INFO_FILE_DOES_NOT_EXIST.get(file))); 351 } 352 logger.info(LocalizableMessage.raw("file '" + file + "' does not exist")); 353 } 354 } 355 356 /** A file operation. */ 357 private abstract class FileOperation { 358 private File objectFile; 359 360 /** 361 * Creates a new file operation. 362 * @param objectFile to be operated on 363 */ 364 public FileOperation(File objectFile) { 365 this.objectFile = objectFile; 366 } 367 368 /** 369 * Gets the file to be operated on. 370 * @return File to be operated on 371 */ 372 protected File getObjectFile() { 373 return objectFile; 374 } 375 376 /** 377 * Make a copy of this class for the child file. 378 * @param child to act as the new file object 379 * @return FileOperation as the same type as this class 380 */ 381 public abstract FileOperation copyForChild(File child); 382 383 /** 384 * Execute this operation. 385 * @throws ApplicationException if there is a problem. 386 */ 387 public abstract void apply() throws ApplicationException; 388 } 389 390 /** A copy operation. */ 391 private class CopyOperation extends FileOperation { 392 private File destination; 393 394 private boolean overwrite; 395 396 /** 397 * Create a new copy operation. 398 * @param objectFile to copy 399 * @param destDir to copy to 400 * @param overwrite if true copy should overwrite any existing file 401 */ 402 public CopyOperation(File objectFile, File destDir, boolean overwrite) { 403 super(objectFile); 404 this.destination = new File(destDir, objectFile.getName()); 405 this.overwrite = overwrite; 406 } 407 408 @Override 409 public FileOperation copyForChild(File child) { 410 return new CopyOperation(child, destination, overwrite); 411 } 412 413 /** 414 * Returns the destination file that is the result of copying 415 * <code>objectFile</code> to <code>destDir</code>. 416 * @return The destination file. 417 */ 418 public File getDestination() { 419 return this.destination; 420 } 421 422 @Override 423 public void apply() throws ApplicationException { 424 File objectFile = getObjectFile(); 425 if (objectFile.isDirectory()) { 426 if (!destination.exists()) { 427 destination.mkdirs(); 428 } 429 } else { 430 // If overwriting and the destination exists then kill it 431 if (destination.exists() && overwrite) { 432 deleteRecursively(destination); 433 } 434 435 if (!destination.exists()) { 436 if (Utils.ensureParentsExist(destination)) { 437 if (application != null && application.isVerbose()) { 438 application.notifyListeners(application.getFormattedWithPoints( 439 INFO_PROGRESS_COPYING_FILE.get( 440 objectFile.getAbsolutePath(), 441 destination.getAbsolutePath()))); 442 } 443 logger.info(LocalizableMessage.raw("copying file '" + 444 objectFile.getAbsolutePath() + "' to '" + 445 destination.getAbsolutePath() + "'")); 446 try (FileInputStream fis = new FileInputStream(objectFile); 447 FileOutputStream fos = new FileOutputStream(destination)) 448 { 449 byte[] buf = new byte[1024]; 450 int i; 451 while ((i = fis.read(buf)) != -1) { 452 fos.write(buf, 0, i); 453 } 454 if (destination.exists() && isUnix()) { 455 // TODO: set the file's permissions. This is made easier in 456 // Java 1.6 but until then use the TestUtilities methods 457 String permissions = Utils.getFileSystemPermissions(objectFile); 458 Utils.setPermissionsUnix(Utils.getPath(destination), permissions); 459 } 460 461 if (application != null && application.isVerbose()) { 462 application.notifyListeners( 463 application.getFormattedDoneWithLineBreak()); 464 } 465 } catch (Exception e) { 466 LocalizableMessage errMsg = INFO_ERROR_COPYING_FILE.get( 467 objectFile.getAbsolutePath(), 468 destination.getAbsolutePath()); 469 throw new ApplicationException( 470 ReturnCode.FILE_SYSTEM_ACCESS_ERROR, 471 errMsg, null); 472 } 473 } else { 474 LocalizableMessage errMsg = INFO_ERROR_COPYING_FILE.get( 475 objectFile.getAbsolutePath(), 476 destination.getAbsolutePath()); 477 throw new ApplicationException( 478 ReturnCode.FILE_SYSTEM_ACCESS_ERROR, 479 errMsg, null); 480 } 481 } else { 482 logger.info(LocalizableMessage.raw("Ignoring file '" + 483 objectFile.getAbsolutePath() + "' since '" + 484 destination.getAbsolutePath() + "' already exists")); 485 if (application != null && application.isVerbose()) { 486 application.notifyListeners( 487 INFO_INFO_IGNORING_FILE.get( 488 objectFile.getAbsolutePath(), 489 destination.getAbsolutePath())); 490 application.notifyListeners(application.getLineBreak()); 491 } 492 } 493 } 494 } 495 } 496 497 /** A delete operation. */ 498 private class DeleteOperation extends FileOperation { 499 private DeletionPolicy deletionPolicy; 500 501 /** 502 * Creates a delete operation. 503 * @param objectFile to delete 504 * @param deletionPolicy describing how files will be deleted 505 * is to take place after this program exists. This is useful 506 * for cleaning up files that are currently in use. 507 */ 508 public DeleteOperation(File objectFile, DeletionPolicy deletionPolicy) { 509 super(objectFile); 510 this.deletionPolicy = deletionPolicy; 511 } 512 513 @Override 514 public FileOperation copyForChild(File child) { 515 return new DeleteOperation(child, deletionPolicy); 516 } 517 518 @Override 519 public void apply() throws ApplicationException { 520 File file = getObjectFile(); 521 boolean isFile = file.isFile(); 522 523 if (application != null && application.isVerbose()) { 524 if (isFile) { 525 application.notifyListeners(application.getFormattedWithPoints( 526 INFO_PROGRESS_DELETING_FILE.get(file.getAbsolutePath()))); 527 } else { 528 application.notifyListeners(application.getFormattedWithPoints( 529 INFO_PROGRESS_DELETING_DIRECTORY.get( 530 file.getAbsolutePath()))); 531 } 532 } 533 logger.info(LocalizableMessage.raw("deleting " + 534 (isFile ? " file " : " directory ") + 535 file.getAbsolutePath())); 536 537 boolean delete = false; 538 /* 539 * Sometimes the server keeps some locks on the files. 540 * TODO: remove this code once stop-ds returns properly when server 541 * is stopped. 542 */ 543 int nTries = 5; 544 for (int i = 0; i < nTries && !delete; i++) { 545 if (DeletionPolicy.DELETE_ON_EXIT.equals(deletionPolicy)) { 546 file.deleteOnExit(); 547 delete = true; 548 } else { 549 delete = file.delete(); 550 if (!delete && DeletionPolicy.DELETE_ON_EXIT_IF_UNSUCCESSFUL. 551 equals(deletionPolicy)) { 552 file.deleteOnExit(); 553 delete = true; 554 } 555 } 556 if (!delete) { 557 try { 558 Thread.sleep(1000); 559 } 560 catch (Exception ex) { 561 // do nothing; 562 } 563 } 564 } 565 566 if (!delete) { 567 LocalizableMessage errMsg; 568 if (isFile) { 569 errMsg = INFO_ERROR_DELETING_FILE.get(file.getAbsolutePath()); 570 } else { 571 errMsg = INFO_ERROR_DELETING_DIRECTORY.get(file.getAbsolutePath()); 572 } 573 throw new ApplicationException( 574 ReturnCode.FILE_SYSTEM_ACCESS_ERROR, 575 errMsg, null); 576 } 577 578 if (application != null && application.isVerbose()) { 579 application.notifyListeners( 580 application.getFormattedDoneWithLineBreak()); 581 } 582 } 583 } 584 585 /** A delete operation. */ 586 private class MoveOperation extends FileOperation { 587 File destination; 588 589 /** 590 * Creates a delete operation. 591 * @param objectFile to delete 592 * @param newParent File where <code>objectFile</code> will be copied. 593 */ 594 public MoveOperation(File objectFile, File newParent) { 595 super(objectFile); 596 this.destination = new File(newParent, objectFile.getName()); 597 } 598 599 @Override 600 public FileOperation copyForChild(File child) { 601 return new MoveOperation(child, destination); 602 } 603 604 @Override 605 public void apply() throws ApplicationException { 606 File objectFile = getObjectFile(); 607 if (destination.exists()) { 608 deleteRecursively(destination); 609 } 610 if (!objectFile.renameTo(destination)) { 611 throw ApplicationException.createFileSystemException( 612 INFO_ERROR_FAILED_MOVING_FILE.get(Utils.getPath(objectFile), 613 Utils.getPath(destination)), 614 null); 615 } 616 } 617 } 618}