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.ifs2;
20  
21  import java.io.IOException;
22  import java.io.UncheckedIOException;
23  import java.nio.file.FileAlreadyExistsException;
24  import java.nio.file.Files;
25  import java.nio.file.Path;
26  import java.nio.file.StandardCopyOption;
27  import java.util.ArrayList;
28  import java.util.List;
29  import java.util.stream.Stream;
30  
31  import org.apache.logging.log4j.LogManager;
32  import org.apache.logging.log4j.Logger;
33  import org.jdom2.Content;
34  import org.jdom2.Document;
35  import org.jdom2.Element;
36  import org.jdom2.JDOMException;
37  import org.mycore.common.content.MCRJDOMContent;
38  import org.mycore.common.content.MCRPathContent;
39  import org.mycore.datamodel.niofs.utils.MCRRecursiveDeleter;
40  import org.mycore.util.concurrent.MCRReadWriteGuard;
41  import org.xml.sax.SAXException;
42  
43  /**
44   * Represents a set of files and directories belonging together, that are stored
45   * in a persistent MCRFileStore. A FileCollection has a unique ID within the
46   * store, it is the root folder of all files and directories in the collection.
47   * 
48   * @author Frank Lützenkirchen
49   */
50  public class MCRFileCollection extends MCRDirectory {
51  
52      /**
53       * The logger
54       */
55      private static final Logger LOGGER = LogManager.getLogger(MCRFileCollection.class);
56  
57      public static final String DATA_FILE = "mcrdata.xml";
58  
59      /**
60       * The store this file collection is stored in.
61       */
62      private MCRStore store;
63  
64      /**
65       * The ID of this file collection
66       */
67      private int id;
68  
69      /**
70       * Guard for additional data
71       *
72       * MCR-1869
73       */
74      private MCRReadWriteGuard dataGuard;
75  
76      /**
77       * Creates a new file collection in the given store, or retrieves an
78       * existing one.
79       *
80       * @see MCRFileStore
81       *
82       * @param store
83       *            the store this file collection is stored in
84       * @param id
85       *            the ID of this file collection
86       */
87      protected MCRFileCollection(MCRStore store, int id) throws IOException {
88          super(null, store.getSlot(id), new Element("collection"));
89          this.store = store;
90          this.id = id;
91          this.dataGuard = new MCRReadWriteGuard();
92          if (Files.exists(path)) {
93              readAdditionalData();
94          } else {
95              Files.createDirectories(path);
96              writeData(Document::new);
97              saveAdditionalData();
98          }
99      }
100 
101     MCRReadWriteGuard getDataGuard() {
102         return dataGuard;
103     }
104 
105     private void readAdditionalData() throws IOException {
106         Path src = path.resolve(DATA_FILE);
107         if (!Files.exists(src)) {
108             LOGGER.warn("Metadata file is missing, repairing metadata...");
109             writeData(e -> {
110                 e.detach();
111                 e.setName("collection");
112                 e.removeContent();
113                 new Document(e);
114             });
115             repairMetadata();
116         }
117         try {
118             Element parsed = new MCRPathContent(src).asXML().getRootElement();
119             writeData(e -> {
120                 e.detach();
121                 e.setName("collection");
122                 e.removeContent();
123                 List<Content> parsedContent = new ArrayList<>(parsed.getContent());
124                 parsedContent.forEach(Content::detach);
125                 e.addContent(parsedContent);
126                 new Document(e);
127             });
128         } catch (JDOMException | SAXException e) {
129             throw new IOException(e);
130         }
131     }
132 
133     protected void saveAdditionalData() throws IOException {
134         Path target = path.resolve(DATA_FILE);
135         try {
136             readData(e -> {
137                 try {
138                     boolean needsUpdate = true;
139                     while (needsUpdate) {
140                         try {
141                             new MCRJDOMContent(e.getDocument()).sendTo(target, StandardCopyOption.REPLACE_EXISTING);
142                             needsUpdate = false;
143                         } catch (FileAlreadyExistsException ex) {
144                             //other process writes to the same file, let us win
145                         } catch (IOException | RuntimeException ex) {
146                             throw ex;
147                         }
148                     }
149                     return null;
150                 } catch (IOException e1) {
151                     throw new UncheckedIOException(e1);
152                 }
153             });
154         } catch (UncheckedIOException e) {
155             throw e.getCause();
156         }
157     }
158 
159     @Override
160     public Stream<MCRNode> getChildren() throws IOException {
161         return super.getChildren()
162             .filter(f -> !DATA_FILE.equals(f.getName()));
163     }
164 
165     /**
166      * Deletes this file collection with all its data and children
167      */
168     public void delete() throws IOException {
169         writeData(Element::removeContent);
170         Files.walkFileTree(path, MCRRecursiveDeleter.instance());
171         store.delete(id);
172     }
173 
174     /**
175      * Throws a exception, because a file collection's name is always the empty
176      * string and therefore can not be renamed.
177      */
178     @Override
179     public void renameTo(String name) {
180         throw new UnsupportedOperationException("File collections can not be renamed");
181     }
182 
183     /**
184      * Returns the store this file collection is stored in.
185      * 
186      * @return the store this file collection is stored in.
187      */
188     public MCRStore getStore() {
189         return store;
190     }
191 
192     /**
193      * Returns the ID of this file collection
194      * 
195      * @return the ID of this file collection
196      */
197     public int getID() {
198         return id;
199     }
200 
201     /**
202      * Returns this object, because the FileCollection instance is the root of
203      * all files and directories contained in the collection.
204      * 
205      * @return this
206      */
207     @Override
208     public MCRFileCollection getRoot() {
209         return this;
210     }
211 
212     @Override
213     public MCRNode getChild(String name) {
214         if (DATA_FILE.equals(name)) {
215             return null;
216         } else {
217             return super.getChild(name);
218         }
219     }
220 
221     @Override
222     public boolean hasChildren() throws IOException {
223         try (Stream<Path> stream = getUsableChildSream()) {
224             return stream.findAny().isPresent();
225         }
226     }
227 
228     @Override
229     public int getNumChildren() throws IOException {
230         try (Stream<Path> stream = getUsableChildSream()) {
231             return Math.toIntExact(stream.count());
232         }
233     }
234 
235     private Stream<Path> getUsableChildSream() throws IOException {
236         return Files.list(path)
237             .filter(p -> !DATA_FILE.equals(p.getFileName().toString()));
238     }
239 
240     @Override
241     public String getName() {
242         return "";
243     }
244 
245     /**
246      * Repairs additional metadata stored for all files and directories in this
247      * collection
248      */
249     @Override
250     public void repairMetadata() throws IOException {
251         super.repairMetadata();
252         writeData(e -> {
253             e.setName("collection");
254             e.removeAttribute("name");
255         });
256         saveAdditionalData();
257     }
258 
259     /**
260      * Returns additional metadata stored for all files and directories in this
261      * collection
262      */
263     Document getMetadata() {
264         return new Document(readData(Element::clone));
265     }
266 
267 }