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