package sushi.application.pages.input;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.ajax.markup.html.form.AjaxCheckBox;
import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
import org.apache.wicket.extensions.markup.html.repeater.data.table.DefaultDataTable;
import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import sushi.FileUtils;
import sushi.application.components.form.BlockingAjaxButton;
import sushi.application.components.form.WarnOnExitForm;
import sushi.application.components.table.AttributeTypeCheckBoxPanel;
import sushi.application.components.table.AttributeTypeDropDownChoicePanel;
import sushi.application.pages.AbstractSushiPage;
import sushi.application.pages.input.model.EventAttributeProvider;
import sushi.application.pages.main.MainPage;
import sushi.csv.importer.CSVImporter;
import sushi.event.SushiEvent;
import sushi.event.SushiEventType;
import sushi.event.attribute.SushiAttribute;
import sushi.event.attribute.SushiAttributeTree;
import sushi.event.attribute.SushiAttributeTypeEnum;
import sushi.eventhandling.Broker;
import sushi.excel.importer.ExcelImporter;
import sushi.excel.importer.FileNormalizer;
import sushi.excel.importer.SushiImportEvent;
import sushi.excel.importer.TimeStampNames;
import sushi.xml.importer.AbstractXMLParser;
public class ExcelEventTypeCreator extends AbstractSushiPage {
private static final long serialVersionUID = 1L;
public static String GENERATED_TIMESTAMP_COLUMN_NAME = AbstractXMLParser.GENERATED_TIMESTAMP_COLUMN_NAME;
private List<IColumn<SushiAttribute, String>> attributeTableColumns;
private DefaultDataTable<SushiAttribute, String> attributeTable;
private EventAttributeProvider eventAttributeProvider;
private ArrayList<String> columnTitles = new ArrayList<String>();
private FileNormalizer fileNormalizer;
private String filePath;
private List<Map<String,String>> tableRows = new ArrayList<Map<String,String>>();
private DropDownChoice<String> timestampDropDownChoice;
private ListView<List<String>> headerContainer;
private ListView<Map<String, String>> rowContainer;
private WebMarkupContainer tableContainer;
private ExcelEventTypeCreator excelEventTypeCreator;
private Form<Void> layoutForm;
private TextField<String> eventTypeNameInput;
private String eventTypeName;
private SushiAttributeTree eventTypeAttributesTree;
private SushiAttribute timestampAttribute = new SushiAttribute();
private AjaxCheckBox importTimeCheckBox;
private boolean eventTypeUsingImportTime = false;
private String timestampName;
public ExcelEventTypeCreator(PageParameters parameters) {
super();
this.excelEventTypeCreator = this;
this.filePath = parameters.get("filePath").toString();
this.eventTypeName = FileUtils.getFileNameWithoutExtension(filePath);
int index = filePath.lastIndexOf('.');
String fileExtension = filePath.substring(index + 1, filePath.length());
if(fileExtension.contains("xls")){
fileNormalizer = new ExcelImporter();
} else {
fileNormalizer = new CSVImporter();
}
columnTitles = (ArrayList<String>) fileNormalizer.getColumnTitlesFromFile(filePath);
buildMainlayout();
}
private void buildMainlayout() {
layoutForm = new WarnOnExitForm("layoutForm");
add(layoutForm);
List<PropertyColumn> columns = new ArrayList<PropertyColumn>();
for (String title : columnTitles) {
columns.add(new PropertyColumn(new Model(title), title));
}
columns.add(new PropertyColumn(new Model(GENERATED_TIMESTAMP_COLUMN_NAME), GENERATED_TIMESTAMP_COLUMN_NAME));
// text field with event type name
eventTypeNameInput = new TextField<String>("eventTypeNameInput", new PropertyModel<String>(this, "eventTypeName"));
eventTypeNameInput.setOutputMarkupId(true);
layoutForm.add(eventTypeNameInput);
List<SushiAttribute> attributes = new ArrayList<SushiAttribute>();
List<String> attributeExpressions = new ArrayList<String>();
for (String attributeName : columnTitles) {
// TODO: only Date attributes should be added to timestamp dropdown choice (~= attributeExpressions)
SushiAttribute attribute = new SushiAttribute(attributeName, SushiAttributeTypeEnum.STRING);
attributes.add(attribute);
attributeExpressions.add(attribute.getAttributeExpression());
}
// initialize attributes (value types) tree of event type
eventTypeAttributesTree = new SushiAttributeTree(attributes);
// initialize provider for event preview table
eventAttributeProvider = new EventAttributeProvider(attributes, timestampName);
// if there is no attribute that could be used as timestamp
if (attributeExpressions.isEmpty()) {
// use import time as timestamp
timestampName = GENERATED_TIMESTAMP_COLUMN_NAME;
eventTypeUsingImportTime = true;
} else {
// else pre-select a timestamp in the dropdown choice
timestampName = setTimestampPreselection(attributes);
timestampAttribute = eventTypeAttributesTree.getAttributeByExpression(timestampName);
eventAttributeProvider.deselectEntry(timestampAttribute);
}
eventAttributeProvider.setTimestampName(timestampName);
// render table with events and values
configurePreviewTable();
importTimeCheckBox = new AjaxCheckBox("importTimeCheckBox", new PropertyModel<Boolean>(this, "eventTypeUsingImportTime")) {
@Override
protected void onUpdate(AjaxRequestTarget target) {
if (eventTypeUsingImportTime) {
timestampName = GENERATED_TIMESTAMP_COLUMN_NAME;
} else {
timestampName = timestampAttribute.getName();
}
// disable timestamp dropdown choice if check box is checked
timestampDropDownChoice.setEnabled(!eventTypeUsingImportTime);
eventAttributeProvider.setTimestampName(timestampName);
target.add(timestampDropDownChoice);
target.add(attributeTable);
target.add(tableContainer);
}
};
timestampDropDownChoice = new DropDownChoice<String>("timestampDropDownChoice", new PropertyModel<String>(this, "timestampName"), attributeExpressions);
timestampDropDownChoice.add(new AjaxFormComponentUpdatingBehavior("onchange") {
private static final long serialVersionUID = 1L;
@Override
protected void onUpdate(AjaxRequestTarget target) {
timestampAttribute = eventTypeAttributesTree.getAttributeByExpression(timestampName);
eventAttributeProvider.setTimestampName(timestampName);
target.add(attributeTable);
target.add(tableContainer);
}
});
// disable components if there is no attribute that could be used as timestamp
if (attributeExpressions.isEmpty()) {
importTimeCheckBox.setEnabled(false);
timestampDropDownChoice.setEnabled(false);
}
importTimeCheckBox.setOutputMarkupId(true);
timestampDropDownChoice.setOutputMarkupId(true);
layoutForm.add(importTimeCheckBox);
layoutForm.add(timestampDropDownChoice);
attributeTableColumns = new ArrayList<IColumn<SushiAttribute, String>>();
attributeTableColumns.add(new AbstractColumn<SushiAttribute, String>(new Model("Select")) {
@Override
public void populateItem(Item cellItem, String componentId, IModel rowModel) {
SushiAttribute attribute = ((SushiAttribute) rowModel.getObject());
boolean checkBoxEnabled = true;
// disable attribute checkbox if the attribute is selected in timestamp dropdown
if (!eventTypeUsingImportTime && timestampAttribute != null && timestampAttribute.equals(attribute)) {
checkBoxEnabled = false;
}
cellItem.add(new AttributeTypeCheckBoxPanel(componentId, attribute, checkBoxEnabled, eventAttributeProvider, tableContainer));
}
});
attributeTableColumns.add(new PropertyColumn<SushiAttribute, String>(Model.of("Name"), "name"));
attributeTableColumns.add(new AbstractColumn<SushiAttribute, String>(new Model("Type")) {
@Override
public void populateItem(Item cellItem, String componentId, IModel rowModel) {
SushiAttribute attribute = ((SushiAttribute) rowModel.getObject());
boolean dropDownChoiceEnabled = true;
// disable attribute type dropdown choice if the attribute is selected in timestamp dropdown
if (!eventTypeUsingImportTime && timestampAttribute != null && timestampAttribute.equals(attribute)) {
dropDownChoiceEnabled = false;
}
cellItem.add(new AttributeTypeDropDownChoicePanel(componentId, attribute, dropDownChoiceEnabled, eventAttributeProvider));
}
});
attributeTable = new DefaultDataTable<SushiAttribute, String>("attributeTable", attributeTableColumns, eventAttributeProvider, 20);
attributeTable.setOutputMarkupId(true);
layoutForm.add(attributeTable);
// confirm button
BlockingAjaxButton confirmButton = new BlockingAjaxButton("confirmButton", layoutForm) {
private static final long serialVersionUID = 1L;
@Override
public void onSubmit(AjaxRequestTarget target, Form form) {
super.onSubmit(target, form);
if (eventTypeName.isEmpty()) {
eventTypeName = FileUtils.getFileNameWithoutExtension(filePath);
}
// check if event type with this name already exists
if (SushiEventType.getAllTypeNames().contains(eventTypeName)) {
// TODO: Ask the user if it shall be added to the existing event type with this name
excelEventTypeCreator.getFeedbackPanel().error("Event type with this name already exists!");
target.add(excelEventTypeCreator.getFeedbackPanel());
return;
}
/*
* remove attribute to be used as timestamp from attributes (value type) tree
* since the attribute is stored in the event type directly
*/
if (!eventTypeUsingImportTime) {
timestampAttribute.removeAttribute();
eventTypeAttributesTree.removeRoot(timestampAttribute);
}
// remove attributes that are not selected
eventTypeAttributesTree.retainAllAttributes(eventAttributeProvider.getSelectedEntities());
SushiEventType eventType;
try {
eventType = new SushiEventType(eventTypeName, eventTypeAttributesTree, timestampName);
Broker.send(eventType);
} catch (RuntimeException e) {
excelEventTypeCreator.getFeedbackPanel().error(e.getMessage());
target.add(excelEventTypeCreator.getFeedbackPanel());
return;
}
List<SushiEvent> events = fileNormalizer.importEventsFromFile(filePath, eventTypeAttributesTree.getRoots(), timestampName);
if (eventTypeUsingImportTime) {
for (SushiEvent actualEvent : events) {
actualEvent.setTimestamp(new Date());
}
}
for (SushiEvent event : events) {
event.setEventType(eventType);
}
Broker.send(events);
PageParameters pageParameters = new PageParameters();
pageParameters.add("successFeedback", events.size() + " events have been added to " + eventTypeName);
setResponsePage(MainPage.class, pageParameters);
}
};
layoutForm.add(confirmButton);
}
private void configurePreviewTable() {
List<SushiImportEvent> events = fileNormalizer.importEventsForPreviewFromFile(filePath, columnTitles);
for (SushiImportEvent event : events) {
Map<String, String> eventValues = new HashMap<String, String>();
if (event.getTimestamp() != null) {
eventValues.put(event.getExtractedTimestampName(), event.getTimestamp().toString());
}
for (String columnTitle : columnTitles) {
if(!(TimeStampNames.contains(columnTitle) || columnTitle.equals(GENERATED_TIMESTAMP_COLUMN_NAME))){
String actualEventValue = (String) event.getValues().get(columnTitle);
if (actualEventValue != null) {
eventValues.put(columnTitle, actualEventValue.toString());
}
}
}
eventValues.put(GENERATED_TIMESTAMP_COLUMN_NAME, event.getImportTime().toString());
tableRows.add(eventValues);
}
// table
tableContainer = new WebMarkupContainer("tableContainer");
tableContainer.setOutputMarkupId(true);
List<String> allColumnTitles = new ArrayList<String>(columnTitles);
allColumnTitles.add(GENERATED_TIMESTAMP_COLUMN_NAME);
List<List<String>> headers = new ArrayList<List<String>>();
headers.add(allColumnTitles);
headerContainer = new ListView<List<String>>("headerContainer", headers) {
private static final long serialVersionUID = -5180277632835328415L;
@Override
protected void populateItem(ListItem<List<String>> item) {
List<String> selectedColumnTitles = eventAttributeProvider.getSelectedColumnNames();
item.add(new ListView<String>("column", selectedColumnTitles) {
private static final long serialVersionUID = -6368677142275503560L;
@Override
protected void populateItem(ListItem<String> item) {
item.add(new Label("cell", item.getModelObject()));
}
});
}
};
headerContainer.setOutputMarkupId(true);
rowContainer = new ListView<Map<String, String>>("rowContainer", tableRows) {
private static final long serialVersionUID = -3289707574304971363L;
@Override
protected void populateItem(ListItem<Map<String, String>> item) {
final List<String> selectedColumnTitles = eventAttributeProvider.getSelectedColumnNames();
final Map<String, String> row = item.getModelObject();
item.add(new ListView<String>("column", selectedColumnTitles) {
private static final long serialVersionUID = -2477176270802239757L;
int i = 0;
@Override
protected void populateItem(ListItem<String> item) {
item.add(new Label("cell", row.get(selectedColumnTitles.get(i++))));
}
});
}
};
rowContainer.setOutputMarkupId(true);
tableContainer.add(headerContainer);
tableContainer.add(rowContainer);
layoutForm.add(tableContainer);
}
public String setTimestampPreselection(List<SushiAttribute> attributes) {
SushiAttribute timestampAttribute = findBestTimestampMatch(attributes);
if (timestampAttribute != null) {
return timestampAttribute.getName();
} else {
return GENERATED_TIMESTAMP_COLUMN_NAME;
}
}
public SushiAttribute findBestTimestampMatch(List<SushiAttribute> attributes) {
for (SushiAttribute attribute : attributes) {
String attributeName = attribute.getName().toLowerCase();
Pattern regex = Pattern.compile("^time|time$|date|datum|uhrzeit|zeit$");
Matcher match = regex.matcher(attributeName);
if (match.find()) {
return attribute;
}
}
return null;
}
}