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.IOException;
22  import java.net.URI;
23  import java.nio.channels.FileChannel;
24  import java.nio.channels.SeekableByteChannel;
25  import java.nio.file.AccessDeniedException;
26  import java.nio.file.AccessMode;
27  import java.nio.file.AtomicMoveNotSupportedException;
28  import java.nio.file.CopyOption;
29  import java.nio.file.DirectoryNotEmptyException;
30  import java.nio.file.DirectoryStream;
31  import java.nio.file.DirectoryStream.Filter;
32  import java.nio.file.FileAlreadyExistsException;
33  import java.nio.file.FileStore;
34  import java.nio.file.FileSystem;
35  import java.nio.file.FileSystemAlreadyExistsException;
36  import java.nio.file.FileSystemNotFoundException;
37  import java.nio.file.Files;
38  import java.nio.file.InvalidPathException;
39  import java.nio.file.LinkOption;
40  import java.nio.file.NoSuchFileException;
41  import java.nio.file.NotDirectoryException;
42  import java.nio.file.OpenOption;
43  import java.nio.file.Path;
44  import java.nio.file.StandardCopyOption;
45  import java.nio.file.StandardOpenOption;
46  import java.nio.file.attribute.BasicFileAttributeView;
47  import java.nio.file.attribute.BasicFileAttributes;
48  import java.nio.file.attribute.FileAttribute;
49  import java.nio.file.attribute.FileAttributeView;
50  import java.nio.file.attribute.FileTime;
51  import java.nio.file.spi.FileSystemProvider;
52  import java.util.Collections;
53  import java.util.EnumSet;
54  import java.util.HashMap;
55  import java.util.HashSet;
56  import java.util.Map;
57  import java.util.Objects;
58  import java.util.Set;
59  import java.util.stream.Collectors;
60  
61  import org.mycore.common.MCRException;
62  import org.mycore.common.config.MCRConfiguration2;
63  import org.mycore.datamodel.ifs2.MCRDirectory;
64  import org.mycore.datamodel.ifs2.MCRFile;
65  import org.mycore.datamodel.ifs2.MCRFileCollection;
66  import org.mycore.datamodel.ifs2.MCRStoredNode;
67  import org.mycore.datamodel.metadata.MCRObjectID;
68  import org.mycore.datamodel.niofs.MCRAbstractFileSystem;
69  import org.mycore.datamodel.niofs.MCRFileAttributes;
70  import org.mycore.datamodel.niofs.MCRMD5AttributeView;
71  import org.mycore.datamodel.niofs.MCRPath;
72  import org.mycore.frontend.fileupload.MCRUploadHelper;
73  
74  import com.google.common.collect.Sets;
75  
76  /**
77   * @author Thomas Scheffler (yagee)
78   */
79  public class MCRFileSystemProvider extends FileSystemProvider {
80  
81      public static final String SCHEME = "ifs2";
82  
83      public static final URI FS_URI = URI.create(SCHEME + ":///");
84  
85      private static MCRAbstractFileSystem FILE_SYSTEM_INSTANCE;
86  
87      private static final Set<? extends CopyOption> SUPPORTED_COPY_OPTIONS = Collections.unmodifiableSet(EnumSet.of(
88          StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING));
89  
90      private static final Set<? extends OpenOption> SUPPORTED_OPEN_OPTIONS = EnumSet.of(StandardOpenOption.APPEND,
91          StandardOpenOption.DSYNC, StandardOpenOption.READ, StandardOpenOption.SPARSE, StandardOpenOption.SYNC,
92          StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
93  
94      /* (non-Javadoc)
95       * @see java.nio.file.spi.FileSystemProvider#getScheme()
96       */
97      @Override
98      public String getScheme() {
99          return SCHEME;
100     }
101 
102     /* (non-Javadoc)
103      * @see java.nio.file.spi.FileSystemProvider#newFileSystem(java.net.URI, java.util.Map)
104      */
105     @Override
106     public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
107         throw new FileSystemAlreadyExistsException();
108     }
109 
110     /* (non-Javadoc)
111      * @see java.nio.file.spi.FileSystemProvider#getFileSystem(java.net.URI)
112      */
113     @Override
114     public MCRIFSFileSystem getFileSystem(URI uri) {
115         if (FILE_SYSTEM_INSTANCE == null) {
116             synchronized (this) {
117                 if (FILE_SYSTEM_INSTANCE == null) {
118                     FILE_SYSTEM_INSTANCE = new MCRIFSFileSystem(this);
119                 }
120             }
121         }
122         return getMCRIFSFileSystem();
123     }
124 
125     /* (non-Javadoc)
126      * @see java.nio.file.spi.FileSystemProvider#getPath(java.net.URI)
127      */
128     @Override
129     public Path getPath(final URI uri) {
130         if (!FS_URI.getScheme().equals(Objects.requireNonNull(uri).getScheme())) {
131             throw new FileSystemNotFoundException("Unkown filesystem: " + uri);
132         }
133         String path = uri.getPath().substring(1);//URI path is absolute -> remove first slash
134         String owner = null;
135         for (int i = 0; i < path.length(); i++) {
136             if (path.charAt(i) == MCRAbstractFileSystem.SEPARATOR) {
137                 break;
138             }
139             if (path.charAt(i) == ':') {
140                 owner = path.substring(0, i);
141                 path = path.substring(i + 1);
142                 break;
143             }
144 
145         }
146         return MCRAbstractFileSystem.getPath(owner, path, getMCRIFSFileSystem());
147     }
148 
149     /* (non-Javadoc)
150      * @see java.nio.file.spi.FileSystemProvider#newByteChannel(java.nio.file.Path, java.util.Set, java.nio.file.attribute.FileAttribute[])
151      */
152     @Override
153     public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs)
154         throws IOException {
155         if (attrs.length > 0) {
156             throw new UnsupportedOperationException("Atomically setting of file attributes is not supported.");
157         }
158         MCRPath ifsPath = MCRFileSystemUtils.checkPathAbsolute(path);
159         Set<? extends OpenOption> fileOpenOptions = options.stream()
160             .filter(option -> !(option == StandardOpenOption.CREATE || option == StandardOpenOption.CREATE_NEW))
161             .collect(Collectors.toSet());
162         boolean create = options.contains(StandardOpenOption.CREATE);
163         boolean createNew = options.contains(StandardOpenOption.CREATE_NEW);
164         if (create || createNew) {
165             checkNewPathName(ifsPath);
166             for (OpenOption option : fileOpenOptions) {
167                 //check before we create any file instance
168                 checkOpenOption(option);
169             }
170         }
171         boolean channelCreateEvent = createNew || Files.notExists(ifsPath);
172         MCRFile mcrFile = MCRFileSystemUtils.getMCRFile(ifsPath, create, createNew, !channelCreateEvent);
173         if (mcrFile == null) {
174             throw new NoSuchFileException(path.toString());
175         }
176         boolean write = options.contains(StandardOpenOption.WRITE) || options.contains(StandardOpenOption.APPEND);
177         FileChannel baseChannel = (FileChannel) Files.newByteChannel(mcrFile.getLocalPath(), fileOpenOptions);
178         return new MCRFileChannel(ifsPath, mcrFile, baseChannel, write, channelCreateEvent);
179     }
180 
181     private static void checkNewPathName(MCRPath ifsPath) throws IOException {
182         //check property lazy as on initialization of this class MCRConfiguration2 is not ready
183         if (MCRConfiguration2.getBoolean("MCR.NIO.PathCreateNameCheck").orElse(true)) {
184             try {
185                 MCRUploadHelper.checkPathName(ifsPath.getFileName().toString(), true);
186             } catch (MCRException e) {
187                 throw new IOException(e.getMessage(), e);
188             }
189         }
190     }
191 
192     static void checkOpenOption(OpenOption option) {
193         if (!SUPPORTED_OPEN_OPTIONS.contains(option)) {
194             throw new UnsupportedOperationException("Unsupported OpenOption: " + option.getClass().getSimpleName()
195                 + "." + option);
196         }
197     }
198 
199     /* (non-Javadoc)
200      * @see java.nio.file.spi.FileSystemProvider#newDirectoryStream(java.nio.file.Path, java.nio.file.DirectoryStream.Filter)
201      */
202     @Override
203     public DirectoryStream<Path> newDirectoryStream(Path dir, Filter<? super Path> filter) throws IOException {
204         MCRPath mcrPath = MCRFileSystemUtils.checkPathAbsolute(dir);
205         MCRStoredNode node = MCRFileSystemUtils.resolvePath(mcrPath);
206         if (node instanceof MCRDirectory) {
207             return MCRDirectoryStreamHelper.getInstance((MCRDirectory) node, mcrPath);
208         }
209         throw new NotDirectoryException(dir.toString());
210     }
211 
212     /* (non-Javadoc)
213      * @see java.nio.file.spi.FileSystemProvider#createDirectory(java.nio.file.Path, java.nio.file.attribute.FileAttribute[])
214      */
215     @Override
216     public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
217         if (attrs.length > 0) {
218             throw new UnsupportedOperationException("Setting 'attrs' atomically is unsupported.");
219         }
220         MCRPath mcrPath = MCRFileSystemUtils.checkPathAbsolute(dir);
221         MCRDirectory rootDirectory;
222         if (mcrPath.isAbsolute() && mcrPath.getNameCount() == 0) {
223             MCRObjectID derId = MCRObjectID.getInstance(mcrPath.getOwner());
224             org.mycore.datamodel.ifs2.MCRFileStore store = MCRFileSystemUtils.getStore(derId.getBase());
225             if (store.retrieve(derId.getNumberAsInteger()) != null) {
226                 throw new FileAlreadyExistsException(mcrPath.toString());
227             }
228             store.create(derId.getNumberAsInteger());
229             return;
230         } else {
231             //not root directory
232             checkNewPathName(mcrPath);
233         }
234         rootDirectory = MCRFileSystemUtils.getFileCollection(mcrPath.getOwner());
235         MCRPath parentPath = mcrPath.getParent();
236         MCRPath absolutePath = getAbsolutePathFromRootComponent(parentPath);
237         MCRStoredNode childByPath = (MCRStoredNode) rootDirectory.getNodeByPath(absolutePath.toString());
238         if (childByPath == null) {
239             throw new NoSuchFileException(parentPath.toString(), dir.getFileName().toString(),
240                 "parent directory does not exist");
241         }
242         if (childByPath instanceof MCRFile) {
243             throw new NotDirectoryException(parentPath.toString());
244         }
245         MCRDirectory parentDir = (MCRDirectory) childByPath;
246         String dirName = mcrPath.getFileName().toString();
247         if (parentDir.getChild(dirName) != null) {
248             throw new FileAlreadyExistsException(mcrPath.toString());
249         }
250         parentDir.createDir(dirName);
251     }
252 
253     static MCRPath getAbsolutePathFromRootComponent(MCRPath mcrPath) {
254         if (!mcrPath.isAbsolute()) {
255             throw new InvalidPathException(mcrPath.toString(), "'path' must be absolute.");
256         }
257         String path = mcrPath.toString().substring(mcrPath.getOwner().length() + 1);
258         return MCRAbstractFileSystem.getPath(null, path, mcrPath.getFileSystem());
259     }
260 
261     /* (non-Javadoc)
262      * @see java.nio.file.spi.FileSystemProvider#delete(java.nio.file.Path)
263      */
264     @Override
265     public void delete(Path path) throws IOException {
266         MCRPath mcrPath = MCRFileSystemUtils.checkPathAbsolute(path);
267         MCRStoredNode child = MCRFileSystemUtils.resolvePath(mcrPath);
268         if (child instanceof MCRDirectory && child.hasChildren()) {
269             throw new DirectoryNotEmptyException(mcrPath.toString());
270         }
271         try {
272             child.delete();
273             MCRPathEventHelper.fireFileDeleteEvent(path);
274         } catch (RuntimeException e) {
275             throw new IOException("Could not delete: " + mcrPath, e);
276         }
277     }
278 
279     /* (non-Javadoc)
280      * @see java.nio.file.spi.FileSystemProvider#copy(java.nio.file.Path, java.nio.file.Path, java.nio.file.CopyOption[])
281      */
282     @Override
283     public void copy(Path source, Path target, CopyOption... options) throws IOException {
284         if (isSameFile(source, target)) {
285             return; //that was easy
286         }
287         checkCopyOptions(options);
288         HashSet<CopyOption> copyOptions = Sets.newHashSet(options);
289         boolean createNew = !copyOptions.contains(StandardCopyOption.REPLACE_EXISTING);
290         MCRPath src = MCRFileSystemUtils.checkPathAbsolute(source);
291         MCRPath tgt = MCRFileSystemUtils.checkPathAbsolute(target);
292         MCRStoredNode srcNode = MCRFileSystemUtils.resolvePath(src);
293         //checkParent of target;
294         if (tgt.getNameCount() == 0 && srcNode instanceof MCRDirectory) {
295             MCRDirectory tgtDir = MCRFileSystemUtils.getFileCollection(tgt.getOwner());
296             if (tgtDir != null) {
297                 if (tgtDir.hasChildren() && copyOptions.contains(StandardCopyOption.REPLACE_EXISTING)) {
298                     throw new DirectoryNotEmptyException(tgt.toString());
299                 }
300             } else {
301                 MCRObjectID tgtDerId = MCRObjectID.getInstance(tgt.getOwner());
302                 org.mycore.datamodel.ifs2.MCRFileStore store = MCRFileSystemUtils.getStore(tgtDerId.getBase());
303                 MCRFileCollection tgtCollection = store.create(tgtDerId.getNumberAsInteger());
304                 if (copyOptions.contains(StandardCopyOption.COPY_ATTRIBUTES)) {
305                     copyDirectoryAttributes((MCRDirectory) srcNode, tgtCollection);
306                 }
307             }
308             return; //created new root component
309         }
310         if (srcNode instanceof MCRFile) {
311             copyFile((MCRFile) srcNode, tgt, copyOptions, createNew);
312         } else if (srcNode instanceof MCRDirectory) {
313             copyDirectory((MCRDirectory) srcNode, tgt, copyOptions);
314         }
315     }
316 
317     private static void copyFile(MCRFile srcNode, MCRPath target, HashSet<CopyOption> copyOptions, boolean createNew)
318         throws IOException {
319         MCRFile srcFile = srcNode;
320         boolean fireCreateEvent = createNew || Files.notExists(target);
321         MCRFile targetFile = MCRFileSystemUtils.getMCRFile(target, true, createNew, !fireCreateEvent);
322         targetFile.setContent(srcFile.getContent());
323         if (copyOptions.contains(StandardCopyOption.COPY_ATTRIBUTES)) {
324             copyFileAttributes(srcFile, targetFile);
325         }
326         if (fireCreateEvent) {
327             MCRPathEventHelper.fireFileCreateEvent(target, targetFile.getBasicFileAttributes());
328         } else {
329             MCRPathEventHelper.fireFileUpdateEvent(target, targetFile.getBasicFileAttributes());
330         }
331     }
332 
333     private static void copyDirectory(MCRDirectory srcNode, MCRPath target, HashSet<CopyOption> copyOptions)
334         throws IOException {
335         MCRDirectory tgtParentDir = MCRFileSystemUtils.resolvePath(target.getParent());
336         MCRStoredNode child = (MCRStoredNode) tgtParentDir.getChild(target.getFileName().toString());
337         if (child != null) {
338             if (!copyOptions.contains(StandardCopyOption.REPLACE_EXISTING)) {
339                 throw new FileAlreadyExistsException(tgtParentDir.toString(), target.getFileName().toString(), null);
340             }
341             if (child instanceof MCRFile) {
342                 throw new NotDirectoryException(target.toString());
343             }
344             MCRDirectory tgtDir = (MCRDirectory) child;
345             if (tgtDir.hasChildren() && copyOptions.contains(StandardCopyOption.REPLACE_EXISTING)) {
346                 throw new DirectoryNotEmptyException(target.toString());
347             }
348             if (copyOptions.contains(StandardCopyOption.COPY_ATTRIBUTES)) {
349                 copyDirectoryAttributes(srcNode, tgtDir);
350             }
351         } else {
352             //simply create directory
353             @SuppressWarnings("unused")
354             MCRDirectory tgtDir = tgtParentDir.createDir(target.getFileName().toString());
355             if (copyOptions.contains(StandardCopyOption.COPY_ATTRIBUTES)) {
356                 copyDirectoryAttributes(srcNode, tgtDir);
357             }
358         }
359     }
360 
361     private static void copyFileAttributes(MCRFile source, MCRFile target)
362         throws IOException {
363         Path targetLocalFile = target.getLocalPath();
364         BasicFileAttributeView targetBasicFileAttributeView = Files.getFileAttributeView(targetLocalFile,
365             BasicFileAttributeView.class);
366         BasicFileAttributes srcAttr = Files.readAttributes(source.getLocalPath(), BasicFileAttributes.class);
367         target.setMD5(source.getMD5());
368         targetBasicFileAttributeView.setTimes(srcAttr.lastModifiedTime(), srcAttr.lastAccessTime(),
369             srcAttr.creationTime());
370     }
371 
372     private static void copyDirectoryAttributes(MCRDirectory source, MCRDirectory target)
373         throws IOException {
374         Path tgtLocalPath = target.getLocalPath();
375         Path srcLocalPath = source.getLocalPath();
376         BasicFileAttributes srcAttrs = Files
377             .readAttributes(srcLocalPath, BasicFileAttributes.class);
378         Files.getFileAttributeView(tgtLocalPath, BasicFileAttributeView.class)
379             .setTimes(srcAttrs.lastModifiedTime(), srcAttrs.lastAccessTime(), srcAttrs.creationTime());
380     }
381 
382     private void checkCopyOptions(CopyOption[] options) {
383         for (CopyOption option : options) {
384             if (!SUPPORTED_COPY_OPTIONS.contains(option)) {
385                 throw new UnsupportedOperationException("Unsupported copy option: " + option);
386             }
387         }
388     }
389 
390     /* (non-Javadoc)
391      * @see java.nio.file.spi.FileSystemProvider#move(java.nio.file.Path, java.nio.file.Path, java.nio.file.CopyOption[])
392      */
393     @Override
394     public void move(Path source, Path target, CopyOption... options) throws IOException {
395         HashSet<CopyOption> copyOptions = Sets.newHashSet(options);
396         if (copyOptions.contains(StandardCopyOption.ATOMIC_MOVE)) {
397             throw new AtomicMoveNotSupportedException(source.toString(), target.toString(),
398                 "ATOMIC_MOVE not supported yet");
399         }
400         if (Files.isDirectory(source)) {
401             MCRPath src = MCRFileSystemUtils.checkPathAbsolute(source);
402             MCRDirectory srcRootDirectory = MCRFileSystemUtils.getFileCollection(src.getOwner());
403             if (srcRootDirectory.hasChildren()) {
404                 throw new IOException("Directory is not empty");
405             }
406         }
407         copy(source, target, options);
408         delete(source);
409     }
410 
411     /* (non-Javadoc)
412      * @see java.nio.file.spi.FileSystemProvider#isSameFile(java.nio.file.Path, java.nio.file.Path)
413      */
414     @Override
415     public boolean isSameFile(Path path, Path path2) throws IOException {
416         return MCRFileSystemUtils.checkPathAbsolute(path).equals(MCRFileSystemUtils.checkPathAbsolute(path2));
417     }
418 
419     /* (non-Javadoc)
420      * @see java.nio.file.spi.FileSystemProvider#isHidden(java.nio.file.Path)
421      */
422     @Override
423     public boolean isHidden(Path path) throws IOException {
424         MCRFileSystemUtils.checkPathAbsolute(path);
425         return false;
426     }
427 
428     /* (non-Javadoc)
429      * @see java.nio.file.spi.FileSystemProvider#getFileStore(java.nio.file.Path)
430      */
431     @Override
432     public FileStore getFileStore(Path path) throws IOException {
433         MCRPath mcrPath = MCRFileSystemUtils.checkPathAbsolute(path);
434         MCRStoredNode node = MCRFileSystemUtils.resolvePath(mcrPath);
435         return MCRFileStore.getInstance(node);
436     }
437 
438     /* (non-Javadoc)
439      * @see java.nio.file.spi.FileSystemProvider#checkAccess(java.nio.file.Path, java.nio.file.AccessMode[])
440      */
441     @Override
442     public void checkAccess(Path path, AccessMode... modes) throws IOException {
443         MCRPath mcrPath = MCRFileSystemUtils.checkPathAbsolute(path);
444         MCRStoredNode node = MCRFileSystemUtils.resolvePath(mcrPath);
445         if (node == null) {
446             throw new NoSuchFileException(mcrPath.toString());
447         }
448         if (node instanceof MCRDirectory) {
449             checkDirectoryAccessModes(modes);
450         } else {
451             checkFile((MCRFile) node, modes);
452         }
453     }
454 
455     private void checkDirectoryAccessModes(AccessMode... modes) throws AccessDeniedException {
456         for (AccessMode mode : modes) {
457             switch (mode) {
458             case READ:
459             case WRITE:
460             case EXECUTE:
461                 break;
462             default:
463                 throw new AccessDeniedException("Unsupported AccessMode: " + mode);
464             }
465         }
466     }
467 
468     private void checkFile(MCRFile file, AccessMode... modes) throws AccessDeniedException {
469         for (AccessMode mode : modes) {
470             switch (mode) {
471             case READ:
472             case WRITE:
473                 break;
474             case EXECUTE:
475                 throw new AccessDeniedException(MCRFileSystemUtils.toPath(file).toString(), null,
476                     "Unsupported AccessMode: " + mode);
477             default:
478                 throw new AccessDeniedException("Unsupported AccessMode: " + mode);
479             }
480         }
481     }
482 
483     /* (non-Javadoc)
484      * @see java.nio.file.spi.FileSystemProvider#getFileAttributeView(java.nio.file.Path, java.lang.Class, java.nio.file.LinkOption[])
485      */
486     @SuppressWarnings("unchecked")
487     @Override
488     public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, LinkOption... options) {
489         MCRPath mcrPath = MCRFileSystemUtils.checkPathAbsolute(path);
490         //must support BasicFileAttributeView
491         if (type == BasicFileAttributeView.class) {
492             return (V) new BasicFileAttributeViewImpl(mcrPath);
493         }
494         if (type == MCRMD5AttributeView.class) {
495             return (V) new MD5FileAttributeViewImpl(mcrPath);
496         }
497         return null;
498     }
499 
500     /* (non-Javadoc)
501      * @see java.nio.file.spi.FileSystemProvider#readAttributes(java.nio.file.Path, java.lang.Class, java.nio.file.LinkOption[])
502      */
503     @SuppressWarnings("unchecked")
504     @Override
505     public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options)
506         throws IOException {
507         MCRPath mcrPath = MCRFileSystemUtils.checkPathAbsolute(path);
508         MCRStoredNode node = MCRFileSystemUtils.resolvePath(mcrPath);
509         //must support BasicFileAttributeView
510         if (type == BasicFileAttributes.class || type == MCRFileAttributes.class) {
511             return (A) MCRBasicFileAttributeViewImpl.readAttributes(node);
512         }
513         return null;
514     }
515 
516     /* (non-Javadoc)
517      * @see java.nio.file.spi.FileSystemProvider#readAttributes(java.nio.file.Path, java.lang.String, java.nio.file.LinkOption[])
518      */
519     @Override
520     public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
521         MCRPath mcrPath = MCRFileSystemUtils.checkPathAbsolute(path);
522         String[] s = splitAttrName(attributes);
523         if (s[0].length() == 0) {
524             throw new IllegalArgumentException(attributes);
525         }
526         BasicFileAttributeViewImpl view = null;
527         switch (s[0]) {
528         case "basic":
529             view = new BasicFileAttributeViewImpl(mcrPath);
530             break;
531         case "md5":
532             view = new MD5FileAttributeViewImpl(mcrPath);
533             break;
534         default:
535             throw new UnsupportedOperationException("View '" + s[0] + "' not available");
536         }
537         return view.getAttributeMap(s[1].split(","));
538     }
539 
540     private static String[] splitAttrName(String attribute) {
541         String[] s = new String[2];
542         int pos = attribute.indexOf(':');
543         if (pos == -1) {
544             s[0] = "basic";
545             s[1] = attribute;
546         } else {
547             s[0] = attribute.substring(0, pos++);
548             s[1] = (pos == attribute.length()) ? "" : attribute.substring(pos);
549         }
550         return s;
551     }
552 
553     /* (non-Javadoc)
554      * @see java.nio.file.spi.FileSystemProvider#setAttribute(java.nio.file.Path, java.lang.String, java.lang.Object, java.nio.file.LinkOption[])
555      */
556     @Override
557     public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
558         throw new UnsupportedOperationException("setAttributes is not implemented yet.");
559     }
560 
561     public static MCRIFSFileSystem getMCRIFSFileSystem() {
562         return (MCRIFSFileSystem) (FILE_SYSTEM_INSTANCE == null ? MCRAbstractFileSystem.getInstance(SCHEME)
563             : FILE_SYSTEM_INSTANCE);
564     }
565 
566     static class BasicFileAttributeViewImpl extends MCRBasicFileAttributeViewImpl {
567         private static final String SIZE_NAME = "size";
568 
569         private static final String CREATION_TIME_NAME = "creationTime";
570 
571         private static final String LAST_ACCESS_TIME_NAME = "lastAccessTime";
572 
573         private static final String LAST_MODIFIED_TIME_NAME = "lastModifiedTime";
574 
575         private static final String FILE_KEY_NAME = "fileKey";
576 
577         private static final String IS_DIRECTORY_NAME = "isDirectory";
578 
579         private static final String IS_REGULAR_FILE_NAME = "isRegularFile";
580 
581         private static final String IS_SYMBOLIC_LINK_NAME = "isSymbolicLink";
582 
583         private static final String IS_OTHER_NAME = "isOther";
584 
585         private static HashSet<String> allowedAttr = Sets.newHashSet("*", SIZE_NAME, CREATION_TIME_NAME,
586             LAST_ACCESS_TIME_NAME, LAST_MODIFIED_TIME_NAME, FILE_KEY_NAME, IS_DIRECTORY_NAME, IS_REGULAR_FILE_NAME,
587             IS_SYMBOLIC_LINK_NAME, IS_OTHER_NAME);
588 
589         protected MCRPath path;
590 
591         BasicFileAttributeViewImpl(Path path) {
592             this.path = MCRPath.toMCRPath(path);
593             if (!path.isAbsolute()) {
594                 throw new InvalidPathException(path.toString(), "'path' must be absolute.");
595             }
596         }
597 
598         @Override
599         protected MCRStoredNode resolveNode() throws IOException {
600             return MCRFileSystemUtils.resolvePath(this.path);
601         }
602 
603         public Map<String, Object> getAttributeMap(String... attributes) throws IOException {
604             Set<String> allowed = getAllowedAttributes();
605             boolean copyAll = false;
606             for (String attr : attributes) {
607                 if (!allowed.contains(attr)) {
608                     throw new IllegalArgumentException("'" + attr + "' not recognized");
609                 }
610                 if ("*".equals(attr)) {
611                     copyAll = true;
612                 }
613             }
614             Set<String> requested = copyAll ? allowed : Sets.newHashSet(attributes);
615             return buildMap(requested, readAttributes());
616         }
617 
618         protected Map<String, Object> buildMap(Set<String> requested, MCRFileAttributes<String> attrs) {
619             HashMap<String, Object> map = new HashMap<>();
620             for (String attr : map.keySet()) {
621                 switch (attr) {
622                 case SIZE_NAME:
623                     map.put(attr, attrs.size());
624                     break;
625                 case CREATION_TIME_NAME:
626                     map.put(attr, attrs.creationTime());
627                     break;
628                 case LAST_ACCESS_TIME_NAME:
629                     map.put(attr, attrs.lastAccessTime());
630                     break;
631                 case LAST_MODIFIED_TIME_NAME:
632                     map.put(attr, attrs.lastModifiedTime());
633                     break;
634                 case FILE_KEY_NAME:
635                     map.put(attr, attrs.fileKey());
636                     break;
637                 case IS_DIRECTORY_NAME:
638                     map.put(attr, attrs.isDirectory());
639                     break;
640                 case IS_REGULAR_FILE_NAME:
641                     map.put(attr, attrs.isRegularFile());
642                     break;
643                 case IS_SYMBOLIC_LINK_NAME:
644                     map.put(attr, attrs.isSymbolicLink());
645                     break;
646                 case IS_OTHER_NAME:
647                     map.put(attr, attrs.isOther());
648                     break;
649                 default:
650                     //ignored
651                     break;
652                 }
653             }
654             return map;
655         }
656 
657         public void setAttribute(String name, Object value) throws IOException {
658             Set<String> allowed = getAllowedAttributes();
659             if ("*".equals(name) || !allowed.contains(name)) {
660                 throw new IllegalArgumentException("'" + name + "' not recognized");
661             }
662             switch (name) {
663             case CREATION_TIME_NAME:
664                 this.setTimes(null, null, (FileTime) value);
665                 break;
666             case LAST_ACCESS_TIME_NAME:
667                 this.setTimes(null, (FileTime) value, null);
668                 break;
669             case LAST_MODIFIED_TIME_NAME:
670                 this.setTimes((FileTime) value, null, null);
671                 break;
672             case SIZE_NAME:
673             case FILE_KEY_NAME:
674             case IS_DIRECTORY_NAME:
675             case IS_REGULAR_FILE_NAME:
676             case IS_SYMBOLIC_LINK_NAME:
677             case IS_OTHER_NAME:
678                 throw new IllegalArgumentException("'" + name + "' is a read-only attribute.");
679             default:
680                 //ignored
681                 break;
682             }
683 
684         }
685 
686         protected Set<String> getAllowedAttributes() {
687             return allowedAttr;
688         }
689     }
690 
691     private static class MD5FileAttributeViewImpl extends BasicFileAttributeViewImpl implements
692         MCRMD5AttributeView<String> {
693 
694         private static String MD5_NAME = "md5";
695 
696         private static Set<String> allowedAttr = Sets.union(BasicFileAttributeViewImpl.allowedAttr,
697             Sets.newHashSet(MD5_NAME));
698 
699         MD5FileAttributeViewImpl(Path path) {
700             super(path);
701         }
702 
703         @Override
704         public MCRFileAttributes<String> readAllAttributes() throws IOException {
705             return readAttributes();
706         }
707 
708         @Override
709         public String name() {
710             return "md5";
711         }
712 
713         @Override
714         protected Set<String> getAllowedAttributes() {
715             return allowedAttr;
716         }
717 
718         @Override
719         protected Map<String, Object> buildMap(Set<String> requested, MCRFileAttributes<String> attrs) {
720             Map<String, Object> buildMap = super.buildMap(requested, attrs);
721             if (requested.contains(MD5_NAME)) {
722                 buildMap.put(MD5_NAME, attrs.md5sum());
723             }
724             return buildMap;
725         }
726 
727         @Override
728         public void setAttribute(String name, Object value) throws IOException {
729             if (MD5_NAME.equals(name)) {
730                 MCRStoredNode node = resolveNode();
731                 if (node instanceof MCRDirectory) {
732                     throw new IOException("Cannot set md5sum on directories: " + path);
733                 }
734                 ((MCRFile) node).setMD5((String) value);
735             } else {
736                 super.setAttribute(name, value);
737             }
738         }
739 
740     }
741 
742 }