001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.core.rolling.helper;
015
016import java.io.File;
017
018import ch.qos.logback.core.CoreConstants;
019import ch.qos.logback.core.rolling.RollingFileAppender;
020import ch.qos.logback.core.rolling.RolloverFailure;
021import ch.qos.logback.core.spi.ContextAwareBase;
022import ch.qos.logback.core.util.EnvUtil;
023import ch.qos.logback.core.util.FileUtil;
024
025/**
026 * Utility class to help solving problems encountered while renaming files.
027 *
028 * @author Ceki Gulcu
029 */
030public class RenameUtil extends ContextAwareBase {
031
032    static String RENAMING_ERROR_URL = CoreConstants.CODES_URL + "#renamingError";
033
034    /**
035     * A relatively robust file renaming method which in case of failure due to src
036     * and target being on different volumes, falls back onto renaming by copying.
037     *
038     * @param src
039     * @param target
040     * @throws RolloverFailure
041     */
042    public void rename(String src, String target) throws RolloverFailure {
043        if (src.equals(target)) {
044            addWarn("Source and target files are the same [" + src + "]. Skipping.");
045            return;
046        }
047        File srcFile = new File(src);
048
049        if (srcFile.exists()) {
050            File targetFile = new File(target);
051            createMissingTargetDirsIfNecessary(targetFile);
052
053            addInfo("Renaming file [" + srcFile + "] to [" + targetFile + "]");
054
055            boolean result = srcFile.renameTo(targetFile);
056
057            if (!result) {
058                addWarn("Failed to rename file [" + srcFile + "] as [" + targetFile + "].");
059                Boolean areOnDifferentVolumes = areOnDifferentVolumes(srcFile, targetFile);
060                if (Boolean.TRUE.equals(areOnDifferentVolumes)) {
061                    addWarn("Detected different file systems for source [" + src + "] and target [" + target
062                            + "]. Attempting rename by copying.");
063                    renameByCopying(src, target);
064                    return;
065                } else {
066                    addWarn("Please consider leaving the [file] option of " + RollingFileAppender.class.getSimpleName()
067                            + " empty.");
068                    addWarn("See also " + RENAMING_ERROR_URL);
069                }
070            }
071        } else {
072            throw new RolloverFailure("File [" + src + "] does not exist.");
073        }
074    }
075
076    /**
077     * Attempts to determine whether both files are on different volumes. Returns
078     * true if we could determine that the files are on different volumes. Returns
079     * false otherwise or if an error occurred while doing the check.
080     *
081     * @param srcFile
082     * @param targetFile
083     * @return true if on different volumes, false otherwise or if an error occurred
084     */
085    Boolean areOnDifferentVolumes(File srcFile, File targetFile) throws RolloverFailure {
086        if (!EnvUtil.isJDK7OrHigher())
087            return false;
088
089        // target file is not certain to exist but its parent has to exist given the
090        // call hierarchy of this method
091        File parentOfTarget = targetFile.getAbsoluteFile().getParentFile();
092
093        if (parentOfTarget == null) {
094            addWarn("Parent of target file [" + targetFile + "] is null");
095            return null;
096        }
097        if (!parentOfTarget.exists()) {
098            addWarn("Parent of target file [" + targetFile + "] does not exist");
099            return null;
100        }
101
102        try {
103            boolean onSameFileStore = FileStoreUtil.areOnSameFileStore(srcFile, parentOfTarget);
104            return !onSameFileStore;
105        } catch (RolloverFailure rf) {
106            addWarn("Error while checking file store equality", rf);
107            return null;
108        }
109    }
110
111    public void renameByCopying(String src, String target) throws RolloverFailure {
112
113        FileUtil fileUtil = new FileUtil(getContext());
114        fileUtil.copy(src, target);
115
116        File srcFile = new File(src);
117        if (!srcFile.delete()) {
118            addWarn("Could not delete " + src);
119        }
120
121    }
122
123    void createMissingTargetDirsIfNecessary(File toFile) throws RolloverFailure {
124        boolean result = FileUtil.createMissingParentDirectories(toFile);
125        if (!result) {
126            throw new RolloverFailure("Failed to create parent directories for [" + toFile.getAbsolutePath() + "]");
127        }
128    }
129
130    @Override
131    public String toString() {
132        return "c.q.l.co.rolling.helper.RenameUtil";
133    }
134}