001    /*
002     * $Revision: 15008 $ 
003     * $Date: 2009-03-25 10:44:36 +0100 (Wed, 25 Mar 2009) $
004     *
005     * This file is part of ***  M y C o R e  ***
006     * See http://www.mycore.de/ for details.
007     *
008     * This program is free software; you can use it, redistribute it
009     * and / or modify it under the terms of the GNU General Public License
010     * (GPL) as published by the Free Software Foundation; either version 2
011     * of the License or (at your option) any later version.
012     *
013     * This program is distributed in the hope that it will be useful, but
014     * WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program, in a file called gpl.txt or license.txt.
020     * If not, write to the Free Software Foundation Inc.,
021     * 59 Temple Place - Suite 330, Boston, MA  02111-1307 USA
022     */
023    
024    package org.mycore.datamodel.ifs2;
025    
026    import java.io.ByteArrayOutputStream;
027    import java.io.InputStream;
028    import java.util.ArrayList;
029    import java.util.Collection;
030    import java.util.List;
031    import java.util.Map;
032    
033    import org.apache.commons.vfs.FileObject;
034    import org.apache.log4j.Logger;
035    import org.mycore.common.MCRUsageException;
036    import org.tmatesoft.svn.core.SVNCommitInfo;
037    import org.tmatesoft.svn.core.SVNDirEntry;
038    import org.tmatesoft.svn.core.SVNLogEntry;
039    import org.tmatesoft.svn.core.SVNLogEntryPath;
040    import org.tmatesoft.svn.core.SVNNodeKind;
041    import org.tmatesoft.svn.core.SVNProperty;
042    import org.tmatesoft.svn.core.SVNPropertyValue;
043    import org.tmatesoft.svn.core.io.ISVNEditor;
044    import org.tmatesoft.svn.core.io.SVNRepository;
045    import org.tmatesoft.svn.core.io.diff.SVNDeltaGenerator;
046    
047    /**
048     * Represents an XML metadata document that is stored in a local filesystem
049     * store and in parallel in a Subversion repository to track and restore
050     * changes.
051     * 
052     * @author Frank Lützenkirchen
053     */
054    public class MCRVersionedMetadata extends MCRStoredMetadata {
055    
056        /**
057         * The logger
058         */
059        protected final static Logger LOGGER = Logger.getLogger(MCRVersionedMetadata.class);
060    
061        /**
062         * The revision number of the metadata version that is currently in the
063         * local filesystem store.
064         */
065        protected long revision;
066    
067        /**
068         * Creates a new metadata object both in the local store and in the SVN
069         * repository
070         * 
071         * @param store
072         *            the store this object is stored in
073         * @param fo
074         *            the file storing the data in the local filesystem
075         * @param id
076         *            the id of the metadata object
077         */
078        MCRVersionedMetadata(MCRMetadataStore store, FileObject fo, int id) {
079            super(store, fo, id);
080            // TODO: set revision of existing data at retrieve()
081        }
082    
083        public MCRVersioningMetadataStore getStore() {
084            return (MCRVersioningMetadataStore) store;
085        }
086    
087        /**
088         * Stores a new metadata object first in the SVN repository, then
089         * additionally in the local store.
090         * 
091         * @param xml
092         *            the metadata document to store
093         */
094        void create(MCRContent xml) throws Exception {
095            super.create(xml);
096            commit("create");
097        }
098    
099        /**
100         * Updates this metadata object, first in the SVN repository and then in the
101         * local store
102         * 
103         * @param xml
104         *            the new version of the document metadata
105         */
106        public void update(MCRContent xml) throws Exception {
107            if (isDeleted())
108                create(xml);
109            else {
110                super.update(xml);
111                commit("update");
112            }
113        }
114    
115        void commit(String mode) throws Exception {
116            SVNRepository repository = getStore().getRepository();
117    
118            // Check which paths already exist in SVN
119            String[] paths = store.getSlotPaths(id);
120            int existing = paths.length - 1;
121            for (; existing >= 0; existing--)
122                if (!repository.checkPath(paths[existing], -1).equals(SVNNodeKind.NONE))
123                    break;
124    
125            existing += 1;
126    
127            // Start commit editor
128            String commitMsg = mode + "d metadata object " + store.getID() + "_" + id + " in store";
129            ISVNEditor editor = repository.getCommitEditor(commitMsg, null);
130            editor.openRoot(-1);
131    
132            // Create directories in SVN that do not exist yet
133            for (int i = existing; i < paths.length - 1; i++) {
134                LOGGER.debug("SVN create directory " + paths[i]);
135                editor.addDir(paths[i], null, -1);
136                editor.closeDir();
137            }
138    
139            // Commit file changes
140            String filePath = paths[paths.length - 1];
141            if (existing < paths.length)
142                editor.addFile(filePath, null, -1);
143            else
144                editor.openFile(filePath, -1);
145    
146            editor.applyTextDelta(filePath, null);
147            SVNDeltaGenerator deltaGenerator = new SVNDeltaGenerator();
148    
149            InputStream in = fo.getContent().getInputStream();
150            String checksum = deltaGenerator.sendDelta(filePath, in, editor, true);
151            in.close();
152    
153            if (store.shouldForceXML())
154                editor.changeFileProperty(filePath, SVNProperty.MIME_TYPE, SVNPropertyValue.create("text/xml"));
155    
156            editor.closeFile(filePath, checksum);
157            editor.closeDir(); // root
158    
159            // Commit to SVN
160            SVNCommitInfo info = editor.closeEdit();
161            this.revision = info.getNewRevision();
162            LOGGER.info("SVN commit of " + mode + " finished, new revision " + revision);
163    
164            setLastModified(info.getDate());
165        }
166    
167        /**
168         * Deletes this metadata object in the SVN repository, and in the local
169         * store.
170         */
171        public void delete() throws Exception {
172            if (isDeleted()) {
173                String msg = "You can not delete already deleted data: " + id;
174                throw new MCRUsageException(msg);
175            }
176            String commitMsg = "Deleted metadata object " + store.getID() + "_" + id + " in store";
177    
178            SVNRepository repository = getStore().getRepository();
179            ISVNEditor editor = repository.getCommitEditor(commitMsg, null);
180            editor.openRoot(-1);
181            editor.deleteEntry(store.getSlotPath(id), -1);
182            editor.closeDir();
183    
184            // Commit to SVN
185            SVNCommitInfo info = editor.closeEdit();
186            this.revision = info.getNewRevision();
187            LOGGER.info("SVN commit of delete finished, new revision " + revision);
188    
189            store.delete(fo);
190        }
191    
192        /**
193         * Updates the version stored in the local filesystem to the latest version
194         * from Subversion repository HEAD.
195         */
196        public void update() throws Exception {
197            SVNRepository repository = getStore().getRepository();
198            ByteArrayOutputStream baos = new ByteArrayOutputStream();
199            revision = repository.getFile(store.getSlotPath(id), -1, null, baos);
200            baos.close();
201            MCRContent.readFrom(baos.toByteArray()).sendTo(fo);
202        }
203    
204        /**
205         * Lists all versions of this metadata object available in the subversion
206         * repository
207         * 
208         * @return all stored versions of this metadata object
209         */
210        public List<MCRMetadataVersion> listVersions() throws Exception {
211            List<MCRMetadataVersion> versions = new ArrayList<MCRMetadataVersion>();
212            SVNRepository repository = getStore().getRepository();
213            String path = store.getSlotPath(id);
214    
215            String dir = (path.contains("/") ? path.substring(0, path.lastIndexOf('/')) : "");
216            Collection<SVNLogEntry> entries = (Collection<SVNLogEntry>) (repository.log(new String[] { dir }, null, 0, -1, true, true));
217    
218            path = "/" + path;
219            for (SVNLogEntry entry : entries) {
220                Map<String, SVNLogEntryPath> paths = (Map<String, SVNLogEntryPath>)(entry.getChangedPaths());
221                if (paths.containsKey(path)) {
222                    char type = paths.get(path).getType();
223                    versions.add(new MCRMetadataVersion(this, entry, type));
224                }
225            }
226            return versions;
227        }
228    
229        /**
230         * Returns the revision number of the version currently stored in the local
231         * filesystem store.
232         * 
233         * @return the revision number of the local version
234         */
235        public long getRevision() {
236            return revision;
237        }
238    
239        /**
240         * Checks if the version in the local store is up to date with the latest
241         * version in SVN repository
242         * 
243         * @return true, if the local version in store is the latest version
244         */
245        public boolean isUpToDate() throws Exception {
246            SVNRepository repository = getStore().getRepository();
247            SVNDirEntry entry = repository.info(store.getSlotPath(id), -1);
248            return (entry.getRevision() <= revision);
249        }
250    }