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.File;
22  import java.io.IOException;
23  import java.nio.file.Files;
24  import java.nio.file.Path;
25  import java.nio.file.attribute.BasicFileAttributes;
26  import java.nio.file.attribute.FileTime;
27  import java.time.Instant;
28  import java.util.Date;
29  import java.util.Map;
30  import java.util.TreeMap;
31  import java.util.function.Consumer;
32  import java.util.function.Function;
33  
34  import org.jdom2.Element;
35  import org.jdom2.Namespace;
36  import org.mycore.common.MCRConstants;
37  import org.mycore.common.MCRSessionMgr;
38  import org.mycore.common.config.MCRConfiguration2;
39  import org.mycore.datamodel.niofs.MCRFileAttributes;
40  import org.mycore.datamodel.niofs.utils.MCRRecursiveDeleter;
41  
42  /**
43   * A file or directory really stored by importing it from outside the system.
44   * Can be modified, updated and deleted, in contrast to virtual nodes.
45   * 
46   * @author Frank Lützenkirchen
47   */
48  public abstract class MCRStoredNode extends MCRNode {
49  
50      private static final String LANG_ATT = "lang";
51  
52      private static final String LABEL_ELEMENT = "label";
53  
54      private static final String NAME_ATT = "name";
55  
56      /**
57       * Any additional data of this node that is not stored in the file object
58       */
59      private Element additionalData;
60  
61      /**
62       * Returns a stored node instance that already exists
63       * 
64       * @param parent
65       *            the parent directory containing this node
66       * @param fo
67       *            the file object in local filesystem representing this node
68       * @param data
69       *            the additional data of this node that is not stored in the
70       *            file object
71       */
72      protected MCRStoredNode(MCRDirectory parent, Path fo, Element data) {
73          super(parent, fo);
74          this.additionalData = data;
75      }
76  
77      /**
78       * Creates a new stored node
79       *
80       * @param parent
81       *            the parent directory
82       * @param name
83       *            the name of the node
84       * @param type
85       *            the node type, dir or file
86       */
87      protected MCRStoredNode(MCRDirectory parent, String name, String type) {
88          super(parent, parent.path.resolve(name));
89          additionalData = new Element(type);
90          additionalData.setAttribute(NAME_ATT, name);
91          parent.writeData(e -> e.addContent(additionalData));
92      }
93  
94      /**
95       * Returns the local {@link File} representing this stored file or directory. Be careful
96       * to use this only for reading data, do never modify directly!
97       *
98       * @return the file in the local filesystem representing this file
99       * @deprecated use {@link #getLocalPath()} instead
100      */
101     @Deprecated
102     public File getLocalFile() {
103         return path.toFile();
104     }
105 
106     /**
107      * Returns the local {@link Path} representing this stored file or directory. Be careful
108      * to use this only for reading data, do never modify directly!
109      *
110      * @return the file in the local filesystem representing this file
111      */
112     public Path getLocalPath() {
113         return path;
114     }
115 
116     /**
117      * Deletes this node with all its data and children
118      */
119     public void delete() throws IOException {
120         writeData(Element::detach);
121         if (Files.isDirectory(path)) {
122             Files.walkFileTree(path, MCRRecursiveDeleter.instance());
123         } else {
124             Files.deleteIfExists(path);
125         }
126         getRoot().saveAdditionalData();
127     }
128 
129     /**
130      * Renames this node.
131      * 
132      * @param name
133      *            the new file name
134      */
135     public void renameTo(String name) throws IOException {
136         Path oldPath = path;
137         Path newPath = path.resolveSibling(name);
138         Files.move(oldPath, newPath);
139         Files.setLastModifiedTime(newPath, FileTime.from(Instant.now()));
140         path = newPath;
141         writeData(e -> e.setAttribute(NAME_ATT, name));
142         getRoot().saveAdditionalData();
143     }
144 
145     /**
146      * Sets last modification time of this file to a custom value.
147      * 
148      * @param time
149      *            the time to be stored as last modification time
150      */
151     public void setLastModified(Date time) throws IOException {
152         Files.setLastModifiedTime(path, FileTime.from(time.toInstant()));
153     }
154 
155     /**
156      * Sets a label for this node
157      * 
158      * @param lang
159      *            the xml:lang language ID
160      * @param label
161      *            the label in this language
162      */
163     public void setLabel(String lang, String label) throws IOException {
164         writeData(e -> {
165             e.getChildren(LABEL_ELEMENT)
166                 .stream()
167                 .filter(child -> lang.equals(
168                     child.getAttributeValue(LANG_ATT,
169                         Namespace.XML_NAMESPACE)))
170                 .findAny()
171                 .orElseGet(() -> {
172                     Element newLabel = new Element(LABEL_ELEMENT).setAttribute(LANG_ATT, lang, Namespace.XML_NAMESPACE);
173                     e.addContent(newLabel);
174                     return newLabel;
175                 })
176                 .setText(label);
177         });
178         getRoot().saveAdditionalData();
179     }
180 
181     /**
182      * Removes all labels set
183      */
184     public void clearLabels() throws IOException {
185         writeData(e -> e.removeChildren(LABEL_ELEMENT));
186         getRoot().saveAdditionalData();
187     }
188 
189     /**
190      * Returns a map of all labels, sorted by xml:lang, Key is xml:lang, value
191      * is the label for that language.
192      */
193     public Map<String, String> getLabels() {
194         Map<String, String> labels = new TreeMap<>();
195         for (Element label : readData(e -> e.getChildren(LABEL_ELEMENT))) {
196             labels.put(label.getAttributeValue(LANG_ATT, Namespace.XML_NAMESPACE), label.getText());
197         }
198         return labels;
199     }
200 
201     /**
202      * Returns the label in the given language
203      * 
204      * @param lang
205      *            the xml:lang language ID
206      * @return the label, or null if there is no label for that language
207      */
208     public String getLabel(String lang) {
209         return readData(e -> e.getChildren(LABEL_ELEMENT)
210             .stream()
211             .filter(label -> lang.equals(
212                 label.getAttributeValue(LANG_ATT, Namespace.XML_NAMESPACE)))
213             .findAny()
214             .map(Element::getText)
215             .orElse(null));
216     }
217 
218     /**
219      * Returns the label in the current language, otherwise in default language,
220      * otherwise the first label defined, if any at all.
221      * 
222      * @return the label
223      */
224     public String getCurrentLabel() {
225         String currentLang = MCRSessionMgr.getCurrentSession().getCurrentLanguage();
226         String label = getLabel(currentLang);
227         if (label != null) {
228             return label;
229         }
230 
231         String defaultLang = MCRConfiguration2.getString("MCR.Metadata.DefaultLang").orElse(MCRConstants.DEFAULT_LANG);
232         label = getLabel(defaultLang);
233         if (label != null) {
234             return label;
235         }
236 
237         return readData(e -> e.getChildText(LABEL_ELEMENT));
238     }
239 
240     /**
241      * Repairs additional metadata of this node
242      */
243     abstract void repairMetadata() throws IOException;
244 
245     protected <T> T readData(Function<Element, T> readOperation) {
246         return getRoot().getDataGuard().read(() -> readOperation.apply(additionalData));
247     }
248 
249     protected <T> void writeData(Consumer<Element> writeOperation) {
250         getRoot().getDataGuard().write(() -> writeOperation.accept(additionalData));
251     }
252 
253     public MCRFileAttributes<String> getBasicFileAttributes() throws IOException {
254         BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
255         return MCRFileAttributes.fromAttributes(attrs, null);
256     }
257 
258 }