1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.datamodel.ifs2;
20
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.nio.file.Files;
24 import java.nio.file.Path;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.List;
28 import java.util.Optional;
29 import java.util.function.Supplier;
30
31 import org.apache.logging.log4j.LogManager;
32 import org.apache.logging.log4j.Logger;
33 import org.jdom2.JDOMException;
34 import org.mycore.common.MCRUsageException;
35 import org.mycore.common.content.MCRByteContent;
36 import org.mycore.common.content.MCRContent;
37 import org.mycore.common.content.streams.MCRByteArrayOutputStream;
38 import org.tmatesoft.svn.core.ISVNLogEntryHandler;
39 import org.tmatesoft.svn.core.SVNCommitInfo;
40 import org.tmatesoft.svn.core.SVNDirEntry;
41 import org.tmatesoft.svn.core.SVNException;
42 import org.tmatesoft.svn.core.SVNLogEntry;
43 import org.tmatesoft.svn.core.SVNLogEntryPath;
44 import org.tmatesoft.svn.core.SVNNodeKind;
45 import org.tmatesoft.svn.core.SVNProperty;
46 import org.tmatesoft.svn.core.SVNPropertyValue;
47 import org.tmatesoft.svn.core.io.ISVNEditor;
48 import org.tmatesoft.svn.core.io.SVNRepository;
49 import org.tmatesoft.svn.core.io.diff.SVNDeltaGenerator;
50
51
52
53
54
55
56
57
58 public class MCRVersionedMetadata extends MCRStoredMetadata {
59
60
61
62
63 protected static final Logger LOGGER = LogManager.getLogger();
64
65
66
67
68
69 protected Supplier<Optional<Long>> revision;
70
71
72
73
74
75
76
77
78
79
80
81
82 MCRVersionedMetadata(MCRMetadataStore store, Path fo, int id, String docType, boolean deleted) {
83 super(store, fo, id, docType);
84 super.deleted = deleted;
85 revision = () -> {
86 try {
87
88 return Optional.ofNullable(Optional.ofNullable(getStore().getRepository().info(getFilePath(), -1))
89 .map(SVNDirEntry::getRevision).orElseGet(this::getLastRevision));
90 } catch (SVNException e) {
91 LOGGER.error("Could not get last revision of {}_{}", getStore().getID(), id, e);
92 return Optional.empty();
93 }
94 };
95 }
96
97 @Override
98 public MCRVersioningMetadataStore getStore() {
99 return (MCRVersioningMetadataStore) store;
100 }
101
102
103
104
105
106
107
108
109
110 @Override
111 void create(MCRContent xml) throws IOException, JDOMException {
112 super.create(xml);
113 commit("create");
114 }
115
116
117
118
119
120
121
122
123
124 @Override
125 public void update(MCRContent xml) throws IOException, JDOMException {
126 if (isDeleted() || isDeletedInRepository()) {
127 create(xml);
128 } else {
129 super.update(xml);
130 commit("update");
131 }
132 }
133
134 void commit(String mode) throws IOException {
135
136 SVNCommitInfo info;
137 try {
138 SVNRepository repository = getStore().getRepository();
139
140
141 String[] paths = store.getSlotPaths(id);
142 int existing = paths.length - 1;
143 for (; existing >= 0; existing--) {
144 if (!repository.checkPath(paths[existing], -1).equals(SVNNodeKind.NONE)) {
145 break;
146 }
147 }
148
149 existing += 1;
150
151
152 String commitMsg = mode + "d metadata object " + store.getID() + "_" + id + " in store";
153 ISVNEditor editor = repository.getCommitEditor(commitMsg, null);
154 editor.openRoot(-1);
155
156
157 for (int i = existing; i < paths.length - 1; i++) {
158 LOGGER.debug("SVN create directory {}", paths[i]);
159 editor.addDir(paths[i], null, -1);
160 editor.closeDir();
161 }
162
163
164 String filePath = paths[paths.length - 1];
165 if (existing < paths.length) {
166 editor.addFile(filePath, null, -1);
167 } else {
168 editor.openFile(filePath, -1);
169 }
170
171 editor.applyTextDelta(filePath, null);
172 SVNDeltaGenerator deltaGenerator = new SVNDeltaGenerator();
173
174 String checksum;
175 try (InputStream in = Files.newInputStream(path)) {
176 checksum = deltaGenerator.sendDelta(filePath, in, editor, true);
177 }
178
179 if (store.shouldForceXML()) {
180 editor.changeFileProperty(filePath, SVNProperty.MIME_TYPE, SVNPropertyValue.create("text/xml"));
181 }
182
183 editor.closeFile(filePath, checksum);
184 editor.closeDir();
185
186 info = editor.closeEdit();
187 } catch (SVNException e) {
188 throw new IOException(e);
189 }
190 revision = () -> Optional.of(info.getNewRevision());
191 LOGGER.info("SVN commit of {} finished, new revision {}", mode, getRevision());
192
193 if (MCRVersioningMetadataStore.shouldSyncLastModifiedOnSVNCommit()) {
194 setLastModified(info.getDate());
195 }
196 }
197
198
199
200
201
202 @Override
203 public void delete() throws IOException {
204 if (isDeleted()) {
205 String msg = "You can not delete already deleted data: " + id;
206 throw new MCRUsageException(msg);
207 }
208 if (!store.exists(id)) {
209 throw new IOException("Does not exist: " + store.getID() + "_" + id);
210 }
211 getStore().delete(getID());
212 deleted = true;
213 }
214
215 public boolean isDeletedInRepository() throws IOException {
216 long rev = getRevision();
217 return rev >= 0 && getRevision(rev).getType() == MCRMetadataVersion.DELETED;
218 }
219
220
221
222
223
224 public void update() throws Exception {
225 SVNRepository repository = getStore().getRepository();
226 MCRByteArrayOutputStream baos = new MCRByteArrayOutputStream();
227 long rev = repository.getFile(getFilePath(), -1, null, baos);
228 revision = () -> Optional.of(rev);
229 baos.close();
230 new MCRByteContent(baos.getBuffer(), 0, baos.size(), this.getLastModified().getTime()).sendTo(path);
231 }
232
233
234
235
236
237
238
239 @SuppressWarnings("unchecked")
240 public List<MCRMetadataVersion> listVersions() throws IOException {
241 try {
242 List<MCRMetadataVersion> versions = new ArrayList<>();
243 SVNRepository repository = getStore().getRepository();
244 String path = getFilePath();
245 String dir = getDirectory();
246
247 Collection<SVNLogEntry> entries = null;
248 try {
249 entries = repository.log(new String[] { dir }, null, 0, repository.getLatestRevision(), true, true);
250 } catch (Exception ioex) {
251 LOGGER.error("Could not get versions", ioex);
252 return versions;
253 }
254
255 for (SVNLogEntry entry : entries) {
256 SVNLogEntryPath svnLogEntryPath = entry.getChangedPaths().get(path);
257 if (svnLogEntryPath != null) {
258 char type = svnLogEntryPath.getType();
259 versions.add(new MCRMetadataVersion(this, Long.toString(entry.getRevision()), entry.getAuthor(),
260 entry.getDate(), type));
261 }
262 }
263 return versions;
264 } catch (SVNException svnExc) {
265 throw new IOException(svnExc);
266 }
267 }
268
269 private String getFilePath() {
270 return "/" + store.getSlotPath(id);
271 }
272
273 private String getDirectory() {
274 String path = getFilePath();
275 return path.substring(0, path.lastIndexOf('/'));
276 }
277
278 public MCRMetadataVersion getRevision(long revision) throws IOException {
279 try {
280 if (revision < 0) {
281 revision = getLastPresentRevision();
282 if (revision < 0) {
283 LOGGER.warn("Metadata object {} in store {} has no last revision!", getID(), getStore().getID());
284 return null;
285 }
286 }
287 SVNRepository repository = getStore().getRepository();
288 String path = getFilePath();
289 String dir = getDirectory();
290 @SuppressWarnings("unchecked")
291 Collection<SVNLogEntry> log = repository.log(new String[] { dir }, null, revision, revision, true, true);
292 for (SVNLogEntry logEntry : log) {
293 SVNLogEntryPath svnLogEntryPath = logEntry.getChangedPaths().get(path);
294 if (svnLogEntryPath != null) {
295 char type = svnLogEntryPath.getType();
296 return new MCRMetadataVersion(this, Long.toString(logEntry.getRevision()), logEntry.getAuthor(),
297 logEntry.getDate(), type);
298 }
299 }
300 LOGGER.warn("Metadata object {} in store {} has no revision ''{}''!", getID(), getStore().getID(),
301 getRevision());
302 return null;
303 } catch (SVNException svnExc) {
304 throw new IOException(svnExc);
305 }
306 }
307
308 public long getLastPresentRevision() throws SVNException {
309 return getLastRevision(false);
310 }
311
312 private long getLastRevision(boolean deleted) throws SVNException {
313 SVNRepository repository = getStore().getRepository();
314 if (repository.getLatestRevision() == 0) {
315
316 return -1;
317 }
318 final String path = getFilePath();
319 String dir = getDirectory();
320 LastRevisionLogHandler lastRevisionLogHandler = new LastRevisionLogHandler(path, deleted);
321 int limit = 0;
322 try {
323 repository.log(new String[] { dir }, repository.getLatestRevision(), 0, true, true, limit, false, null,
324 lastRevisionLogHandler);
325 } catch (LastRevisionFoundException ignored) {
326 }
327 return lastRevisionLogHandler.getLastRevision();
328 }
329
330 private Long getLastRevision() {
331 try {
332 long lastRevision = getLastRevision(true);
333 return lastRevision < 0 ? null : lastRevision;
334 } catch (SVNException e) {
335 LOGGER.warn("Could not get last revision of: {}_{}", getStore(), id, e);
336 return null;
337 }
338 }
339
340
341
342
343
344
345
346 public long getRevision() {
347 return revision.get().orElse(-1L);
348 }
349
350
351
352
353
354
355
356 public boolean isUpToDate() throws IOException {
357 SVNDirEntry entry;
358 try {
359 SVNRepository repository = getStore().getRepository();
360 entry = repository.info(getFilePath(), -1);
361 } catch (SVNException e) {
362 throw new IOException(e);
363 }
364 return entry.getRevision() <= getRevision();
365 }
366
367 private static final class LastRevisionFoundException extends RuntimeException {
368 private static final long serialVersionUID = 1L;
369 }
370
371 private static final class LastRevisionLogHandler implements ISVNLogEntryHandler {
372 private final String path;
373
374 long lastRevision = -1;
375
376 private boolean deleted;
377
378 private LastRevisionLogHandler(String path, boolean deleted) {
379 this.path = path;
380 this.deleted = deleted;
381 }
382
383 @Override
384 public void handleLogEntry(SVNLogEntry logEntry) throws SVNException {
385 SVNLogEntryPath svnLogEntryPath = logEntry.getChangedPaths().get(path);
386 if (svnLogEntryPath != null) {
387 char type = svnLogEntryPath.getType();
388 if (deleted || type != SVNLogEntryPath.TYPE_DELETED) {
389 lastRevision = logEntry.getRevision();
390
391 throw new LastRevisionFoundException();
392 }
393 }
394 }
395
396 long getLastRevision() {
397 return lastRevision;
398 }
399 }
400
401 }