View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.appender.rolling.action;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.PrintWriter;
22  import java.nio.file.Files;
23  import java.nio.file.Paths;
24  import java.nio.file.StandardCopyOption;
25  
26  /**
27   * File rename action.
28   */
29  public class FileRenameAction extends AbstractAction {
30  
31      /**
32       * Source.
33       */
34      private final File source;
35  
36      /**
37       * Destination.
38       */
39      private final File destination;
40  
41      /**
42       * If true, rename empty files, otherwise delete empty files.
43       */
44      private final boolean renameEmptyFiles;
45  
46      /**
47       * Creates an FileRenameAction.
48       *
49       * @param src current file name.
50       * @param dst new file name.
51       * @param renameEmptyFiles if true, rename file even if empty, otherwise delete empty files.
52       */
53      public FileRenameAction(final File src, final File dst, final boolean renameEmptyFiles) {
54          source = src;
55          destination = dst;
56          this.renameEmptyFiles = renameEmptyFiles;
57      }
58  
59      /**
60       * Rename file.
61       *
62       * @return true if successfully renamed.
63       */
64      @Override
65      public boolean execute() {
66          return execute(source, destination, renameEmptyFiles);
67      }
68  
69      /**
70       * Gets the destination.
71       *
72       * @return the destination.
73       */
74      public File getDestination() {
75          return this.destination;
76      }
77  
78      /**
79       * Gets the source.
80       *
81       * @return the source.
82       */
83      public File getSource() {
84          return this.source;
85      }
86  
87      /**
88       * Whether to rename empty files. If true, rename empty files, otherwise delete empty files.
89       *
90       * @return Whether to rename empty files.
91       */
92      public boolean isRenameEmptyFiles() {
93          return renameEmptyFiles;
94      }
95  
96      /**
97       * Rename file.
98       *
99       * @param source current file name.
100      * @param destination new file name.
101      * @param renameEmptyFiles if true, rename file even if empty, otherwise delete empty files.
102      * @return true if successfully renamed.
103      */
104     public static boolean execute(final File source, final File destination, final boolean renameEmptyFiles) {
105         if (renameEmptyFiles || (source.length() > 0)) {
106             final File parent = destination.getParentFile();
107             if ((parent != null) && !parent.exists()) {
108                 // LOG4J2-679: ignore mkdirs() result: in multithreaded scenarios,
109                 // if one thread succeeds the other thread returns false
110                 // even though directories have been created. Check if dir exists instead.
111                 parent.mkdirs();
112                 if (!parent.exists()) {
113                     LOGGER.error("Unable to create directory {}", parent.getAbsolutePath());
114                     return false;
115                 }
116             }
117             try {
118                 try {
119                     Files.move(Paths.get(source.getAbsolutePath()), Paths.get(destination.getAbsolutePath()),
120                             StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
121                     LOGGER.trace("Renamed file {} to {} with Files.move", source.getAbsolutePath(),
122                             destination.getAbsolutePath());
123                     return true;
124                 } catch (final IOException exMove) {
125                     LOGGER.error("Unable to move file {} to {}: {} {}", source.getAbsolutePath(),
126                             destination.getAbsolutePath(), exMove.getClass().getName(), exMove.getMessage());
127                     boolean result = source.renameTo(destination);
128                     if (!result) {
129                         try {
130                             Files.copy(Paths.get(source.getAbsolutePath()), Paths.get(destination.getAbsolutePath()),
131                                     StandardCopyOption.REPLACE_EXISTING);
132                             try {
133                                 Files.delete(Paths.get(source.getAbsolutePath()));
134                                 result = true;
135                                 LOGGER.trace("Renamed file {} to {} using copy and delete",
136                                         source.getAbsolutePath(), destination.getAbsolutePath());
137                             } catch (final IOException exDelete) {
138                                 LOGGER.error("Unable to delete file {}: {} {}", source.getAbsolutePath(),
139                                         exDelete.getClass().getName(), exDelete.getMessage());
140                                 try {
141                                     new PrintWriter(source.getAbsolutePath()).close();
142                                     LOGGER.trace("Renamed file {} to {} with copy and truncation",
143                                             source.getAbsolutePath(), destination.getAbsolutePath());
144                                 } catch (final IOException exOwerwrite) {
145                                     LOGGER.error("Unable to overwrite file {}: {} {}",
146                                             source.getAbsolutePath(), exOwerwrite.getClass().getName(),
147                                             exOwerwrite.getMessage());
148                                 }
149                             }
150                         } catch (final IOException exCopy) {
151                             LOGGER.error("Unable to copy file {} to {}: {} {}", source.getAbsolutePath(),
152                                     destination.getAbsolutePath(), exCopy.getClass().getName(), exCopy.getMessage());
153                         }
154                     } else {
155                         LOGGER.trace("Renamed file {} to {} with source.renameTo",
156                                 source.getAbsolutePath(), destination.getAbsolutePath());
157                     }
158                     return result;
159                 }
160             } catch (final RuntimeException ex) {
161                 LOGGER.error("Unable to rename file {} to {}: {} {}", source.getAbsolutePath(),
162                         destination.getAbsolutePath(), ex.getClass().getName(), ex.getMessage());
163             }
164         } else {
165             try {
166                 source.delete();
167             } catch (final Exception exDelete) {
168                 LOGGER.error("Unable to delete empty file {}: {} {}", source.getAbsolutePath(),
169                         exDelete.getClass().getName(), exDelete.getMessage());
170             }
171         }
172 
173         return false;
174     }
175 
176     @Override
177     public String toString() {
178         return FileRenameAction.class.getSimpleName() + '[' + source + " to " + destination
179                 + ", renameEmptyFiles=" + renameEmptyFiles + ']';
180     }
181 
182 }