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 }