package de.dhbw.humbuch.view;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.NoSuchElementException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.eventbus.EventBus;
import com.google.inject.Inject;
import com.vaadin.data.Container.Filter;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.data.util.BeanItemContainer;
import com.vaadin.data.util.filter.Or;
import com.vaadin.data.util.filter.SimpleStringFilter;
import com.vaadin.event.FieldEvents.TextChangeEvent;
import com.vaadin.event.FieldEvents.TextChangeListener;
import com.vaadin.event.ShortcutAction.KeyCode;
import com.vaadin.event.ShortcutListener;
import com.vaadin.navigator.View;
import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent;
import com.vaadin.server.StreamResource;
import com.vaadin.ui.AbstractTextField.TextChangeEventMode;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Table;
import com.vaadin.ui.TextField;
import com.vaadin.ui.Upload;
import com.vaadin.ui.Upload.Receiver;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.data.Property;
import de.davherrmann.mvvm.BasicState;
import de.davherrmann.mvvm.State;
import de.davherrmann.mvvm.StateChangeListener;
import de.davherrmann.mvvm.ViewModelComposer;
import de.davherrmann.mvvm.annotations.BindState;
import de.dhbw.humbuch.event.MessageEvent;
import de.dhbw.humbuch.event.MessageEvent.Type;
import de.dhbw.humbuch.model.entity.BorrowedMaterial;
import de.dhbw.humbuch.model.entity.Student;
import de.dhbw.humbuch.util.PDFHandler;
import de.dhbw.humbuch.util.PDFStudentList;
import de.dhbw.humbuch.view.components.ConfirmDialog;
import de.dhbw.humbuch.view.components.PrintingComponent;
import de.dhbw.humbuch.viewmodel.StudentInformationViewModel;
import de.dhbw.humbuch.viewmodel.StudentInformationViewModel.Students;
/**
* Provides an overview over all students and the possibility to import new
* ones.
*
* @author Johannes
*/
@SuppressWarnings("serial")
public class StudentInformationView extends VerticalLayout implements View,
ViewInformation {
private static final long serialVersionUID = -739081142499192817L;
private final static Logger LOG = LoggerFactory
.getLogger(StudentInformationView.class);
private static final String TITLE = "Schülerübersicht";
private static final String TABLE_FIRSTNAME = "firstname";
private static final String TABLE_LASTNAME = "lastname";
private static final String TABLE_GRADE = "grade";
private static final String TABLE_BIRTHDAY = "birthday";
private static final String TABLE_GENDER = "gender";
private Upload upload;
private UploadReceiver receiver = new UploadReceiver(5242880); // =5MB
private HorizontalLayout head;
private TextField filter;
private Table studentsTable;
private Button showMaterials;
protected StudentInformationViewModel studentInformationViewModel;
@BindState(Students.class)
public State<Collection<Student>> students = new BasicState<>(
Collection.class);
private BeanItemContainer<Student> tableData;
private EventBus eventBus;
/**
* constructor
*
* @param viewModelComposer
* @param importViewModel
*/
@Inject
public StudentInformationView(ViewModelComposer viewModelComposer,
StudentInformationViewModel importViewModel, EventBus eventBus) {
this.studentInformationViewModel = importViewModel;
this.eventBus = eventBus;
init();
buildLayout();
bindViewModel(viewModelComposer, importViewModel);
}
/**
* Initializes the components and sets attributes.
*/
private void init() {
head = new HorizontalLayout();
head.setWidth("100%");
head.setSpacing(true);
// Filter
filter = new TextField();
filter.setImmediate(true);
filter.setInputPrompt("Name, Vorname oder Klasse...");
filter.setWidth("50%");
filter.setTextChangeEventMode(TextChangeEventMode.EAGER);
head.addComponent(filter);
head.setExpandRatio(filter, 1);
head.setComponentAlignment(filter, Alignment.MIDDLE_LEFT);
HorizontalLayout buttons = new HorizontalLayout();
showMaterials = new Button("Materialien anzeigen");
showMaterials.setEnabled(false);
buttons.addComponent(showMaterials);
buttons.setSpacing(true);
// Import button
upload = new Upload();
upload.setReceiver(receiver);
upload.setImmediate(true);
upload.addSucceededListener(receiver);
upload.setButtonCaption("Import");
buttons.addComponent(upload);
head.addComponent(buttons);
head.setComponentAlignment(buttons, Alignment.MIDDLE_RIGHT);
// Table
studentsTable = new Table() {
private static final long serialVersionUID = 1885098955441122118L;
final SimpleDateFormat df = new SimpleDateFormat();
{
df.applyPattern("dd.MM.yyyy");
}
@Override
protected String formatPropertyValue(Object rowId, Object colId,
Property<?> property) {
if (colId.equals(TABLE_BIRTHDAY)) {
if (property.getValue() == null) {
return null;
} else {
return df.format(((Date) property.getValue()).getTime());
}
}
return super.formatPropertyValue(rowId, colId, property);
}
};
studentsTable.setSelectable(true);
studentsTable.setImmediate(true);
studentsTable.setSizeFull();
studentsTable.setColumnCollapsingAllowed(true);
tableData = new BeanItemContainer<Student>(Student.class);
studentsTable.setContainerDataSource(tableData);
studentsTable.setVisibleColumns(new Object[] { "lastname", "firstname",
"grade", "birthday", "gender" });
studentsTable.setColumnHeader(TABLE_LASTNAME, "Name");
studentsTable.setColumnHeader(TABLE_FIRSTNAME, "Vorname");
studentsTable.setColumnHeader(TABLE_GRADE, "Klasse");
studentsTable.setColumnHeader(TABLE_BIRTHDAY, "Geburtstag");
studentsTable.setColumnHeader(TABLE_GENDER, "Geschlecht");
this.addListener();
}
/**
* Adds all listener to their corresponding components
*/
private void addListener() {
/**
* Implements the button click event to show all borrowed materials of a
* student
*/
showMaterials.addClickListener(new ClickListener() {
private static final long serialVersionUID = 1947881830091475265L;
@Override
public void buttonClick(ClickEvent event) {
doStudentListPrinting();
}
});
/**
* Enables/disables the show materials button
*/
studentsTable
.addValueChangeListener(new Property.ValueChangeListener() {
private static final long serialVersionUID = 1L;
@Override
public void valueChange(ValueChangeEvent event) {
Student item = (Student) studentsTable.getValue();
showMaterials.setEnabled(item != null);
}
});
/**
* Provides the live search of the table by adding a filter after every
* keypress in the search field.
*/
filter.addTextChangeListener(new TextChangeListener() {
private static final long serialVersionUID = -1684545652234105334L;
@Override
public void textChange(TextChangeEvent event) {
SimpleStringFilter cond1 = new SimpleStringFilter(
TABLE_LASTNAME, event.getText(), true, false);
SimpleStringFilter cond2 = new SimpleStringFilter(
TABLE_FIRSTNAME, event.getText(), true, false);
SimpleStringFilter cond3 = new SimpleStringFilter(TABLE_GRADE,
event.getText(), true, false);
Filter filter = new Or(cond1, cond2, cond3);
tableData.removeAllContainerFilters();
tableData.addContainerFilter(filter);
}
});
/**
* Listens for changes in all students collection an adds them to the
* container
*/
students.addStateChangeListener(new StateChangeListener() {
@Override
public void stateChange(Object value) {
tableData.removeAllItems();
for (Student student : students.get()) {
studentsTable.addItem(student);
}
}
});
filter.addShortcutListener(new ShortcutListener("Clear",
KeyCode.ESCAPE, null) {
private static final long serialVersionUID = 7058759679522434521L;
@Override
public void handleAction(Object sender, Object target) {
filter.setValue("");
tableData.removeAllContainerFilters();
}
});
}
/**
* Builds the layout by adding the components to the view
*/
private void buildLayout() {
setSizeFull();
setSpacing(true);
setMargin(true);
addComponent(head);
addComponent(studentsTable);
setExpandRatio(studentsTable, 1);
}
/**
* Creates a PDF from the currently selected student in the table. The
* generated PDF is displayed in a new Window.
*/
private void doStudentListPrinting() {
Student item = (Student) studentsTable.getValue();
if (item != null) {
List<BorrowedMaterial> borrowedMaterials = new ArrayList<BorrowedMaterial>();
;
for (BorrowedMaterial bm : item.getBorrowedMaterials()) {
if (bm.isReceived() && bm.getReturnDate() == null) {
borrowedMaterials.add(bm);
}
}
if (!borrowedMaterials.isEmpty()) {
PDFStudentList.Builder builder = new PDFStudentList.Builder()
.borrowedMaterialList(borrowedMaterials);
ByteArrayOutputStream boas = new PDFStudentList(builder)
.createByteArrayOutputStreamForPDF();
StreamResource sr = new StreamResource(
new PDFHandler.PDFStreamSource(boas), "Infoliste_"
+ item.getLastname() + "_"
+ item.getFirstname() + ".pdf");
new PrintingComponent(sr, "Ausgeliehene Materialien von "
+ item.getLastname() + ", " + item.getFirstname());
} else {
eventBus.post(new MessageEvent("Der Schüler '"
+ item.getLastname() + ", " + item.getFirstname()
+ "' hat keine Materialien ausgeliehen."));
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void enter(ViewChangeEvent event) {
/*
* BUGFIX: Set the value of the table to null so the user has to update
* the selection if he/she wants to edit the same item again.
*/
studentsTable.setValue(null);
studentInformationViewModel.refresh();
}
private void bindViewModel(ViewModelComposer viewModelComposer,
Object... viewModels) {
try {
viewModelComposer.bind(this, viewModels);
} catch (IllegalAccessException | NoSuchElementException
| UnsupportedOperationException e) {
e.printStackTrace();
}
}
/**
* {@inheritDoc}
*/
@Override
public String getTitle() {
return TITLE;
}
/**
* Creates a Window with the question whether the import is a full import or
* a delta import.
*/
public void selectImportTypeDialog(final ByteArrayOutputStream outputStream) {
ConfirmDialog
.show("Import-Typ auswählen",
"Wie soll die Datei importiert werden? Bei einem Delta Import werden keine Schüler gelöscht",
"Vollständiger Import", "Delta Import",
new ConfirmDialog.Listener() {
@Override
public void onClose(ConfirmDialog dialog) {
boolean fullImport;
if (dialog.isConfirmed()) {
fullImport = true;
} else {
fullImport = false;
}
studentInformationViewModel
.receiveUploadByteOutputStream(
outputStream, fullImport);
}
});
}
/**
* Implementation of a vaadin receiver
*
* @author Johannes
*/
public class UploadReceiver implements Receiver, Upload.SucceededListener,
Upload.ProgressListener {
private static final long serialVersionUID = 1L;
private ByteArrayOutputStream outputStream;
private final long maxSize;
private boolean interrupted;
/**
* @param maxSize
* The maximum file size that will be accepted (in bytes). -1
* in case of no limit. 100Kb = 100000
*/
public UploadReceiver(long maxSize) {
this.maxSize = maxSize;
}
public OutputStream receiveUpload(String filename, String MIMEType) {
if (!MIMEType.equals("text/csv")
&& !MIMEType.equals("application/vnd.ms-excel")) {
upload.interruptUpload();
interrupted = true;
eventBus.post(new MessageEvent(
"Import nicht möglich.",
"Die ausgewählte Datei ist keine CSV-Datei." + MIMEType,
Type.ERROR));
}
reset();
this.outputStream = new ByteArrayOutputStream();
return outputStream;
}
public void uploadSucceeded(Upload.SucceededEvent event) {
if (!interrupted) {
selectImportTypeDialog(this.outputStream);
}
}
/**
* Resets the upload
*/
public void reset() {
interrupted = false;
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException ex) {
LOG.trace("Couldn't close previous OutputStream");
}
}
outputStream = null;
}
/**
* Interrupts the current upload
*/
protected void interrupt() {
upload.interruptUpload();
interrupted = true;
eventBus.post(new MessageEvent(
"Import nicht möglich.",
"Die ausgewählte Datei ist zu groß. Bitte kontaktieren Sie einen Entwickler.",
Type.ERROR));
}
@Override
public void updateProgress(long readBytes, long contentLength) {
if (readBytes > maxSize || contentLength > maxSize) {
interrupt();
}
}
}
}