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;
20  
21  import java.io.IOException;
22  import java.net.URI;
23  import java.net.URISyntaxException;
24  import java.nio.file.DirectoryNotEmptyException;
25  import java.nio.file.FileAlreadyExistsException;
26  import java.nio.file.FileSystem;
27  import java.nio.file.FileSystemException;
28  import java.nio.file.FileSystemNotFoundException;
29  import java.nio.file.Files;
30  import java.nio.file.NoSuchFileException;
31  import java.nio.file.Path;
32  import java.nio.file.PathMatcher;
33  import java.nio.file.WatchService;
34  import java.nio.file.attribute.UserPrincipalLookupService;
35  import java.nio.file.spi.FileSystemProvider;
36  import java.util.Collections;
37  import java.util.Objects;
38  import java.util.Set;
39  
40  import org.apache.logging.log4j.LogManager;
41  import org.mycore.common.MCRException;
42  import org.mycore.common.MCRUtils;
43  
44  import com.google.common.cache.CacheBuilder;
45  import com.google.common.cache.CacheLoader;
46  import com.google.common.cache.LoadingCache;
47  import com.google.common.collect.Iterables;
48  import com.google.common.collect.Sets;
49  
50  public abstract class MCRAbstractFileSystem extends FileSystem {
51  
52      public static final char SEPARATOR = '/';
53  
54      public static final String SEPARATOR_STRING = String.valueOf(SEPARATOR);
55  
56      private final LoadingCache<String, MCRPath> rootDirectoryCache = CacheBuilder.newBuilder().weakValues()
57          .build(new CacheLoader<String, MCRPath>() {
58  
59              @Override
60              public MCRPath load(final String owner) throws Exception {
61                  return getPath(owner, "/", instance());
62              }
63          });
64  
65      private final MCRPath emptyPath;
66  
67      public MCRAbstractFileSystem() {
68          super();
69          emptyPath = getPath(null, "", this);
70      }
71  
72      /**
73       * Returns any subclass that implements and handles the given scheme.
74       * @param scheme a valid {@link URI} scheme
75       * @see FileSystemProvider#getScheme()
76       * @throws FileSystemNotFoundException if no filesystem handles this scheme
77       */
78      public static MCRAbstractFileSystem getInstance(String scheme) {
79          URI uri;
80          try {
81              uri = MCRPaths.getURI(scheme, "helper", SEPARATOR_STRING);
82          } catch (URISyntaxException e) {
83              throw new MCRException(e);
84          }
85          for (FileSystemProvider provider : Iterables.concat(MCRPaths.webAppProvider,
86              FileSystemProvider.installedProviders())) {
87              if (provider.getScheme().equals(scheme)) {
88                  return (MCRAbstractFileSystem) provider.getFileSystem(uri);
89              }
90          }
91          throw new FileSystemNotFoundException("Provider \"" + scheme + "\" not found");
92      }
93  
94      public static MCRPath getPath(final String owner, final String path, final MCRAbstractFileSystem fs) {
95          Objects.requireNonNull(fs, MCRAbstractFileSystem.class.getSimpleName() + " instance may not be null.");
96          return new MCRPath(owner, path) {
97  
98              @Override
99              public MCRAbstractFileSystem getFileSystem() {
100                 return fs;
101             }
102         };
103     }
104 
105     /**
106      * Creates a new root under the given name.
107      * 
108      * After calling this method the implementing FileSystem should
109      * be ready to accept data for this root.
110      * 
111      * @param owner ,e.g. derivate ID
112      * @throws FileSystemException if creating the root directory fails
113      * @throws FileAlreadyExistsException more specific, if the directory already exists
114      */
115     public abstract void createRoot(String owner) throws FileSystemException;
116 
117     /**
118      * Checks if the file for given Path is still valid.
119      * 
120      * This should check if the file is still completely readable and the MD5 sum still matches the recorded value.
121      * @param path Path to the file to check
122      * @return if the file is still in good condition
123      */
124     public boolean verifies(MCRPath path) throws NoSuchFileException {
125         try {
126             return verifies(path, Files.readAttributes(path, MCRFileAttributes.class));
127         } catch (IOException e) {
128             if (e instanceof NoSuchFileException) {
129                 throw (NoSuchFileException) e;
130             }
131             return false;
132         }
133     }
134 
135     /**
136      * Checks if the file for given Path is still valid.
137      * 
138      * This should check if the file is still completely readable and the MD5 sum still matches the recorded value.
139      * This method does the same as {@link #verifies(MCRPath)} but uses the given attributes to save a file access.
140      * @param path Path to the file to check
141      * @param attrs matching attributes to file
142      */
143     public boolean verifies(MCRPath path, MCRFileAttributes<?> attrs) throws NoSuchFileException {
144         if (Files.notExists(Objects.requireNonNull(path, "Path may not be null."))) {
145             throw new NoSuchFileException(path.toString());
146         }
147         Objects.requireNonNull(attrs, "attrs may not be null");
148         String md5Sum;
149         try {
150             md5Sum = MCRUtils.getMD5Sum(Files.newInputStream(path));
151         } catch (IOException e) {
152             LogManager.getLogger(getClass()).error("Could not verify path: {}", path, e);
153             return false;
154         }
155         boolean returns = md5Sum.matches(attrs.md5sum());
156         if (!returns) {
157             LogManager.getLogger(getClass()).warn("MD5sum does not match: {}", path);
158         }
159         return returns;
160     }
161 
162     /**
163      * Removes a root with the given name.
164      * 
165      * Call this method if you want to remove a stalled directory that is not in use anymore.
166      * 
167      * @param owner ,e.g. derivate ID
168      * @throws FileSystemException if removing the root directory fails
169      * @throws DirectoryNotEmptyException more specific, if the directory is not empty
170      * @throws NoSuchFileException more specific, if the directory does not exist
171      */
172     public abstract void removeRoot(String owner) throws FileSystemException;
173 
174     @Override
175     public void close() throws IOException {
176         throw new UnsupportedOperationException();
177     }
178 
179     public MCRPath emptyPath() {
180         return emptyPath;
181     }
182 
183     @Override
184     public Path getPath(final String first, final String... more) {
185         String root = null;
186         final StringBuilder path = new StringBuilder();
187         if (!first.isEmpty() && first.charAt(first.length() - 1) == ':') {
188             //we have a root;
189             root = first;
190             path.append(SEPARATOR);
191         } else {
192             path.append(first);
193         }
194         boolean addSep = path.length() > 0;
195         for (final String element : more) {
196             if (!element.isEmpty()) {
197                 if (addSep) {
198                     path.append(SEPARATOR);
199                 } else {
200                     addSep = true;
201                 }
202                 path.append(element);
203             }
204         }
205         return getPath(root, path.toString(), this);
206     }
207 
208     @Override
209     public PathMatcher getPathMatcher(final String syntaxAndPattern) {
210         final int pos = syntaxAndPattern.indexOf(':');
211         if (pos <= 0 || pos == syntaxAndPattern.length()) {
212             throw new IllegalArgumentException();
213         }
214         final String syntax = syntaxAndPattern.substring(0, pos);
215         final String pattern = syntaxAndPattern.substring(pos + 1);
216         switch (syntax) {
217         case "glob":
218             return new MCRGlobPathMatcher(pattern);
219         case "regex":
220             return new MCRPathMatcher(pattern);
221         default:
222             throw new UnsupportedOperationException("If the pattern syntax '" + syntax + "' is not known.");
223         }
224     }
225 
226     public MCRPath getRootDirectory(final String owner) {
227         return rootDirectoryCache.getUnchecked(owner);
228     }
229 
230     @Override
231     public String getSeparator() {
232         return SEPARATOR_STRING;
233     }
234 
235     @Override
236     public UserPrincipalLookupService getUserPrincipalLookupService() {
237         throw new UnsupportedOperationException();
238     }
239 
240     @Override
241     public boolean isOpen() {
242         return true;
243     }
244 
245     @Override
246     public boolean isReadOnly() {
247         return false;
248     }
249 
250     @Override
251     public WatchService newWatchService() throws IOException {
252         throw new UnsupportedOperationException();
253     }
254 
255     @Override
256     public Set<String> supportedFileAttributeViews() {
257         return Collections.unmodifiableSet(Sets.newHashSet("basic", "mcrifs"));
258     }
259 
260     public MCRPath toThisFileSystem(final MCRPath other) {
261         if (Objects.requireNonNull(other).getFileSystem() == this) {
262             return other;
263         }
264         return getPath(other.root, other.path, this);
265     }
266 
267     private MCRAbstractFileSystem instance() {
268         return this;
269     }
270 
271 }