View Javadoc
1   package org.codehaus.plexus.components.io.resources;
2   
3   /*
4    * Copyright 2007 The Codehaus Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import javax.annotation.Nonnull;
20  
21  import java.io.ByteArrayInputStream;
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.net.URL;
27  import java.nio.file.Files;
28  import java.nio.file.LinkOption;
29  import java.nio.file.attribute.BasicFileAttributes;
30  import java.nio.file.attribute.FileTime;
31  import java.util.Arrays;
32  
33  import org.apache.commons.io.IOUtils;
34  import org.apache.commons.io.output.DeferredFileOutputStream;
35  import org.codehaus.plexus.components.io.attributes.AttributeUtils;
36  import org.codehaus.plexus.components.io.attributes.FileAttributes;
37  import org.codehaus.plexus.components.io.attributes.PlexusIoResourceAttributes;
38  import org.codehaus.plexus.components.io.functions.ContentSupplier;
39  import org.codehaus.plexus.components.io.functions.FileSupplier;
40  import org.codehaus.plexus.components.io.functions.HardLinkIdentitySupplier;
41  import org.codehaus.plexus.components.io.functions.InputStreamTransformer;
42  import org.codehaus.plexus.components.io.functions.ResourceAttributeSupplier;
43  
44  import static java.util.Objects.requireNonNull;
45  
46  /**
47   * Implementation of {@link PlexusIoResource} for files.
48   */
49  public class PlexusIoFileResource extends AbstractPlexusIoResource
50          implements ResourceAttributeSupplier, FileSupplier, HardLinkIdentitySupplier {
51  
52      @Nonnull
53      private final File file;
54  
55      @Nonnull
56      private final PlexusIoResourceAttributes attributes;
57  
58      @Nonnull
59      private final FileAttributes fileAttributes;
60  
61      private final ContentSupplier contentSupplier;
62  
63      private final boolean originalContent;
64  
65      private final DeferredFileOutputStream dfos;
66  
67      protected PlexusIoFileResource(@Nonnull File file, @Nonnull String name, @Nonnull PlexusIoResourceAttributes attrs)
68              throws IOException {
69          this(file, name, attrs, null, null);
70      }
71  
72      PlexusIoFileResource(
73              @Nonnull final File file,
74              @Nonnull String name,
75              @Nonnull PlexusIoResourceAttributes attrs,
76              final ContentSupplier contentSupplier,
77              final InputStreamTransformer streamTransformer)
78              throws IOException {
79          this(file, name, attrs, new FileAttributes(file, true), contentSupplier, streamTransformer);
80      }
81  
82      PlexusIoFileResource(
83              @Nonnull final File file,
84              @Nonnull String name,
85              @Nonnull PlexusIoResourceAttributes attrs,
86              @Nonnull FileAttributes fileAttributes,
87              final ContentSupplier contentSupplier,
88              final InputStreamTransformer streamTransformer)
89              throws IOException {
90          super(
91                  name,
92                  fileAttributes.getLastModifiedTime().toMillis(),
93                  fileAttributes.getSize(),
94                  fileAttributes.isRegularFile(),
95                  fileAttributes.isDirectory(),
96                  fileAttributes.isRegularFile()
97                          || fileAttributes.isDirectory()
98                          || fileAttributes.isSymbolicLink()
99                          || fileAttributes.isOther());
100         this.file = file;
101         this.attributes = requireNonNull(attrs, "attributes is null for file " + file.getName());
102         this.fileAttributes = requireNonNull(fileAttributes, "fileAttributes is null for file " + file.getName());
103         this.contentSupplier = contentSupplier != null ? contentSupplier : getRootContentSupplier(file);
104 
105         boolean hasTransformer = streamTransformer != null && streamTransformer != identityTransformer;
106         InputStreamTransformer transToUse = streamTransformer != null ? streamTransformer : identityTransformer;
107 
108         originalContent = contentSupplier == null && !hasTransformer;
109 
110         dfos = hasTransformer && file.isFile() ? asDeferredStream(this.contentSupplier, transToUse, this) : null;
111     }
112 
113     private static DeferredFileOutputStream asDeferredStream(
114             @Nonnull ContentSupplier supplier, @Nonnull InputStreamTransformer transToUse, PlexusIoResource resource)
115             throws IOException {
116         DeferredFileOutputStream dfos = DeferredFileOutputStream.builder()
117                 .setThreshold(5000000)
118                 .setPrefix("p-archiver")
119                 .get();
120         try (InputStream inputStream = supplier.getContents();
121                 InputStream transformed = transToUse.transform(resource, inputStream);
122                 DeferredFileOutputStream closeable = dfos) {
123             IOUtils.copy(transformed, dfos);
124         }
125         return dfos;
126     }
127 
128     private static ContentSupplier getRootContentSupplier(final File file) {
129         return () -> Files.newInputStream(file.toPath());
130     }
131 
132     public static String getName(File file) {
133         return file.getPath().replace('\\', '/');
134     }
135 
136     /**
137      * Returns the resource file.
138      */
139     @Nonnull
140     public File getFile() {
141         return file;
142     }
143 
144     @Nonnull
145     public InputStream getContents() throws IOException {
146         if (dfos == null) {
147             return contentSupplier.getContents();
148         }
149         if (dfos.isInMemory()) {
150             return new ByteArrayInputStream(dfos.getData());
151         } else {
152             return new FileInputStream(dfos.getFile()) {
153                 @SuppressWarnings("ResultOfMethodCallIgnored")
154                 @Override
155                 public void close() throws IOException {
156                     super.close();
157                     dfos.getFile().delete();
158                 }
159             };
160         }
161     }
162 
163     @Nonnull
164     public URL getURL() throws IOException {
165         return getFile().toURI().toURL();
166     }
167 
168     public long getSize() {
169         if (dfos == null) {
170             return fileAttributes.getSize();
171         }
172         if (dfos.isInMemory()) {
173             return dfos.getByteCount();
174         } else {
175             return dfos.getFile().length();
176         }
177     }
178 
179     public boolean isDirectory() {
180         return fileAttributes.isDirectory();
181     }
182 
183     public boolean isExisting() {
184         if (attributes instanceof FileAttributes) {
185             return true;
186         }
187         return getFile().exists();
188     }
189 
190     public boolean isFile() {
191         return fileAttributes.isRegularFile();
192     }
193 
194     @Nonnull
195     public PlexusIoResourceAttributes getAttributes() {
196         return attributes;
197     }
198 
199     @Nonnull
200     public FileAttributes getFileAttributes() {
201         return fileAttributes;
202     }
203 
204     public long getLastModified() {
205         FileTime lastModified = fileAttributes.getLastModifiedTime();
206         if (lastModified != null) {
207             return lastModified.toMillis();
208         }
209         return AttributeUtils.getLastModified(getFile());
210     }
211 
212     @Override
213     public boolean isSymbolicLink() {
214         return getAttributes().isSymbolicLink();
215     }
216 
217     /**
218      * Identifies an untransformed regular file without following symbolic links.
219      * Subclasses that change content must explicitly supply their own guarantee.
220      *
221      * @return a filesystem-scoped file identity, or null for unknown content
222      * @throws IOException if the file attributes cannot be read
223      */
224     @Override
225     public Object getHardLinkIdentity() throws IOException {
226         // A backing file alone does not describe custom suppliers or transformed bytes.
227         if (!originalContent || getClass() != PlexusIoFileResource.class) {
228             return null;
229         }
230         BasicFileAttributes attrs =
231                 Files.readAttributes(file.toPath(), BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
232         if (!attrs.isRegularFile() || attrs.fileKey() == null) {
233             return null;
234         }
235         // Include the observed content version so a changed source cannot reuse an old payload.
236         return Arrays.asList(file.toPath().getFileSystem(), attrs.fileKey(), attrs.size(), attrs.lastModifiedTime());
237     }
238 
239     protected DeferredFileOutputStream getDfos() {
240         return dfos;
241     }
242 
243     private static final InputStreamTransformer identityTransformer =
244             AbstractPlexusIoResourceCollection.identityTransformer;
245 }