Dépôt officiel du code source de l'ERP OpenConcerto
Rev 149 | Blame | Compare with Previous | Last modification | View Log | RSS feed
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.erp.core.sales.pos.model;
import org.openconcerto.erp.core.sales.pos.model.RegisterFiles.HashMode;
import org.openconcerto.erp.core.sales.pos.model.RegisterLogEntry.ReceiptEntry;
import org.openconcerto.erp.core.sales.pos.model.RegisterLogEntry.RegisterEntry;
import org.openconcerto.erp.core.sales.pos.model.RegisterState.Status;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.CompareUtils;
import java.io.IOException;
import java.nio.file.Path;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
/**
* To parse the log file for a register.
*
* @author sylvain
*/
public class RegisterLog {
// TODO RECEIPT_MODIFICATION
static public enum EventType {
REGISTER_OPENING, RECEIPT_CREATION, REGISTER_CLOSURE;
}
static private final int VERSION = 2;
static private final String VERSION_ATTR_NAME = "version";
static final Element createRootElement() {
return new Element("registerLog").setAttribute(VERSION_ATTR_NAME, String.valueOf(VERSION));
}
private final Path logFile;
private Document parsed;
private int version;
private RegisterEntry opening;
public RegisterLog(Path logFile) {
super();
this.logFile = logFile;
}
public final Path getLogFile() {
return this.logFile;
}
public final RegisterLog parse() throws IOException, JDOMException {
final Document doc = RegisterFiles.parse(this.logFile);
final List<Element> elems = doc.getRootElement().getChildren();
if (elems.isEmpty())
throw new IllegalStateException("Empty log");
final RegisterLogEntry firstEntry;
try {
firstEntry = RegisterLogEntry.parseEntry(elems.get(0));
} catch (ParseException e) {
throw new IOException(e);
}
if (firstEntry.getType() != EventType.REGISTER_OPENING)
throw new IllegalStateException("Do not begin with " + EventType.REGISTER_OPENING + " : " + firstEntry);
this.opening = (RegisterEntry) firstEntry;
this.parsed = doc;
this.version = Integer.parseInt(doc.getRootElement().getAttributeValue(VERSION_ATTR_NAME, "1"));
return this;
}
public final Document getDocument() {
return this.parsed;
}
public final int getVersion() {
return this.version;
}
public final RegisterLogEntry getLastEvent() throws ParseException {
return this.getEvent(-1);
}
public final RegisterLogEntry getEvent(final int index) throws ParseException {
final List<Element> elems = this.parsed.getRootElement().getChildren();
return RegisterLogEntry.parseEntry(elems.get(CollectionUtils.getValidIndex(elems, index, true)));
}
public final List<RegisterLogEntry> getAllEvents() throws ParseException {
final List<Element> elems = this.parsed.getRootElement().getChildren();
final List<RegisterLogEntry> res = new ArrayList<>();
for (final Element elem : elems)
res.add(RegisterLogEntry.parseEntry(elem));
return res;
}
public final String getLastReceiptHash() throws ParseException {
final ReceiptEntry lastReceiptCreationEvent = this.getLastReceiptCreationEvent();
if (lastReceiptCreationEvent != null)
return lastReceiptCreationEvent.getFileHash();
else
return this.getFirstRegisterEvent().getLastReceiptHash();
}
public final int getRegisterID() {
return this.getFirstRegisterEvent().getRegisterID();
}
public final RegisterEntry getFirstRegisterEvent() {
return this.opening;
}
public final RegisterEntry getLastRegisterEvent() throws ParseException {
final RegisterLogEntry lastEvent = this.getLastEvent();
if (lastEvent.getType() == EventType.REGISTER_CLOSURE)
return (RegisterEntry) lastEvent;
else
return this.opening;
}
public final RegisterState getRegisterState() throws ParseException {
final RegisterEntry lastRegisterEvent = this.getLastRegisterEvent();
return new RegisterState(lastRegisterEvent.getType() == EventType.REGISTER_OPENING ? Status.OPEN : Status.CLOSED, lastRegisterEvent.getDate());
}
public final List<ReceiptEntry> getReceiptEvents() throws ParseException {
final List<Element> elems = this.parsed.getRootElement().getChildren();
final int size = elems.size();
if (size <= 1)
return Collections.emptyList();
final List<ReceiptEntry> res = new ArrayList<>();
for (final Element elem : elems.subList(1, size)) {
final RegisterLogEntry parsedEntry = RegisterLogEntry.parseEntry(elem);
if (parsedEntry instanceof ReceiptEntry) {
res.add((ReceiptEntry) parsedEntry);
} else if (parsedEntry.getType() == EventType.REGISTER_CLOSURE) {
if (res.size() != size - 2)
throw new IllegalStateException("Closure not at the end : " + elems);
} else {
throw new IllegalStateException("Unexpected entry : " + parsedEntry);
}
}
return res;
}
/**
* Parse all receipts logged in this. This also validates that data recorded in this log matches
* the parsed receipts' files.
*
* @return the list of current receipts in this.
* @throws ParseException if an entry couldn't be parsed.
* @throws IOException if the receipts couldn't be parsed.
*/
public final List<Ticket> parseReceipts() throws ParseException, IOException {
final List<Ticket> receipts = new ArrayList<>();
final Path toUse = this.getLogFile().getParent();
final List<ReceiptEntry> receiptEvents = this.getReceiptEvents();
String lastHash = this.getFirstRegisterEvent().getLastReceiptHash();
int index = 1;
for (final ReceiptEntry receiptEntry : receiptEvents) {
final Path receiptFile = toUse.resolve(receiptEntry.getCode().getFileName());
final Ticket receipt;
try {
receipt = Ticket.parseFile(receiptFile.toFile(), HashMode.equalTo(receiptEntry.getFileHash()));
} catch (Exception e) {
throw new IOException("Couldn't parse file of " + receiptEntry + " : " + receiptFile, e);
}
assert receipt != null;
if (!receipt.getCode().equals(receiptEntry.getCodeString()))
throw new IllegalStateException("Code mismatch");
if (!CompareUtils.equals(lastHash, receipt.getPreviousHash()))
throw new IllegalStateException("Previous hash mismatch for " + receiptEntry.getCodeString());
lastHash = receiptEntry.getFileHash();
if (receipt.getNumber() != index)
throw new IllegalStateException("Invalid index, expected " + index + " got " + receipt.getNumber());
index++;
if (receipt.getCreationDate().compareTo(receiptEntry.getDate()) != 0)
throw new IllegalStateException("Date mismatch for " + receiptEntry.getCodeString() + " logged " + receiptEntry.getDate() + " but receipt " + receipt.getCreationDate());
receipts.add(receipt);
}
return receipts;
}
public final ReceiptEntry getLastReceiptCreationEvent() throws ParseException {
for (int i = -1; i >= -2; i--) {
final RegisterLogEntry event = this.getEvent(i);
if (event.getType() == EventType.REGISTER_OPENING) {
return null;
} else if (event.getType() == EventType.RECEIPT_CREATION) {
return (ReceiptEntry) event;
}
}
throw new IllegalStateException("2 " + EventType.REGISTER_CLOSURE);
}
@Override
public String toString() {
return this.getClass().getSimpleName() + " " + this.getLogFile() + " opened " + this.getFirstRegisterEvent().getDate();
}
}