View Javadoc
1   /*
2    * Copyright 2001-2005 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.codehaus.plexus.archiver.dir;
17  
18  import javax.inject.Named;
19  
20  import java.io.File;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.nio.file.Files;
24  import java.nio.file.attribute.FileTime;
25  import java.util.ArrayList;
26  import java.util.List;
27  
28  import org.codehaus.plexus.archiver.AbstractArchiver;
29  import org.codehaus.plexus.archiver.ArchiveEntry;
30  import org.codehaus.plexus.archiver.ArchiverException;
31  import org.codehaus.plexus.archiver.ResourceIterator;
32  import org.codehaus.plexus.archiver.exceptions.EmptyArchiveException;
33  import org.codehaus.plexus.archiver.util.ArchiveEntryUtils;
34  import org.codehaus.plexus.archiver.util.ResourceUtils;
35  import org.codehaus.plexus.components.io.attributes.SymlinkUtils;
36  import org.codehaus.plexus.components.io.functions.SymlinkDestinationSupplier;
37  import org.codehaus.plexus.components.io.resources.PlexusIoResource;
38  
39  /**
40   * A plexus archiver implementation that stores the files to archive in a directory.
41   */
42  @Named("dir")
43  public class DirectoryArchiver extends AbstractArchiver {
44  
45      private final List<Runnable> directoryChmods = new ArrayList<>();
46      private long filesCopied;
47  
48      /**
49       * Default constructor.
50       */
51      public DirectoryArchiver() {
52          // preserver default behavior
53          setForced(false);
54      }
55  
56      public void resetArchiver() throws IOException {
57          cleanUp();
58      }
59  
60      @Override
61      public void execute() throws ArchiverException, IOException {
62          // Most of this method was copied from org.codehaus.plexus.archiver.tar.TarArchiver
63          // and modified to store files in a directory, not a tar archive.
64          final ResourceIterator iter = getResources();
65          if (!iter.hasNext()) {
66              throw new EmptyArchiveException("archive cannot be empty");
67          }
68  
69          final File destDirectory = getDestFile();
70          if (destDirectory == null) {
71              throw new ArchiverException("You must set the destination directory.");
72          }
73          if (destDirectory.exists() && !destDirectory.isDirectory()) {
74              throw new ArchiverException(destDirectory + " is not a directory.");
75          }
76          if (destDirectory.exists() && !destDirectory.canWrite()) {
77              throw new ArchiverException(destDirectory + " is not writable.");
78          }
79  
80          getLogger().info("Copying files to {}", destDirectory.getAbsolutePath());
81  
82          try {
83              while (iter.hasNext()) {
84                  final ArchiveEntry f = iter.next();
85                  // Check if we don't add directory file in itself
86                  if (ResourceUtils.isSame(f.getResource(), destDirectory)) {
87                      throw new ArchiverException("The destination directory cannot include itself.");
88                  }
89                  String fileName = f.getName();
90                  final String destDir = destDirectory.getCanonicalPath();
91                  fileName = destDir + File.separator + fileName;
92                  PlexusIoResource resource = f.getResource();
93                  if (resource instanceof SymlinkDestinationSupplier) {
94                      String dest = ((SymlinkDestinationSupplier) resource).getSymlinkDestination();
95                      File target = new File(dest);
96                      File symlink = new File(fileName);
97                      makeParentDirectories(symlink);
98                      SymlinkUtils.createSymbolicLink(symlink, target);
99                  } else {
100                     copyFile(f, fileName);
101                 }
102             }
103 
104             directoryChmods.forEach(Runnable::run);
105             directoryChmods.clear();
106 
107             if (filesCopied > 0) {
108                 getLogger()
109                         .info(
110                                 "{} file{} copied to {}",
111                                 filesCopied,
112                                 filesCopied > 0 ? "s" : "",
113                                 destDirectory.getAbsolutePath());
114             } else {
115                 getLogger().info("All files are uptodate in {}", destDirectory.getAbsolutePath());
116             }
117 
118             filesCopied = 0;
119 
120         } catch (final IOException ioe) {
121             final String message = "Problem copying files : " + ioe.getMessage();
122             throw new ArchiverException(message, ioe);
123         }
124     }
125 
126     /**
127      * Copies the specified file to the specified path, creating any ancestor directory structure as necessary.
128      *
129      * @param entry The file to copy (IOException will be thrown if this does not exist)
130      * @param vPath The fully qualified path to copy the file to.
131      *
132      * @throws ArchiverException If there is a problem creating the directory structure
133      * @throws IOException If there is a problem copying the file
134      */
135     protected void copyFile(final ArchiveEntry entry, final String vPath) throws ArchiverException, IOException {
136         // don't add "" to the archive
137         if (vPath.length() <= 0) {
138             return;
139         }
140 
141         final PlexusIoResource in = entry.getResource();
142         final File outFile = new File(vPath);
143 
144         final long inLastModified = in.getLastModified();
145 
146         if (!isForced()) {
147             final long outLastModified = outFile.lastModified();
148             if (ResourceUtils.isUptodate(inLastModified, outLastModified)) {
149                 return;
150             }
151         }
152 
153         if (!in.isDirectory()) {
154             makeParentDirectories(outFile);
155             try (InputStream input = entry.getInputStream()) {
156                 ResourceUtils.copyFile(input, outFile);
157             }
158             filesCopied++;
159             setFileModes(entry, outFile, inLastModified);
160         } else { // file is a directory
161             if (outFile.exists()) {
162                 if (!outFile.isDirectory()) {
163                     // should we just delete the file and replace it with a directory?
164                     // throw an exception, let the user delete the file manually.
165                     throw new ArchiverException("Expected directory and found file at copy destination of "
166                             + in.getName() + " to " + outFile);
167                 }
168             } else if (!outFile.mkdirs()) {
169                 // Failure, unable to create specified directory for some unknown reason.
170                 throw new ArchiverException("Unable to create directory or parent directory of " + outFile);
171             }
172 
173             directoryChmods.add(() -> {
174                 try {
175                     setFileModes(entry, outFile, inLastModified);
176                 } catch (IOException e) {
177                     throw new ArchiverException("Failed setting file attributes", e);
178                 }
179             });
180         }
181     }
182 
183     private static void makeParentDirectories(File file) {
184         if (!file.getParentFile().exists()) {
185             // create the parent directory...
186             if (!file.getParentFile().mkdirs()) {
187                 // Failure, unable to create specified directory for some unknown reason.
188                 throw new ArchiverException("Unable to create directory or parent directory of " + file);
189             }
190         }
191     }
192 
193     private void setFileModes(ArchiveEntry entry, File outFile, long inLastModified) throws IOException {
194         if (!isIgnorePermissions()) {
195             ArchiveEntryUtils.chmod(outFile, entry.getMode());
196         }
197 
198         if (getLastModifiedTime() == null) {
199             FileTime fromMillis = FileTime.fromMillis(
200                     inLastModified == PlexusIoResource.UNKNOWN_MODIFICATION_DATE
201                             ? System.currentTimeMillis()
202                             : inLastModified);
203             Files.setLastModifiedTime(outFile.toPath(), fromMillis);
204         } else {
205             Files.setLastModifiedTime(outFile.toPath(), getLastModifiedTime());
206         }
207     }
208 
209     @Override
210     protected void cleanUp() throws IOException {
211         super.cleanUp();
212         setIncludeEmptyDirs(false);
213         setIncludeEmptyDirs(true);
214     }
215 
216     @Override
217     protected void close() throws IOException {}
218 
219     @Override
220     protected String getArchiveType() {
221         return "directory";
222     }
223 
224     @Override
225     public boolean isSupportingForced() {
226         return true;
227     }
228 }