View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.datamodel.niofs.ifs2;
20  
21  import java.io.FileInputStream;
22  import java.io.IOException;
23  import java.nio.channels.FileChannel;
24  import java.nio.channels.SeekableByteChannel;
25  import java.nio.file.ClosedDirectoryStreamException;
26  import java.nio.file.DirectoryStream;
27  import java.nio.file.FileAlreadyExistsException;
28  import java.nio.file.Files;
29  import java.nio.file.LinkOption;
30  import java.nio.file.NoSuchFileException;
31  import java.nio.file.NotDirectoryException;
32  import java.nio.file.OpenOption;
33  import java.nio.file.Path;
34  import java.nio.file.StandardCopyOption;
35  import java.nio.file.StandardOpenOption;
36  import java.nio.file.attribute.BasicFileAttributeView;
37  import java.nio.file.attribute.BasicFileAttributes;
38  import java.nio.file.attribute.FileAttribute;
39  import java.nio.file.attribute.FileAttributeView;
40  import java.nio.file.attribute.FileTime;
41  import java.util.Iterator;
42  import java.util.Set;
43  
44  import org.apache.logging.log4j.LogManager;
45  import org.apache.logging.log4j.Logger;
46  import org.mycore.common.function.MCRThrowFunction;
47  import org.mycore.datamodel.ifs2.MCRDirectory;
48  import org.mycore.datamodel.ifs2.MCRFile;
49  import org.mycore.datamodel.ifs2.MCRFileCollection;
50  import org.mycore.datamodel.ifs2.MCRStoredNode;
51  import org.mycore.datamodel.niofs.MCRAbstractFileSystem;
52  import org.mycore.datamodel.niofs.MCRFileAttributes;
53  import org.mycore.datamodel.niofs.MCRMD5AttributeView;
54  import org.mycore.datamodel.niofs.MCRPath;
55  
56  /**
57   * A {@link SecureDirectoryStream} on internal file system. This implementation uses IFS directly. Do use this class but
58   * stick to the interface.
59   *
60   * @author Thomas Scheffler (yagee)
61   */
62  class MCRDirectoryStreamHelper {
63      static Logger LOGGER = LogManager.getLogger();
64  
65      static DirectoryStream<Path> getInstance(MCRDirectory dir, MCRPath path) throws IOException {
66          DirectoryStream.Filter<Path> filter = (dir instanceof MCRFileCollection) ? MCRFileCollectionFilter.FILTER
67              : AcceptAllFilter.FILTER;
68          LOGGER.debug("Dir {}, class {}, filter {}", path, dir.getClass(), filter.getClass());
69          DirectoryStream<Path> baseDirectoryStream = Files.newDirectoryStream(dir.getLocalPath(), filter);
70          LOGGER.debug("baseStream {}", baseDirectoryStream.getClass());
71          if (baseDirectoryStream instanceof java.nio.file.SecureDirectoryStream) {
72              LOGGER.debug("Returning SecureDirectoryStream");
73              return new SecureDirectoryStream(dir, path,
74                  (java.nio.file.SecureDirectoryStream<Path>) baseDirectoryStream);
75          }
76          return new SimpleDirectoryStream(path, baseDirectoryStream);
77      }
78  
79      private static class AcceptAllFilter
80          implements DirectoryStream.Filter<Path> {
81          static final MCRDirectoryStreamHelper.AcceptAllFilter FILTER = new AcceptAllFilter();
82  
83          @Override
84          public boolean accept(Path entry) {
85              return true;
86          }
87      }
88  
89      private static class MCRFileCollectionFilter
90          implements DirectoryStream.Filter<Path> {
91          static final MCRDirectoryStreamHelper.MCRFileCollectionFilter FILTER = new MCRFileCollectionFilter();
92  
93          @Override
94          public boolean accept(Path entry) {
95              return !MCRFileCollection.DATA_FILE.equals(entry.getFileName().toString());
96          }
97      }
98  
99      private static class SimpleDirectoryStream<T extends DirectoryStream<Path>> implements DirectoryStream<Path> {
100         protected final MCRPath dirPath;
101 
102         protected final T baseStream;
103 
104         boolean isClosed;
105 
106         SimpleDirectoryStream(MCRPath dirPath, T baseStream) {
107             this.dirPath = dirPath;
108             this.baseStream = baseStream;
109         }
110 
111         @Override
112         public Iterator<Path> iterator() {
113             return new SimpleDirectoryIterator(dirPath, baseStream);
114         }
115 
116         @Override
117         public void close() throws IOException {
118             baseStream.close();
119             isClosed = true;
120         }
121 
122         protected boolean isClosed() {
123             return isClosed;
124         }
125     }
126 
127     private static class SecureDirectoryStream extends SimpleDirectoryStream<java.nio.file.SecureDirectoryStream<Path>>
128         implements java.nio.file.SecureDirectoryStream<Path> {
129 
130         private final MCRDirectory dir;
131 
132         SecureDirectoryStream(MCRDirectory dir, MCRPath dirPath,
133             java.nio.file.SecureDirectoryStream<Path> baseStream) {
134             super(dirPath, baseStream);
135             this.dir = dir;
136         }
137 
138         @Override
139         public java.nio.file.SecureDirectoryStream<Path> newDirectoryStream(Path path, LinkOption... options)
140             throws IOException {
141             checkClosed();
142             if (path.isAbsolute()) {
143                 return (SecureDirectoryStream) Files.newDirectoryStream(path);
144             }
145             MCRStoredNode nodeByPath = resolve(path);
146             if (!nodeByPath.isDirectory()) {
147                 throw new NotDirectoryException(nodeByPath.getPath());
148             }
149             MCRDirectory newDir = (MCRDirectory) nodeByPath;
150             return (java.nio.file.SecureDirectoryStream<Path>) MCRDirectoryStreamHelper.getInstance(newDir,
151                 getCurrentSecurePath(newDir));
152         }
153 
154         private MCRStoredNode resolve(Path path) throws IOException {
155             checkRelativePath(path);
156             return (MCRStoredNode) dir.getNodeByPath(path.toString());
157         }
158 
159         /**
160          * always resolves the path to this directory
161          * currently not really secure, but more secure than sticking to <code>dirPath</code>
162          * @param node to get Path from
163          */
164         private MCRPath getCurrentSecurePath(MCRStoredNode node) {
165             return MCRAbstractFileSystem.getPath(MCRFileSystemUtils.getOwnerID(node), node.getPath(),
166                 MCRFileSystemProvider.getMCRIFSFileSystem());
167         }
168 
169         @Override
170         public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options,
171             FileAttribute<?>... attrs) throws IOException {
172             checkClosed();
173             if (path.isAbsolute()) {
174                 return Files.newByteChannel(path, options);
175             }
176             MCRPath mcrPath = checkRelativePath(path);
177             Path resolved = getCurrentSecurePath(dir).resolve(mcrPath);
178             return Files.newByteChannel(resolved, options);
179         }
180 
181         @Override
182         public void deleteFile(Path path) throws IOException {
183             checkClosed();
184             if (path.isAbsolute()) {
185                 Files.delete(path);
186             }
187             final MCRStoredNode storedNode = resolve(path);
188             final MCRPath mcrPath = getCurrentSecurePath(storedNode);
189             storedNode.delete();
190             MCRPathEventHelper.fireFileDeleteEvent(mcrPath);
191         }
192 
193         @Override
194         public void deleteDirectory(Path path) throws IOException {
195             checkClosed();
196             if (path.isAbsolute()) {
197                 Files.delete(path);
198             }
199             resolve(path).delete();
200         }
201 
202         @Override
203         public void move(Path srcpath, java.nio.file.SecureDirectoryStream<Path> targetdir, Path targetpath)
204             throws IOException {
205             checkClosed();
206             MCRPath src = checkFileSystem(srcpath);
207             MCRFile srcFile = srcpath.isAbsolute() ? MCRFileSystemUtils.getMCRFile(src, false, false, true)
208                 : (MCRFile) resolve(srcpath);
209             if (srcFile == null) {
210                 throw new NoSuchFileException(this.dirPath.toString(), srcpath.toString(), null);
211             }
212             if (!targetpath.isAbsolute() && targetdir instanceof SecureDirectoryStream) {
213                 LOGGER.debug("Move Case #1");
214                 SecureDirectoryStream that = (SecureDirectoryStream) targetdir;
215                 MCRFile file = getMCRFile(that, targetpath);
216                 Files.delete(file.getLocalPath()); //delete for move
217                 if (!srcpath.isAbsolute()) {
218                     LOGGER.debug("Move Case #1.1");
219                     baseStream.move(toLocalPath(src), that.baseStream, toLocalPath(targetpath));
220                 } else {
221                     LOGGER.debug("Move Case #1.2");
222                     baseStream.move(srcFile.getLocalPath(), that.baseStream, toLocalPath(targetpath));
223                 }
224                 file.setMD5(srcFile.getMD5()); //restore md5
225                 final MCRPath targetAbsolutePath = that.getCurrentSecurePath(file);
226                 final BasicFileAttributes attrs = that.getFileAttributeView(targetpath, BasicFileAttributeView.class)
227                     .readAttributes();
228                 MCRPathEventHelper.fireFileCreateEvent(targetAbsolutePath, attrs);
229             } else {
230                 LOGGER.debug("Move Case #2");
231                 if (targetpath.isAbsolute()) {
232                     LOGGER.debug("Move Case #2.1");
233                     Files.move(srcFile.getLocalPath(), targetpath, StandardCopyOption.COPY_ATTRIBUTES);
234                 } else {
235                     LOGGER.debug("Move Case #2.2");
236                     try (FileInputStream fis = new FileInputStream(srcFile.getLocalPath().toFile());
237                         FileChannel inChannel = fis.getChannel();
238                         SeekableByteChannel targetChannel = targetdir.newByteChannel(targetpath,
239                             Set.of(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE))) {
240                         long bytesTransferred = 0;
241                         while (bytesTransferred < inChannel.size()) {
242                             bytesTransferred += inChannel.transferTo(bytesTransferred, inChannel.size(), targetChannel);
243                         }
244                     }
245                 }
246             }
247             srcFile.delete();
248             MCRPathEventHelper.fireFileDeleteEvent(this.dirPath.resolve(src));
249         }
250 
251         private static MCRFile getMCRFile(SecureDirectoryStream ds, Path relativePath) throws IOException {
252             MCRStoredNode storedNode = ds.resolve(relativePath);
253             if (storedNode != null) {
254                 throw new FileAlreadyExistsException(ds.dirPath.resolve(relativePath).toString());
255             }
256             //does not exist, have to create
257             MCRStoredNode parent = ds.dir;
258             if (relativePath.getNameCount() > 1) {
259                 parent = (MCRStoredNode) parent.getNodeByPath(relativePath.getParent().toString());
260                 if (parent == null) {
261                     throw new NoSuchFileException(ds.dirPath.resolve(relativePath.getParent()).toString());
262                 }
263                 if (!(parent instanceof MCRDirectory)) {
264                     throw new NotDirectoryException(ds.dirPath.resolve(relativePath.getParent()).toString());
265                 }
266             }
267             return ((MCRDirectory) parent).createFile(relativePath.getFileName().toString());
268         }
269 
270         @Override
271         public <V extends FileAttributeView> V getFileAttributeView(Class<V> type) {
272             V fileAttributeView = baseStream.getFileAttributeView(type);
273             if (fileAttributeView != null) {
274                 return fileAttributeView;
275             }
276             if (type == MCRMD5AttributeView.class) {
277                 BasicFileAttributeView baseView = baseStream.getFileAttributeView(BasicFileAttributeView.class);
278                 return (V) new MD5FileAttributeViewImpl(baseView, (v) -> dir);
279             }
280             return null;
281         }
282 
283         @Override
284         public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, LinkOption... options) {
285             Path localRelativePath = toLocalPath(path);
286             if (type == MCRMD5AttributeView.class) {
287                 BasicFileAttributeView baseView = baseStream.getFileAttributeView(localRelativePath,
288                     BasicFileAttributeView.class, options);
289                 return (V) new MD5FileAttributeViewImpl(baseView, (v) -> resolve(path));
290             }
291             return baseStream.getFileAttributeView(localRelativePath, type, options);
292         }
293 
294         private Path toLocalPath(Path path) {
295             return MCRFileSystemUtils.toNativePath(dir.getLocalPath().getFileSystem(), path);
296         }
297 
298         void checkClosed() {
299             if (isClosed) {
300                 throw new ClosedDirectoryStreamException();
301             }
302         }
303 
304         MCRPath checkRelativePath(Path path) {
305             if (path.isAbsolute()) {
306                 throw new IllegalArgumentException(path + " is absolute.");
307             }
308             return checkFileSystem(path);
309         }
310 
311         private MCRPath checkFileSystem(Path path) {
312             if (!(path.getFileSystem() instanceof MCRIFSFileSystem)) {
313                 throw new IllegalArgumentException(path + " is not from " + MCRIFSFileSystem.class.getSimpleName());
314             }
315             return MCRPath.toMCRPath(path);
316         }
317     }
318 
319     private static class SimpleDirectoryIterator implements Iterator<Path> {
320         private final Iterator<Path> baseIterator;
321 
322         private final MCRPath dir;
323 
324         SimpleDirectoryIterator(MCRPath dir, DirectoryStream<Path> baseStream) {
325             this.baseIterator = baseStream.iterator();
326             this.dir = dir;
327         }
328 
329         @Override
330         public boolean hasNext() {
331             return baseIterator.hasNext();
332         }
333 
334         @Override
335         public Path next() {
336             Path basePath = baseIterator.next();
337             return dir.resolve(basePath.getFileName().toString());
338         }
339     }
340 
341     private static class MD5FileAttributeViewImpl implements
342         MCRMD5AttributeView {
343 
344         private final BasicFileAttributeView baseAttrView;
345 
346         private final MCRThrowFunction<Void, MCRStoredNode, IOException> nodeSupplier;
347 
348         MD5FileAttributeViewImpl(BasicFileAttributeView baseAttrView,
349             MCRThrowFunction<Void, MCRStoredNode, IOException> nodeSupplier) {
350             this.baseAttrView = baseAttrView;
351             this.nodeSupplier = nodeSupplier;
352         }
353 
354         @Override
355         public String name() {
356             return "md5";
357         }
358 
359         @Override
360         public BasicFileAttributes readAttributes() throws IOException {
361             return null;
362         }
363 
364         @Override
365         public void setTimes(FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime)
366             throws IOException {
367             baseAttrView.setTimes(lastModifiedTime, lastAccessTime, createTime);
368         }
369 
370         @Override
371         public MCRFileAttributes readAllAttributes() throws IOException {
372             MCRStoredNode node = nodeSupplier.apply(null);
373             if (node instanceof MCRFile) {
374                 return MCRFileAttributes.fromAttributes(baseAttrView.readAttributes(),
375                     ((MCRFile) node).getMD5());
376             }
377             return MCRFileAttributes.fromAttributes(baseAttrView.readAttributes(), null);
378         }
379     }
380 }