OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 144 | Rev 151 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
144 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.erp.core.sales.pos.model;
15
 
16
import org.openconcerto.erp.core.sales.pos.POSConfiguration;
17
import org.openconcerto.erp.core.sales.pos.model.RegisterLog.EventType;
18
import org.openconcerto.erp.core.sales.pos.model.RegisterLogEntry.ReceiptEntry;
19
import org.openconcerto.erp.core.sales.pos.model.RegisterState.Status;
20
import org.openconcerto.utils.CompareUtils;
21
import org.openconcerto.utils.FileUtils;
22
import org.openconcerto.utils.MessageDigestUtils;
23
import org.openconcerto.utils.StringUtils;
149 ilm 24
import org.openconcerto.utils.TimeUtils;
144 ilm 25
import org.openconcerto.utils.cc.ExnTransformer;
26
import org.openconcerto.utils.checks.ValidState;
27
 
28
import java.io.BufferedInputStream;
29
import java.io.IOException;
30
import java.io.InputStream;
31
import java.io.RandomAccessFile;
32
import java.nio.file.DirectoryStream;
33
import java.nio.file.FileVisitResult;
34
import java.nio.file.Files;
35
import java.nio.file.LinkOption;
36
import java.nio.file.Path;
37
import java.nio.file.SimpleFileVisitor;
38
import java.nio.file.StandardCopyOption;
39
import java.nio.file.attribute.BasicFileAttributes;
40
import java.security.DigestInputStream;
41
import java.security.DigestOutputStream;
42
import java.sql.SQLException;
43
import java.text.ParseException;
44
import java.util.ArrayList;
45
import java.util.Arrays;
46
import java.util.Calendar;
47
import java.util.Collections;
48
import java.util.Comparator;
49
import java.util.Date;
50
import java.util.List;
51
import java.util.SortedSet;
52
import java.util.TreeSet;
53
import java.util.logging.Level;
54
import java.util.regex.Pattern;
55
 
56
import org.jdom2.Document;
57
import org.jdom2.Element;
58
import org.jdom2.JDOMException;
59
import org.jdom2.input.SAXBuilder;
60
import org.jdom2.output.Format;
61
import org.jdom2.output.XMLOutputter;
62
 
63
/**
64
 * <pre>
65
lockFile
66
2017
67
  12
68
    18/current or if not there previous
69
       log.xml
70
       log.xml.hash
71
       0218121700001.xml
72
       0218121700001.xml.hash
73
 * </pre>
74
 */
75
public class RegisterFiles {
76
 
77
    private static final String REGISTER_DIRNAME = "register";
78
    public static final String STRUCT_VERSION_2013 = "v20131206";
79
    public static final String STRUCT_VERSION = "v20171220";
80
 
81
    private static final String LOG_FILENAME = "log.xml";
82
    static final String HASH_SUFFIX = ".hash";
83
    private static final String LOG_HASH_FILENAME = LOG_FILENAME + HASH_SUFFIX;
84
 
85
    static private final Comparator<Path> FILENAME_COMPARATOR = new Comparator<Path>() {
86
        @Override
87
        public int compare(Path p1, Path p2) {
88
            return p1.getFileName().toString().compareTo(p2.getFileName().toString());
89
        }
90
    };
91
    static private final Comparator<Path> PATH_COMPARATOR = new Comparator<Path>() {
92
        @Override
93
        public int compare(Path p1, Path p2) {
94
            return p1.toString().compareTo(p2.toString());
95
        }
96
    };
97
 
98
    static private final Path getGreatestSubDir(final Path dir) throws IOException {
99
        Path res = null;
100
        if (dir != null && Files.exists(dir, LinkOption.NOFOLLOW_LINKS)) {
101
            try (final DirectoryStream<Path> subdirs = Files.newDirectoryStream(dir, FileUtils.DIR_PATH_FILTER)) {
102
                for (final Path subdir : subdirs) {
103
                    if (res == null || FILENAME_COMPARATOR.compare(subdir, res) > 0) {
104
                        res = subdir;
105
                    }
106
                }
107
            }
108
        }
109
        return res;
110
    }
111
 
112
    static private final ValidState canReadFile(final Path f, final String missingString, final String missingPermString) throws IOException {
113
        if (!Files.isRegularFile(f))
114
            return ValidState.createCached(false, missingString);
115
        if (!Files.isReadable(f))
116
            return ValidState.createCached(false, missingPermString);
117
        return ValidState.getTrueInstance();
118
    }
119
 
120
    static public final byte[] save(final Document doc, final Path f) throws IOException {
121
        final XMLOutputter out = new XMLOutputter(Format.getPrettyFormat());
122
        final byte[] res;
123
        try (final DigestOutputStream digestStream = new DigestOutputStream(Files.newOutputStream(f), MessageDigestUtils.getSHA256())) {
124
            out.output(doc, digestStream);
125
            res = digestStream.getMessageDigest().digest();
126
        }
127
        Files.write(f.resolveSibling(f.getFileName() + HASH_SUFFIX), MessageDigestUtils.asHex(res).getBytes(StringUtils.UTF8));
128
        return res;
129
    }
130
 
131
    static public final class HashMode {
132
 
133
        static public final HashMode NOT_REQUIRED = new HashMode(false, null);
134
        static public final HashMode REQUIRED = new HashMode(true, null);
135
 
136
        static public final HashMode equalTo(final String hashRequired) {
137
            return new HashMode(true, hashRequired);
138
        }
139
 
140
        private final boolean hashFileRequired;
141
        private final String hashRequired;
142
 
143
        private HashMode(boolean hashFileRequired, String hashRequired) {
144
            super();
145
            this.hashFileRequired = hashFileRequired;
146
            this.hashRequired = hashRequired;
147
        }
148
    }
149
 
150
    static public final Document parse(final Path f) throws IOException, JDOMException {
151
        return parse(f, HashMode.REQUIRED);
152
    }
153
 
154
    static public final Document parse(final Path f, final HashMode hashMode) throws IOException, JDOMException {
155
        final byte[] hash;
156
        final Path logHashFile = f.resolveSibling(f.getFileName() + HASH_SUFFIX);
157
        if (Files.isRegularFile(logHashFile)) {
158
            final String hashString = Files.readAllLines(logHashFile, StringUtils.UTF8).get(0);
159
            if (hashMode.hashRequired != null && !hashString.equals(hashMode.hashRequired))
160
                throw new IllegalStateException("Required hash doesn't match recorded hash");
161
            hash = MessageDigestUtils.fromHex(hashString);
162
            assert hash != null;
163
        } else if (hashMode.hashFileRequired) {
164
            throw new IllegalStateException("Missing required hash file for " + f);
165
        } else {
166
            hash = null;
167
        }
168
        final Document doc;
169
        try (final InputStream ins = new BufferedInputStream(Files.newInputStream(f, LinkOption.NOFOLLOW_LINKS));
170
                final DigestInputStream dIns = new DigestInputStream(ins, MessageDigestUtils.getSHA256())) {
171
            doc = new SAXBuilder().build(dIns);
172
            if (hash != null && !Arrays.equals(hash, dIns.getMessageDigest().digest()))
173
                throw new IOException("File hash doesn't match recorded hash for " + f);
174
        }
175
        return doc;
176
    }
177
 
178
    static private final Pattern DIGITS_PATTERN = Pattern.compile("[0-9]+");
179
 
180
    static public final List<RegisterFiles> scan(final Path rootDir) throws IOException {
181
        final Path registersDir = rootDir.resolve(REGISTER_DIRNAME);
182
        if (!Files.exists(registersDir))
183
            return Collections.emptyList();
184
        final List<RegisterFiles> res = new ArrayList<>();
185
        try (final DirectoryStream<Path> stream = Files.newDirectoryStream(registersDir, new DirectoryStream.Filter<Path>() {
186
            @Override
187
            public boolean accept(Path entry) throws IOException {
188
                return DIGITS_PATTERN.matcher(entry.getFileName().toString()).matches();
189
            }
190
        })) {
191
            for (final Path registerDir : stream) {
192
                if (Files.isDirectory(registerDir.resolve(STRUCT_VERSION))) {
193
                    res.add(new RegisterFiles(rootDir, true, Integer.parseInt(registerDir.getFileName().toString())));
194
                }
195
            }
196
        }
197
        return res;
198
    }
199
 
200
    // under that year/month/day
201
    private final Path rootDir;
202
    private final boolean useHardLinks;
203
    private final int posID;
204
    private final ThreadLocal<Boolean> hasLock = new ThreadLocal<Boolean>() {
205
        protected Boolean initialValue() {
206
            return Boolean.FALSE;
207
        };
208
    };
209
 
210
    public RegisterFiles(final Path rootDir, final boolean useHardLinks, final int caisse) {
211
        super();
212
        this.rootDir = rootDir.resolve(REGISTER_DIRNAME).resolve(Integer.toString(caisse));
213
        this.useHardLinks = useHardLinks;
214
        this.posID = caisse;
215
    }
216
 
217
    private final Path getRootDir() {
218
        return this.rootDir;
219
    }
220
 
221
    public final Path getVersionDir() {
222
        return this.getRootDir().resolve(STRUCT_VERSION);
223
    }
224
 
225
    public final Path getDayDir(final Calendar day, final boolean create) {
226
        return ReceiptCode.getDayDir(getVersionDir().toFile(), day, create).toPath();
227
    }
228
 
229
    public final Path getReceiptFile(final ReceiptCode code) throws IOException {
230
        final Path dayDirToUse = getDayDirToUse(getDayDir(code.getDay(), false));
231
        return dayDirToUse == null ? null : dayDirToUse.resolve(code.getFileName());
232
    }
233
 
149 ilm 234
    public final Path getLogFile(final Calendar day) throws IOException {
235
        return getLogFile(getDayDir(day, false));
236
    }
237
 
238
    private final Path getLogFile(final Path dayDir) throws IOException {
239
        final Path dayDirToUse = getDayDirToUse(dayDir);
240
        return dayDirToUse == null ? null : dayDirToUse.resolve(LOG_FILENAME);
241
    }
242
 
144 ilm 243
    public final int getPosID() {
244
        return this.posID;
245
    }
246
 
247
    public <T, Exn extends Exception> T doWithLock(final ExnTransformer<RegisterFiles, T, Exn> transf) throws IOException, Exn {
248
        if (this.hasLock.get())
249
            throw new IllegalStateException("Already locked");
250
        this.hasLock.set(Boolean.TRUE);
251
        try {
252
            return FileUtils.doWithLock(this.getRootDir().resolve("lockFile").toFile(), new ExnTransformer<RandomAccessFile, T, Exn>() {
253
                @Override
254
                public T transformChecked(RandomAccessFile input) throws Exn {
255
                    return transf.transformChecked(RegisterFiles.this);
256
                }
257
            });
258
        } finally {
259
            this.hasLock.set(Boolean.FALSE);
260
        }
261
    }
262
 
263
    public final RegisterLog getLastLog() throws IOException, JDOMException {
264
        final Path lastLogFile = this.findLastLogFile();
265
        if (lastLogFile == null)
266
            return null;
267
        return new RegisterLog(lastLogFile).parse();
268
    }
269
 
270
    public final Path findLastLogFile() throws IOException {
271
        final Path versionDir = this.getVersionDir();
272
 
273
        // first quick search
274
        final Path yearDir = getGreatestSubDir(versionDir);
275
        final Path monthDir = getGreatestSubDir(yearDir);
276
        if (monthDir != null) {
277
            final SortedSet<Path> sortedDays = new TreeSet<>(Collections.reverseOrder(FILENAME_COMPARATOR));
278
            try (final DirectoryStream<Path> dayDirs = Files.newDirectoryStream(monthDir, FileUtils.DIR_PATH_FILTER)) {
279
                for (final Path dayDir : dayDirs) {
280
                    sortedDays.add(dayDir);
281
                }
282
            }
283
            Path logToUse = getLogToUse(sortedDays);
284
            if (logToUse != null)
285
                return logToUse;
286
 
287
            // then walk the whole tree before giving up
288
            logToUse = getLogToUse(getSortedDays(false));
289
            if (logToUse != null)
290
                return logToUse;
291
        }
292
 
293
        return null;
294
    }
295
 
296
    private SortedSet<Path> getSortedDays(final boolean chronological) throws IOException {
297
        final Path versionDir = this.getVersionDir();
298
        final SortedSet<Path> sortedPaths = new TreeSet<>(chronological ? PATH_COMPARATOR : Collections.reverseOrder(PATH_COMPARATOR));
299
        if (Files.exists(versionDir)) {
300
            Files.walkFileTree(versionDir, new SimpleFileVisitor<Path>() {
301
                @Override
302
                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
303
                    final Path dateDir = versionDir.relativize(dir);
304
                    if (dateDir.getNameCount() == 3) {
305
                        sortedPaths.add(dir);
306
                        return FileVisitResult.SKIP_SUBTREE;
307
                    } else {
308
                        return FileVisitResult.CONTINUE;
309
                    }
310
                }
311
            });
312
        }
313
        return sortedPaths;
314
    }
315
 
316
    private Path getLogToUse(final SortedSet<Path> sortedDays) throws IOException {
317
        for (final Path dayDir : sortedDays) {
149 ilm 318
            final Path logFile = getLogFile(dayDir);
319
            if (logFile != null)
320
                return logFile;
144 ilm 321
        }
322
        return null;
323
    }
324
 
325
    public final List<Path> findLogFiles() throws IOException {
326
        final List<Path> res = new ArrayList<>();
327
        for (final Path dayDir : getSortedDays(true)) {
149 ilm 328
            final Path logFile = getLogFile(dayDir);
329
            if (logFile != null)
330
                res.add(logFile);
144 ilm 331
        }
332
        return res;
333
    }
334
 
335
    private final Path getDayDirToUse(final Path dayDir) throws IOException {
336
        for (final Path subdir : new Path[] { dayDir.resolve("current"), dayDir.resolve("previous") }) {
337
            if (Files.exists(subdir)) {
338
                final ValidState validity = getDayDirValidity(subdir);
339
                if (validity.isValid())
340
                    return subdir;
341
                else
342
                    throw new IOException("Invalid " + subdir + " : " + validity.getValidationText());
343
            }
344
        }
345
        return null;
346
    }
347
 
348
    private final ValidState getDayDirValidity(final Path dayDir) throws IOException {
349
        if (!Files.isDirectory(dayDir))
350
            return ValidState.createCached(false, "Not a directory");
351
        final ValidState canReadLog = canReadFile(dayDir.resolve(LOG_FILENAME), "Missing log file", "Unreadable log file");
352
        if (!canReadLog.isValid())
353
            return canReadLog;
354
        final ValidState canReadLogHash = canReadFile(dayDir.resolve(LOG_HASH_FILENAME), "Missing log hash file", "Unreadable log hash file");
355
        if (!canReadLogHash.isValid())
356
            return canReadLogHash;
357
        return ValidState.getTrueInstance();
358
    }
359
 
360
    public final RegisterLog open(final int userID, final DBState dbState) throws IOException {
361
        if (!this.hasLock.get())
362
            throw new IllegalStateException("Not locked");
363
        return createOpen(userID, null, dbState).transformChecked(this);
364
    }
365
 
366
    public final RegisterLog open(final int userID, final RegisterDB registerDB) throws IOException {
367
        return this.doWithLock(createOpen(userID, registerDB, null));
368
    }
369
 
370
    static private final ExnTransformer<RegisterFiles, RegisterLog, IOException> createOpen(final int userID, final RegisterDB registerDB, final DBState passedDBState) throws IOException {
371
        // TODO use UpdateDir like save() and close()
372
        return new ExnTransformer<RegisterFiles, RegisterLog, IOException>() {
373
            @Override
374
            public RegisterLog transformChecked(RegisterFiles input) throws IOException {
375
                POSConfiguration.getLogger().log(Level.FINE, "Begin opening of FS state for register {0}", input.getPosID());
376
                POSConfiguration.checkRegisterID(input.getPosID(), registerDB.getPosID());
377
                final RegisterLog lastLog = input.checkStatus(true);
378
                final String lastLocalHash;
149 ilm 379
                final Date prevDate;
144 ilm 380
                if (lastLog == null) {
381
                    lastLocalHash = null;
149 ilm 382
                    prevDate = null;
144 ilm 383
                } else {
384
                    try {
385
                        lastLocalHash = lastLog.getLastReceiptHash();
386
                    } catch (ParseException e) {
387
                        throw new IOException("Couldn't parse last receipt of log", e);
388
                    }
149 ilm 389
                    prevDate = lastLog.getFirstRegisterEvent().getDate();
390
                    if (lastLocalHash != null && prevDate == null)
391
                        throw new IOException("There's a receipt, but no previous closure date");
144 ilm 392
                }
393
 
394
                final DBState dbState;
395
                if (passedDBState == null) {
396
                    try {
397
                        dbState = registerDB.open(lastLocalHash, userID);
398
                    } catch (SQLException e) {
399
                        throw new IOException("Couldn't open the register in the DB", e);
400
                    }
401
                } else {
402
                    dbState = passedDBState;
403
                }
404
                if (dbState.getRegisterState().getStatus() != Status.OPEN)
405
                    throw new IllegalArgumentException("DB not open : " + dbState);
406
 
407
                final Calendar cal = dbState.getLastEntry().getDate("DATE");
408
 
409
                // e.g. 2017/12/21/
410
                final Path dayDir = input.getDayDir(cal, true);
411
                // e.g. 2017/12/21/current/
412
                final Path dayDirToUse = input.getDayDirToUse(dayDir);
413
                if (dayDirToUse != null)
414
                    throw new IllegalStateException(cal.getTime() + " already open");
415
 
416
                final Path stagingDir = dayDir.resolve("staging");
417
                final Path currentDir = stagingDir.resolveSibling("current");
418
                final Path prevDir = stagingDir.resolveSibling("previous");
419
                FileUtils.rm_R(stagingDir);
420
                FileUtils.rm_R(currentDir);
421
                FileUtils.rm_R(prevDir);
422
                Files.createDirectory(stagingDir);
423
 
149 ilm 424
                final Element rootElem = RegisterLog.createRootElement();
425
                rootElem.addContent(new RegisterLogEntry.RegisterEntry(EventType.REGISTER_OPENING, cal.getTime(), userID, input.getPosID(), lastLocalHash, prevDate).toXML());
144 ilm 426
                save(new Document(rootElem), stagingDir.resolve(LOG_FILENAME));
427
 
428
                Files.move(stagingDir, currentDir, StandardCopyOption.ATOMIC_MOVE);
429
 
430
                POSConfiguration.getLogger().log(Level.INFO, "Finished opening of FS state for register {0}", registerDB);
431
 
432
                // TODO parse and validate before moving into place
433
                try {
434
                    return new RegisterLog(currentDir.resolve(LOG_FILENAME)).parse();
435
                } catch (JDOMException e) {
436
                    throw new IOException("Couldn't parse new log");
437
                }
438
            }
439
        };
440
    }
441
 
442
    private abstract class UpdateDir<I, T> extends ExnTransformer<RegisterFiles, T, IOException> {
443
 
444
        private final String logMsg;
445
 
446
        public UpdateDir(final String logMsg) {
447
            this.logMsg = logMsg;
448
        }
449
 
450
        @Override
451
        public T transformChecked(RegisterFiles input) throws IOException {
452
            POSConfiguration.getLogger().log(Level.FINE, "Begin " + this.logMsg + " for register {0}", input.getPosID());
453
            final RegisterLog lastLog = checkStatus(needsClosed());
454
 
455
            // e.g. 2017/12/21/current/
456
            final Path toUse = lastLog.getLogFile().getParent();
457
            final Path stagingDir = toUse.resolveSibling("staging");
458
            final Path currentDir = stagingDir.resolveSibling("current");
459
            final Path prevDir = stagingDir.resolveSibling("previous");
460
 
461
            FileUtils.rm_R(stagingDir);
462
            FileUtils.copyDirectory(toUse, stagingDir, input.useHardLinks, StandardCopyOption.COPY_ATTRIBUTES);
463
 
464
            final I intermediateRes = updateDir(stagingDir, lastLog);
465
 
466
            if (Files.exists(currentDir)) {
467
                FileUtils.rm_R(prevDir);
468
                Files.move(currentDir, prevDir, StandardCopyOption.ATOMIC_MOVE);
469
            }
470
            assert !Files.exists(currentDir);
471
 
472
            Files.move(stagingDir, currentDir, StandardCopyOption.ATOMIC_MOVE);
473
 
474
            POSConfiguration.getLogger().log(Level.INFO, "Finished " + this.logMsg + " for register {0}", input.getPosID());
475
 
476
            assert Files.isDirectory(currentDir);
477
            try {
478
                FileUtils.rm_R(prevDir);
479
            } catch (Exception e) {
480
                // OK to leave behind some small files
481
                e.printStackTrace();
482
            }
483
 
484
            return createResult(currentDir, intermediateRes);
485
        }
486
 
487
        protected abstract boolean needsClosed();
488
 
489
        protected abstract I updateDir(final Path stagingDir, final RegisterLog lastLog) throws IOException;
490
 
491
        protected abstract T createResult(final Path currentDir, final I intermediateRes) throws IOException;
492
 
493
    }
494
 
495
    private final RegisterLog checkStatus(final boolean needsClosed) throws IOException {
496
        final RegisterLog lastLog;
497
        try {
498
            lastLog = getLastLog();
499
            final boolean closed = lastLog == null || lastLog.getLastRegisterEvent().getType() != EventType.REGISTER_OPENING;
500
            if (closed != needsClosed)
501
                throw new IllegalStateException(needsClosed ? "Not closed" : "Not open");
502
        } catch (JDOMException | ParseException e) {
503
            throw new IOException(e);
504
        }
505
        return lastLog;
506
    }
507
 
508
    public final RegisterLog close(final int userID) throws IOException {
509
        return this.doWithLock(new UpdateDir<Object, RegisterLog>("closure of FS state") {
510
            @Override
511
            protected boolean needsClosed() {
512
                return false;
513
            }
514
 
515
            @Override
516
            protected Object updateDir(final Path stagingDir, final RegisterLog lastLog) throws IOException {
517
                final String lastHash;
518
                try {
519
                    lastHash = lastLog.getLastReceiptHash();
520
                } catch (ParseException e) {
521
                    throw new IOException("Couldn't find last receipt hash", e);
522
                }
523
                // TODO verify that receipts' files match the log's content
524
                final Document doc = lastLog.getDocument().clone();
149 ilm 525
                doc.getRootElement().addContent(new RegisterLogEntry.RegisterEntry(EventType.REGISTER_CLOSURE, new Date(), userID, getPosID(), lastHash, null).toXML());
144 ilm 526
                save(doc, stagingDir.resolve(LOG_FILENAME));
527
                return null;
528
            }
529
 
530
            @Override
531
            protected RegisterLog createResult(final Path currentDir, final Object intermediateRes) throws IOException {
532
                // TODO parse and validate before moving into place
533
                try {
534
                    return new RegisterLog(currentDir.resolve(LOG_FILENAME)).parse();
535
                } catch (JDOMException e) {
536
                    throw new IOException("Couldn't parse new log");
537
                }
538
            }
539
        });
540
    }
541
 
149 ilm 542
    public static final class DifferentDayException extends IllegalStateException {
543
        protected DifferentDayException(final RegisterLog lastLog) {
544
            super("Cannot save a receipt for a different day than the register opening : " + lastLog.getFirstRegisterEvent());
545
        }
546
    }
547
 
144 ilm 548
    public final String save(final Ticket t) throws IOException, SQLException {
549
        return this.doWithLock(new UpdateDir<String, String>("saving receipt") {
550
            @Override
551
            protected boolean needsClosed() {
552
                return false;
553
            }
554
 
555
            @Override
556
            protected String updateDir(final Path stagingDir, final RegisterLog lastLog) throws IOException {
149 ilm 557
                if (!TimeUtils.isSameDay(t.getCreationCal(), lastLog.getFirstRegisterEvent().getDate()))
558
                    throw new DifferentDayException(lastLog);
144 ilm 559
                try {
560
                    final ReceiptEntry lastReceipt = lastLog.getLastReceiptCreationEvent();
561
                    final int expectedIndex;
562
                    final String expectedHash = lastLog.getLastReceiptHash();
563
                    if (lastReceipt == null) {
564
                        expectedIndex = 1;
565
                    } else {
566
                        expectedIndex = lastReceipt.getCode().getDayIndex() + 1;
567
                    }
568
                    if (t.getReceiptCode().getDayIndex() != expectedIndex)
569
                        throw new IllegalStateException("Non consecutive number");
570
                    if (!CompareUtils.equals(expectedHash, t.getPreviousHash()))
571
                        throw new IllegalStateException("Previous hash mismatch, expected " + expectedHash + " but previous of receipt was " + t.getPreviousHash());
572
                } catch (ParseException e) {
573
                    throw new IOException("Couldn't parse last receipt of log", e);
574
                }
575
                // save receipt
576
                final String fileHash = MessageDigestUtils.asHex(t.saveToFile(stagingDir.resolve(t.getReceiptCode().getFileName())));
577
                // update log
578
                final Document doc = lastLog.getDocument().clone();
579
                doc.getRootElement().addContent(new RegisterLogEntry.ReceiptEntry(t.getCreationDate(), t.getReceiptCode(), fileHash).toXML());
580
                save(doc, stagingDir.resolve(LOG_FILENAME));
581
                return fileHash;
582
            }
583
 
584
            @Override
585
            protected String createResult(final Path currentDir, final String intermediateRes) {
586
                return intermediateRes;
587
            }
588
        });
589
    }
590
}