001    /*
002     * $Revision: 14596 $ $Date: 2009-01-19 08:12:38 +0100 (Mon, 19 Jan 2009) $ This file is part of M y C o R e See http://www.mycore.de/ for details. This program
003     * is free software; you can use it, redistribute it and / or modify it under the terms of the GNU General Public License (GPL) as published by the Free
004     * Software Foundation; either version 2 of the License or (at your option) any later version. This program is distributed in the hope that it will be useful,
005     * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
006     * more details. You should have received a copy of the GNU General Public License along with this program, in a file called gpl.txt or license.txt. If not,
007     * write to the Free Software Foundation Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307 USA
008     */
009    
010    package org.mycore.datamodel.ifs;
011    
012    import java.io.IOException;
013    import java.text.DateFormat;
014    import java.text.SimpleDateFormat;
015    import java.util.GregorianCalendar;
016    
017    import org.jdom.Document;
018    import org.jdom.Element;
019    import org.jdom.JDOMException;
020    import org.mycore.common.MCRUsageException;
021    
022    /**
023     * Represents a stored file or directory node with its metadata and content.
024     * 
025     * @author Frank L\u00fctzenkirchen
026     * @version $Revision: 14596 $ $Date: 2009-01-19 08:12:38 +0100 (Mon, 19 Jan 2009) $
027     */
028    public abstract class MCRFilesystemNode {
029        protected static MCRFileMetadataManager manager = MCRFileMetadataManager.instance();
030    
031        public static MCRFilesystemNode getNode(String ID) {
032            if (ID == null || ID.trim().length() == 0) {
033                throw new MCRUsageException("ID is an empty String or null");
034            }
035    
036            return manager.retrieveNode(ID);
037        }
038    
039        public static MCRFilesystemNode getRootNode(String ownerID) {
040            if (ownerID == null || ownerID.trim().length() == 0) {
041                throw new MCRUsageException("owner ID is an empty String or null");
042            }
043    
044            return manager.retrieveRootNode(ownerID);
045        }
046    
047        protected String ID;
048    
049        /** The ID of the node owner, e .g. a MILESS derivate ID */
050        protected String ownerID;
051    
052        /** The ID of the parent directory, if any */
053        protected String parentID;
054    
055        /** The name of this node */
056        protected String name;
057    
058        /** The optional label of this node */
059        protected String label;
060    
061        /** The size in number of bytes */
062        protected long size;
063    
064        /** The date of last modification of this node */
065        protected GregorianCalendar lastModified;
066    
067        /** A flag indicating if this node is deleted and therefore invalid */
068        protected boolean deleted = false;
069    
070        protected MCRFilesystemNode(String name, String ownerID) {
071            this(name, null, ownerID);
072        }
073    
074        protected MCRFilesystemNode(String name, MCRDirectory parent) {
075            this(name, parent.ID, parent.ownerID);
076        }
077    
078        protected MCRFilesystemNode(String name, MCRDirectory parent, boolean checkName) {
079            this(name, parent.ID, parent.ownerID, checkName);
080        }
081    
082        private MCRFilesystemNode(String name, String parentID, String ownerID) {
083            this(name, parentID, ownerID, true);
084        }
085    
086        private MCRFilesystemNode(String name, String parentID, String ownerID, boolean doExistCheck) {
087            if (ownerID == null || ownerID.trim().length() == 0) {
088                throw new MCRUsageException("owner ID is an empty String or null");
089            }
090    
091            this.ID = manager.createNodeID();
092            this.parentID = parentID;
093            this.ownerID = ownerID;
094            this.size = 0;
095            this.lastModified = new GregorianCalendar();
096            this.label = null;
097            checkName(name, doExistCheck);
098            this.name = name;
099        }
100    
101        protected MCRFilesystemNode(String ID, String parentID, String ownerID, String name, String label, long size, GregorianCalendar date) {
102            this.ID = ID;
103            this.parentID = parentID;
104            this.ownerID = ownerID;
105            this.name = name;
106            this.label = label;
107            this.size = size;
108            this.lastModified = date;
109            this.deleted = false;
110        }
111    
112        protected void storeNew() {
113            manager.storeNode(this);
114    
115            if (hasParent()) {
116                getParent().addChild(this);
117            }
118        }
119    
120        public void delete() {
121            this.removeAllAdditionalData();
122            manager.deleteNode(ID);
123    
124            if (parentID != null) {
125                getParent().removeChild(this);
126            }
127    
128            this.ID = null;
129            this.ownerID = null;
130            this.name = null;
131            this.label = null;
132            this.size = 0;
133            this.lastModified = null;
134            this.parentID = null;
135            this.deleted = true;
136        }
137    
138        /*
139         * protected void checkName(String name, boolean doExistCheck) {
140         * MCRArgumentChecker.ensureNotEmpty(name, "name");
141         * 
142         * boolean error = (name.indexOf("/") + name.indexOf("\\")) != -2; String
143         * errorMsg =
144         * "Filesystem node name must not contain '\' or '/' characters: " + name;
145         * MCRArgumentChecker.ensureIsFalse(error, errorMsg);
146         * 
147         * if (hasParent() && doExistCheck) { boolean exists =
148         * getParent().hasChild(name); String existsMsg =
149         * "A node with this name already exists: " + name;
150         * MCRArgumentChecker.ensureIsFalse(exists, existsMsg); } }
151         */
152    
153        /**
154         * Changed method because of problems with update of files.
155         * 
156         * @author Stefan Freitag
157         * 
158         */
159        protected void checkName(String name, boolean doExistCheck) {
160    
161            if (name == null)
162                throw new MCRUsageException(name + " is null.");
163    
164            boolean error = (name.indexOf("/") + name.indexOf("\\")) != -2;
165            String errorMsg = "Filesystem node name must not contain '\' or '/' characters: " + name;
166            if (error)
167                throw new MCRUsageException(errorMsg);
168    
169            if (hasParent() && doExistCheck) {
170                boolean exists = getParent().hasChild(name);
171                if (exists) {
172                    getParent().getChild(name).delete();
173                    System.out.println(name + " exists already, file was deleted...");
174                    checkName(name, true);
175                }
176            }
177        }
178    
179        public String getID() {
180            return ID;
181        }
182    
183        /**
184         * Returns the ID of the owner of this node
185         * 
186         * @return the ID of the owner of this node
187         */
188        public String getOwnerID() {
189            ensureNotDeleted();
190    
191            return ownerID;
192        }
193    
194        public String getParentID() {
195            return parentID;
196        }
197    
198        public MCRDirectory getParent() {
199            ensureNotDeleted();
200    
201            if (!hasParent()) {
202                return null;
203            }
204            return MCRDirectory.getDirectory(parentID);
205        }
206    
207        public boolean hasParent() {
208            ensureNotDeleted();
209    
210            return (parentID != null);
211        }
212    
213        public MCRDirectory getRootDirectory() {
214            ensureNotDeleted();
215    
216            if (hasParent()) {
217                return getParent().getRootDirectory();
218            } else if (this instanceof MCRDirectory) {
219                return (MCRDirectory) this;
220            } else {
221                return null;
222            }
223        }
224    
225        protected void ensureNotDeleted() {
226            if (deleted) {
227                throw new MCRUsageException("Do not use this node, it is deleted");
228            }
229        }
230    
231        /**
232         * Sets the name of this node
233         */
234        public void setName(String name) {
235            ensureNotDeleted();
236    
237            if (this.name.equals(name)) {
238                return;
239            }
240    
241            checkName(name, true);
242            this.name = name;
243            this.lastModified = new GregorianCalendar();
244    
245            manager.storeNode(this);
246    
247            if (parentID != null) {
248                getParent().touch();
249            }
250        }
251    
252        /**
253         * Returns the name of this node
254         * 
255         * @return the name of this node
256         */
257        public String getName() {
258            ensureNotDeleted();
259    
260            return name;
261        }
262    
263        /**
264         * Sets the label of this node
265         * 
266         * @param label
267         *            the label (may be null)
268         */
269        public void setLabel(String label) {
270            ensureNotDeleted();
271    
272            if ((label != null) && (label.trim().length() == 0)) {
273                label = null;
274            }
275    
276            if (this.label.equals(label)) {
277                return;
278            }
279    
280            this.label = label;
281            this.lastModified = new GregorianCalendar();
282    
283            manager.storeNode(this);
284    
285            if (parentID != null) {
286                getParent().touch();
287            }
288        }
289    
290        /**
291         * Returns the label of this node
292         * 
293         * @return the label of this node, or null
294         */
295        public String getLabel() {
296            ensureNotDeleted();
297    
298            return label;
299        }
300    
301        public String getPath() {
302            ensureNotDeleted();
303    
304            if (hasParent()) {
305                return getParent().getPath() + "/" + name;
306            }
307            return name;
308        }
309    
310        public String getAbsolutePath() {
311            ensureNotDeleted();
312    
313            if (hasParent()) {
314                String path = getParent().getAbsolutePath();
315    
316                if (!path.endsWith("/")) {
317                    path += "/";
318                }
319    
320                return path + name;
321            }
322            return "/";
323        }
324    
325        /**
326         * Returns the node size as number of bytes
327         */
328        public long getSize() {
329            ensureNotDeleted();
330    
331            return size;
332        }
333    
334        /**
335         * Returns the node size, formatted as a string
336         */
337        public String getSizeFormatted() {
338            ensureNotDeleted();
339    
340            return getSizeFormatted(size);
341        }
342    
343        /**
344         * Takes a file size in bytes and formats it as a string for output. For
345         * values < 5 KB the output format is for example "320 Byte". For values
346         * > 5 KB the output format is for example "6,8 KB". For values > 1 MB
347         * the output format is for example "3,45 MB".
348         */
349        public static String getSizeFormatted(long bytes) {
350            String sizeUnit;
351            String sizeText;
352            double sizeValue;
353    
354            if (bytes >= (1024 * 1024)) // >= 1 MB
355            {
356                sizeUnit = "MB";
357                sizeValue = (double) (Math.round(bytes / 10485.76)) / 100;
358            } else if (bytes >= (5 * 1024)) // >= 5 KB
359            {
360                sizeUnit = "KB";
361                sizeValue = (double) (Math.round(bytes / 102.4)) / 10;
362            } else // < 5 KB
363            {
364                sizeUnit = "Byte";
365                sizeValue = bytes;
366            }
367    
368            sizeText = String.valueOf(sizeValue).replace('.', ',');
369    
370            if (sizeText.endsWith(",0")) {
371                sizeText = sizeText.substring(0, sizeText.length() - 2);
372            }
373    
374            return sizeText + " " + sizeUnit;
375        }
376    
377        /**
378         * Returns the time of last modification of this node
379         */
380        public GregorianCalendar getLastModified() {
381            ensureNotDeleted();
382    
383            return lastModified;
384        }
385    
386        /**
387         * Stores additional XML data for this node. The name of the data element is
388         * used as unique key for storing data. If data with this name already
389         * exists, it is overwritten.
390         * 
391         * @param data
392         *            the additional XML data to be saved
393         * @throws IOException
394         *             if the XML data can not be retrieved
395         * @throws JDOMException
396         *             if the XML data can not be parsed
397         */
398        public void setAdditionalData(Element data) throws IOException, JDOMException {
399            MCRFile dataFile = MCRFile.getRootFile(this.ID);
400            Document doc;
401            if (dataFile == null) {
402                String name = "MCRFilesystemNode.additionalData";
403                dataFile = new MCRFile(name, this.ID);
404                doc = new Document(new Element("additionalData"));
405            } else
406                doc = dataFile.getContentAsJDOM();
407    
408            Element child = doc.getRootElement().getChild(data.getName());
409            if (child != null)
410                child.detach();
411            doc.getRootElement().addContent((Element) (data.clone()));
412            dataFile.setContentFrom(doc);
413        }
414    
415        /**
416         * Removes additional XML data from this node.
417         * 
418         * @param dataName
419         *            the name of the additional XML data element to be removed
420         * @throws IOException
421         *             if the XML data can not be retrieved
422         * @throws JDOMException
423         *             if the XML data can not be parsed
424         */
425        public void removeAdditionalData(String dataName) throws IOException, JDOMException {
426            MCRFile dataFile = MCRFile.getRootFile(this.ID);
427            if (dataFile == null)
428                return;
429            Document doc = dataFile.getContentAsJDOM();
430            Element child = doc.getRootElement().getChild(dataName);
431            if (child != null)
432                child.detach();
433            if (doc.getRootElement().getChildren().size() == 0)
434                dataFile.delete();
435            else
436                dataFile.setContentFrom(doc);
437        }
438    
439        /**
440         * Removes all additional XML data stored for this node, if any.
441         */
442        public void removeAllAdditionalData() {
443            MCRFile dataFile = MCRFile.getRootFile(this.ID);
444            if (dataFile != null)
445                dataFile.delete();
446        }
447    
448        /**
449         * Gets additional XML data stored for this node, if any.
450         * 
451         * @param dataName
452         *            the name of the additional XML data element to be retrieved
453         * @return the additional XML data elemet that was stored, or null
454         * @throws IOException
455         *             if the XML data can not be retrieved
456         * @throws JDOMException
457         *             if the XML data can not be parsed
458         */
459        public Element getAdditionalData(String dataName) throws IOException, JDOMException {
460            MCRFile dataFile = MCRFile.getRootFile(this.ID);
461            if ((dataFile == null) || (dataFile.getSize() == 0))
462                return null;
463            Document doc = dataFile.getContentAsJDOM();
464            return doc.getRootElement().getChild(dataName);
465        }
466    
467        /**
468         * Gets all additional XML data stored for this node, if any.
469         * 
470         * @return the additional XML data document that was stored, or null
471         * @throws IOException
472         *             if the XML data can not be retrieved
473         * @throws JDOMException
474         *             if the XML data can not be parsed
475         */
476        public Document getAllAdditionalData() throws IOException, JDOMException {
477            MCRFile dataFile = MCRFile.getRootFile(this.ID);
478            if ((dataFile == null) || (dataFile.getSize() == 0))
479                return null;
480            else
481                return dataFile.getContentAsJDOM();
482        }
483    
484        protected static DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss_SSS");
485    
486        public String toString() {
487            String date = formatter.format(lastModified.getTime());
488    
489            StringBuffer sb = new StringBuffer();
490            sb.append("ID          = ").append(this.ID).append("\n");
491            sb.append("Name        = ").append(this.name).append("\n");
492            sb.append("Label       = ").append(this.label).append("\n");
493            sb.append("Type        = ").append(this.getClass().getName()).append("\n");
494            sb.append("ParentID    = ").append(this.parentID).append("\n");
495            sb.append("OwnerID     = ").append(this.ownerID).append("\n");
496            sb.append("Size        = ").append(this.size).append("\n");
497            sb.append("Modified    = ").append(date).append("\n");
498    
499            return sb.toString();
500        }
501    }