package de.dhbw.humbuch.viewmodel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.hibernate.criterion.Restrictions;
import org.mozilla.universalchardet.UniversalDetector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import au.com.bytecode.opencsv.CSVReader;
import com.google.common.eventbus.EventBus;
import com.google.inject.Inject;
import de.davherrmann.mvvm.ActionHandler;
import de.davherrmann.mvvm.BasicState;
import de.davherrmann.mvvm.State;
import de.davherrmann.mvvm.annotations.AfterVMBinding;
import de.davherrmann.mvvm.annotations.HandlesAction;
import de.davherrmann.mvvm.annotations.ProvidesState;
import de.dhbw.humbuch.event.MessageEvent;
import de.dhbw.humbuch.event.MessageEvent.Type;
import de.dhbw.humbuch.model.DAO;
import de.dhbw.humbuch.model.entity.BorrowedMaterial;
import de.dhbw.humbuch.model.entity.Grade;
import de.dhbw.humbuch.model.entity.Student;
import de.dhbw.humbuch.util.CSVHandler;
import de.dhbw.humbuch.view.StudentInformationView;
/**
* Provides a {@link State} for all {@link Student}s.<br>
* Imports {@link Student}.
*
* @author David Vitt
* @author Benjamin Räthlein
* @author Johannes Idelhauser
*
*/
public class StudentInformationViewModel {
private final static Logger LOG = LoggerFactory.getLogger(StudentInformationView.class);
public interface PersistStudents extends ActionHandler {}
public interface Students extends State<Collection<Student>> {}
@ProvidesState(Students.class)
public State<Collection<Student>> students = new BasicState<>(Collection.class);
private EventBus eventBus;
private DAO<Student> daoStudent;
private DAO<Grade> daoGrade;
private DAO<BorrowedMaterial> daoBorrowedMaterial;
/**
* Constructor
*
* @param daoTeachingMaterial
* DAO implementation to access TeachingMaterial entities
*/
@Inject
public StudentInformationViewModel(DAO<Student> daoStudent, DAO<Grade> daoGrade, DAO<BorrowedMaterial> daoBorrowedMaterial,
EventBus eventBus) {
this.daoStudent = daoStudent;
this.daoGrade = daoGrade;
this.daoBorrowedMaterial = daoBorrowedMaterial;
this.eventBus = eventBus;
}
@AfterVMBinding
public void initialiseStates() {
students.set(new ArrayList<Student>());
}
public void refresh() {
updateStudents();
}
private void updateStudents() {
students.set(daoStudent.findAll());
}
/**
* Imports the given {@link List} of {@link Student}s.<br>
* If {@code fullImport} is set, all {@link Student}s in the database will be deleted which aren't included in the {@link List}.
* Existing {@link Student}s will be updated.<br>
* If {@code fullImport} is not set, new {@link Student}s will be persisted and the existing {@link Student}s will be updated.
*
* @param csvStudents
* @param fullImport
*/
@HandlesAction(PersistStudents.class)
public void persistStudents(List<Student> csvStudents, boolean fullImport) {
int deletedStudents = 0;
int changedStudents = 0;
if (fullImport) {
deletedStudents = deleteReturnedMarkUnreturned(csvStudents);
}
changedStudents = updateInsertStudents(csvStudents);
eventBus.post(new MessageEvent("Import erfolgreich",
"Es wurden " + changedStudents + " Schüler hinzugefügt oder aktualisiert und " + deletedStudents + " gelöscht.",
Type.TRAYINFO));
updateStudents();
}
/**
* Receives the OutputStream provided by an upload.
*
* @param outputStream
* @param fullImport
*/
public void receiveUploadByteOutputStream(ByteArrayOutputStream outputStream, boolean fullImport) {
try {
String encoding = checkEncoding(outputStream);
CSVReader reader;
LOG.warn(encoding);
if (encoding != null) {
reader = new CSVReader(new InputStreamReader(new ByteArrayInputStream(outputStream.toByteArray()), encoding), ';', '\'', 0);
}
else{
reader = new CSVReader(new InputStreamReader(new ByteArrayInputStream(outputStream.toByteArray())), ';', '\'', 0);
}
List<Student> students = CSVHandler.createStudentObjectsFromCSV(reader);
persistStudents(students, fullImport);
} catch (UnsupportedOperationException uoe) {
eventBus.post(new MessageEvent("Import nicht möglich.", uoe.getMessage(), Type.ERROR));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
/**
* Checks the encoding of the given {@link ByteArrayOutputStream}.
*
* @param outputStream
* @return The encoding of the {@link ByteArrayOutputStream}. If no encoding can be deteced: <code>null</code>.
*/
private String checkEncoding(ByteArrayOutputStream outputStream) {
byte[] buf = new byte[4096];
ByteArrayInputStream bais;
try {
bais = new ByteArrayInputStream(outputStream.toByteArray());
// (1)
UniversalDetector detector = new UniversalDetector(null);
// (2)
int nread;
while ((nread = bais.read(buf)) > 0 && !detector.isDone()) {
detector.handleData(buf, 0, nread);
}
// (3)
detector.dataEnd();
// (4)
String encoding = detector.getDetectedCharset();
// (5)
detector.reset();
bais.close();
return encoding;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* @return {@link List} of {@link Student}s which have {@code received} {@link BorrowedMaterial}s with {@code isReturned == false}
*/
private List<Student> getStudentsWithUnreturnedBorrowedMaterials() {
Collection<BorrowedMaterial> unreturnedBorrowedMaterials = this.daoBorrowedMaterial.findAllWithCriteria(
Restrictions.eq("received", true),
Restrictions.isNull("returnDate"));
List<Student> studentsWithUnreturnedBorrowedMaterials = new ArrayList<>();
for (BorrowedMaterial borrowedMaterial : unreturnedBorrowedMaterials) {
studentsWithUnreturnedBorrowedMaterials.add(borrowedMaterial.getStudent());
}
return studentsWithUnreturnedBorrowedMaterials;
}
/**
* Deletes the given {@link Student}s if they don't have any {@code unreturned} {@link BorrowedMaterial}s left.<br>
* A {@link Student} will be marked as {@code leavingSchool} if he has {@code unreturned} {@link BorrowedMaterial}s.
*
* @param csvStudents
* @return Number of deleted or updated {@link Student}s
*/
private int deleteReturnedMarkUnreturned(List<Student> csvStudents) {
List<Student> toDelete = new ArrayList<>(daoStudent.findAll());
toDelete.removeAll(csvStudents);
List<Student> studentsWithUnreturnedBorrowedMaterials = getStudentsWithUnreturnedBorrowedMaterials();
studentsWithUnreturnedBorrowedMaterials.retainAll(toDelete);
for (Student student : studentsWithUnreturnedBorrowedMaterials) {
student.setLeavingSchool(true);
}
daoStudent.update(studentsWithUnreturnedBorrowedMaterials);
toDelete.removeAll(studentsWithUnreturnedBorrowedMaterials);
daoStudent.delete(toDelete);
return toDelete.size();
}
/**
* Checks if one of the given {@link Student}s is already existing in the database.
* In this case, the {@link Student} will be updated, otherwise it will be persisted.
*
* @param csvStudents
* @return Number of updated or inserted {@link Student}s
*/
private int updateInsertStudents(List<Student> csvStudents) {
int changedStudents = 0;
for(Student student : csvStudents) {
Grade grade = daoGrade.findSingleWithCriteria(
Restrictions.and(
Restrictions.like("grade", student.getGrade().getGrade()),
Restrictions.like("suffix", student.getGrade().getSuffix())
));
if (grade == null) {
grade = daoGrade.insert(student.getGrade());
}
student.setGrade(grade);
Student existingStudent = daoStudent.find(student.getId());
if(existingStudent != null) {
existingStudent.copyDataFrom(student);
daoStudent.update(existingStudent);
} else {
daoStudent.insert(student);
}
changedStudents++;
}
return changedStudents;
}
}