From b43d85b032c6f1d3196ee45f7db8a353531acbcd Mon Sep 17 00:00:00 2001 From: Martin Senne Date: Sun, 21 Feb 2016 13:01:57 +0100 Subject: [PATCH 1/6] First draft of CSV import of phone entries. --- .gitignore | 20 +++ README.md | 22 ++- example.csv | 4 + phonebook_example_entries.csv | 3 + pom.xml | 7 + src/main/java/org/example/CSVImportView.java | 160 +++++++++++++++++++ src/main/java/org/example/GroupsView.java | 2 + src/main/java/org/example/csv/CSVUtil.java | 76 +++++++++ src/main/resources/about.md | 19 +++ 9 files changed, 310 insertions(+), 3 deletions(-) create mode 100644 .gitignore create mode 100644 example.csv create mode 100644 phonebook_example_entries.csv create mode 100644 src/main/java/org/example/CSVImportView.java create mode 100644 src/main/java/org/example/csv/CSVUtil.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..536565e --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +### Java template +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +# Created by .ignore support plugin (hsz.mobi) + +# Custom +.idea/ +target/ +*.iml diff --git a/README.md b/README.md index 042a677..a8c66eb 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,25 @@ own domain model. It also demonstrates a very simple add-on usage with Vaadin If you are new to Maven and want to try this application, check out this [tiny Maven tutorial](https://vaadin.com/blog/-/blogs/the-maven-essentials-for-the-impatient-developer). -To get this running execute "mvn wildfly:run" to launch this locally on Wildfly -server or deploy to a Java EE 7+ server like Wildfly or Glassfish. With Liberty you'll need to -present a data source (other modern servers provide "development datasource" when +## Run the example + +As this application is based on JEE it relies on a JEE capable application server. +The Maven project setup includes a Wildfly Maven application server for demonstration purposes. + +To run the project, you can use + +``` +mvn wildfly:run +``` +to launch this locally on Wildfly server. Afterwards, the application is available +in your local browser at + +[http://localhost:8080/jpa-addressbook-1.0-SNAPSHOT](http://localhost:8080/jpa-addressbook-1.0-SNAPSHOT) + + +Alternatively, you can deploy to a Java EE 7+ server like Wildfly or Glassfish. +With Liberty you'll need to present a data source +(other modern servers provide "development datasource" when no jta-datasource is present in persistence.xml). This is a suitable basis for small to medium sized apps. For larger applications, diff --git a/example.csv b/example.csv new file mode 100644 index 0000000..d5ea611 --- /dev/null +++ b/example.csv @@ -0,0 +1,4 @@ +a, b +1, 2 +3, 4 +5, 6 diff --git a/phonebook_example_entries.csv b/phonebook_example_entries.csv new file mode 100644 index 0000000..7a44d14 --- /dev/null +++ b/phonebook_example_entries.csv @@ -0,0 +1,3 @@ +name,number,email +Martin Senne,+49 234 823 9178,martin@vaadin.com +Marc Manager,+356 253 346 221,marc@coolcompany.com diff --git a/pom.xml b/pom.xml index 5fddf3f..ab64e69 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,13 @@ + + + com.opencsv + opencsv + 3.3 + + org.apache.deltaspike.core deltaspike-core-api diff --git a/src/main/java/org/example/CSVImportView.java b/src/main/java/org/example/CSVImportView.java new file mode 100644 index 0000000..a7464e8 --- /dev/null +++ b/src/main/java/org/example/CSVImportView.java @@ -0,0 +1,160 @@ +package org.example; + +import com.vaadin.cdi.CDIView; +import com.vaadin.cdi.UIScoped; +import com.vaadin.data.Container; +import com.vaadin.data.util.IndexedContainer; +import com.vaadin.navigator.View; +import com.vaadin.navigator.ViewChangeListener; +import com.vaadin.ui.Button; +import com.vaadin.ui.Grid; +import com.vaadin.ui.Upload; +import com.vaadin.ui.VerticalLayout; +import org.example.backend.PhoneBookEntry; +import org.example.backend.PhoneBookService; +import org.example.csv.CSVUtil; +import org.vaadin.cdiviewmenu.ViewMenuItem; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import java.io.*; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +@UIScoped +@CDIView("csv_imports") +@ViewMenuItem(order = 2) +public class CSVImportView extends VerticalLayout implements View { + + @Inject + PhoneBookService service; + + Upload upload = new Upload(); + + Grid grid = new Grid(); + Button saveButton; + + static class UploadReceptor implements Upload.Receiver, Upload.SucceededListener { + + private File tempFile; + + private Consumer reader; + + public UploadReceptor( Consumer reader) { + this.reader = reader; + + try { + tempFile = File.createTempFile("temp", ".csv"); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException("Can't create temporary file."); + } + } + + @Override + public OutputStream receiveUpload(String s, String s1) { + try { + return new FileOutputStream(tempFile); + } catch (FileNotFoundException e) { + e.printStackTrace(); + throw new RuntimeException("Temporary file not found."); + } + } + + @Override + public void uploadSucceeded(Upload.SucceededEvent event) { + try { + System.out.println("Got file '" + event.getFilename() + + "' and mimetype '" + event.getMIMEType() + "'."); + FileReader fileReader = new FileReader(tempFile); + reader.accept( fileReader ); + } catch (FileNotFoundException e) { + e.printStackTrace(); + throw new RuntimeException("Problem with upload."); + } + + } + } + + + static class SaveableGrid extends VerticalLayout { + private Grid grid; + private Button cancelButton; + private Button saveButton; + + public SaveableGrid() { + grid = new Grid(); + saveButton = new Button("Save"); + cancelButton = new Button("Cancel"); + } + } + + @PostConstruct + void init() { + + /** + * External handler, after upload was successful. + */ + Consumer consumeReader = reader -> { + System.out.println(reader); + try { + IndexedContainer csvContainer = CSVUtil.buildContainerFromCSV(reader); + grid.setContainerDataSource(csvContainer); + grid.setVisible(true); + saveButton.setVisible(true); + } catch (IOException e) { + e.printStackTrace(); + } + }; + + UploadReceptor uploadReceptor = new UploadReceptor(consumeReader); + upload.setReceiver( uploadReceptor ); + upload.setCaption("Upload CSV"); + upload.addSucceededListener(uploadReceptor); + + saveButton = new Button("Save"); + saveButton.addClickListener(clickEvent -> { + List entries = createEntries(grid.getContainerDataSource()); + saveEntries( entries ); + }); + + grid.setVisible(false); + saveButton.setVisible(false); + + addComponent(upload); + addComponent(grid); + addComponent(saveButton); + } + + private List createEntries(Container.Indexed container ) { + String NAME = "name"; + String NUMBER = "number"; + String EMAIL = "email"; + + List entries = new ArrayList<>(); + int n = container.size(); + + // TODO: Improve hard-wired error-prone mapping + for ( Object itemId : container.getItemIds() ) { + String name = (String) container.getContainerProperty(itemId, NAME).getValue(); + String number = (String) container.getContainerProperty(itemId, NUMBER).getValue(); + String email = (String) container.getContainerProperty(itemId, EMAIL).getValue(); + PhoneBookEntry entry = new PhoneBookEntry(name, number, email); + entries.add(entry); + } + return entries; + } + + private void saveEntries( List entries ) { + System.out.println("Before saving entries."); + for (PhoneBookEntry entry : entries) { + service.save(entry); + } + System.out.println("After saving entries."); + } + + @Override + public void enter(ViewChangeListener.ViewChangeEvent event) { + } +} diff --git a/src/main/java/org/example/GroupsView.java b/src/main/java/org/example/GroupsView.java index b834ab6..f800152 100644 --- a/src/main/java/org/example/GroupsView.java +++ b/src/main/java/org/example/GroupsView.java @@ -13,6 +13,7 @@ import javax.inject.Inject; import org.example.backend.PhoneBookGroup; import org.example.backend.PhoneBookService; +import org.vaadin.cdiviewmenu.ViewMenuItem; import org.vaadin.viritin.button.MButton; import org.vaadin.viritin.fields.MTable; import org.vaadin.viritin.fields.MTextField; @@ -23,6 +24,7 @@ @UIScoped @CDIView("groups") +@ViewMenuItem(order = 1) public class GroupsView extends CssLayout implements View { @Inject diff --git a/src/main/java/org/example/csv/CSVUtil.java b/src/main/java/org/example/csv/CSVUtil.java new file mode 100644 index 0000000..3e6e9c5 --- /dev/null +++ b/src/main/java/org/example/csv/CSVUtil.java @@ -0,0 +1,76 @@ +package org.example.csv; + +import com.opencsv.CSVReader; +import com.vaadin.data.Item; +import com.vaadin.data.util.IndexedContainer; +import com.vaadin.ui.Table; +import com.vaadin.ui.Upload; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.Window; + +import java.io.*; + + +public class CSVUtil { + + /** + * Uses http://opencsv.sourceforge.net/ to read the entire contents of a CSV + * file, and creates an IndexedContainer from it + * + * @param reader + * @return + * @throws IOException + */ + public static IndexedContainer buildContainerFromCSV(Reader reader) throws IOException { + IndexedContainer container = new IndexedContainer(); + CSVReader csvReader = new CSVReader(reader); + String[] columnHeaders = null; + String[] record; + while ((record = csvReader.readNext()) != null) { + // Headerline + if (columnHeaders == null) { + columnHeaders = record; + addItemProperties(container, columnHeaders); + } else { + addItem(container, columnHeaders, record); + } + } + return container; + } + + + /** + * Set's up the item property ids for the container. Each is a String (of course, + * you can create whatever data type you like, but I guess you need to parse the whole file + * to work it out) + * + * @param container The container to set + * @param columnHeaders The column headers, i.e. the first row from the CSV file + */ + private static void addItemProperties(IndexedContainer container, String[] columnHeaders) { + for (String propertyName : columnHeaders) { + container.addContainerProperty(propertyName, String.class, null); + } + } + + /** + * Adds an item to the given container, assuming each field maps to it's corresponding property id. + * Again, note that I am assuming that the field is a string. + * + * @param container + * @param propertyIds + * @param fields + */ + private static void addItem(IndexedContainer container, String[] propertyIds, String[] fields) { + if (propertyIds.length != fields.length) { + throw new IllegalArgumentException("Hmmm - Different number of columns to fields in the record"); + } + Object itemId = container.addItem(); + Item item = container.getItem(itemId); + for (int i = 0; i < fields.length; i++) { + String propertyId = propertyIds[i]; + String field = fields[i]; + item.getItemProperty(propertyId).setValue(field); + } + } +} \ No newline at end of file diff --git a/src/main/resources/about.md b/src/main/resources/about.md index 20afa71..0e75c3e 100644 --- a/src/main/resources/about.md +++ b/src/main/resources/about.md @@ -10,3 +10,22 @@ own domain model. It also demonstrates a very simple add-on usage with Vaadin The source code for this example is available [from github](https://github.com/mstahv/jpa-addressbook). + +## Views +Currently, four different views are avaiable. + +### Main + +Main view for *viewing* phone entries and *adding* new entries. + +### Groups + +Management of group to whom phone entries can be assigned. + +### CSVImport +View that allows for importing phone entries from a CSV file +An example CSV file is given at [/phonebook_example_entries.csv](/phonebook_example_entries.csv) + +### About +This about dialogue. + From 58339a19506378d540902aad15e68985a468c72b Mon Sep 17 00:00:00 2001 From: Martin Senne Date: Sun, 21 Feb 2016 20:47:10 +0100 Subject: [PATCH 2/6] CSV Import for entries. --- ...ample_entries.csv => phonebook_entries.csv | 0 example.csv => phonebook_incorrect.csv | 0 src/main/java/org/example/CSVImportView.java | 201 ++++++++++-------- .../csv/{CSVUtil.java => CSVReadUtil.java} | 2 +- .../example/csv/FileBasedUploadReceptor.java | 54 +++++ 5 files changed, 166 insertions(+), 91 deletions(-) rename phonebook_example_entries.csv => phonebook_entries.csv (100%) rename example.csv => phonebook_incorrect.csv (100%) rename src/main/java/org/example/csv/{CSVUtil.java => CSVReadUtil.java} (98%) create mode 100644 src/main/java/org/example/csv/FileBasedUploadReceptor.java diff --git a/phonebook_example_entries.csv b/phonebook_entries.csv similarity index 100% rename from phonebook_example_entries.csv rename to phonebook_entries.csv diff --git a/example.csv b/phonebook_incorrect.csv similarity index 100% rename from example.csv rename to phonebook_incorrect.csv diff --git a/src/main/java/org/example/CSVImportView.java b/src/main/java/org/example/CSVImportView.java index a7464e8..8dd6cbd 100644 --- a/src/main/java/org/example/CSVImportView.java +++ b/src/main/java/org/example/CSVImportView.java @@ -6,136 +6,158 @@ import com.vaadin.data.util.IndexedContainer; import com.vaadin.navigator.View; import com.vaadin.navigator.ViewChangeListener; -import com.vaadin.ui.Button; -import com.vaadin.ui.Grid; -import com.vaadin.ui.Upload; -import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.*; import org.example.backend.PhoneBookEntry; import org.example.backend.PhoneBookService; -import org.example.csv.CSVUtil; +import org.example.csv.CSVReadUtil; +import org.example.csv.FileBasedUploadReceptor; import org.vaadin.cdiviewmenu.ViewMenuItem; +import org.vaadin.viritin.label.Header; +import org.vaadin.viritin.layouts.MVerticalLayout; import javax.annotation.PostConstruct; import javax.inject.Inject; import java.io.*; -import java.util.ArrayList; -import java.util.List; +import java.util.*; import java.util.function.Consumer; @UIScoped @CDIView("csv_imports") @ViewMenuItem(order = 2) -public class CSVImportView extends VerticalLayout implements View { +public class CSVImportView extends MVerticalLayout implements View { @Inject PhoneBookService service; Upload upload = new Upload(); - - Grid grid = new Grid(); + Grid grid; + Button cancelButton; Button saveButton; - - static class UploadReceptor implements Upload.Receiver, Upload.SucceededListener { - - private File tempFile; - - private Consumer reader; - - public UploadReceptor( Consumer reader) { - this.reader = reader; - - try { - tempFile = File.createTempFile("temp", ".csv"); - } catch (IOException e) { - e.printStackTrace(); - throw new RuntimeException("Can't create temporary file."); - } - } - - @Override - public OutputStream receiveUpload(String s, String s1) { - try { - return new FileOutputStream(tempFile); - } catch (FileNotFoundException e) { - e.printStackTrace(); - throw new RuntimeException("Temporary file not found."); - } - } - - @Override - public void uploadSucceeded(Upload.SucceededEvent event) { - try { - System.out.println("Got file '" + event.getFilename() + - "' and mimetype '" + event.getMIMEType() + "'."); - FileReader fileReader = new FileReader(tempFile); - reader.accept( fileReader ); - } catch (FileNotFoundException e) { - e.printStackTrace(); - throw new RuntimeException("Problem with upload."); - } - - } - } - - - static class SaveableGrid extends VerticalLayout { - private Grid grid; - private Button cancelButton; - private Button saveButton; - - public SaveableGrid() { - grid = new Grid(); - saveButton = new Button("Save"); - cancelButton = new Button("Cancel"); - } - } + Header header; @PostConstruct void init() { + header = new Header("CSV Import"); + /** * External handler, after upload was successful. */ Consumer consumeReader = reader -> { System.out.println(reader); try { - IndexedContainer csvContainer = CSVUtil.buildContainerFromCSV(reader); + IndexedContainer csvContainer = CSVReadUtil.buildContainerFromCSV(reader); + // this is intentional, as attaching new datasource to existing grid seems cause problems in this Vaadin version + grid = new Grid(); + grid.setWidth(100, Unit.PERCENTAGE); grid.setContainerDataSource(csvContainer); - grid.setVisible(true); - saveButton.setVisible(true); + displayUploadedData(); + } catch (IOException e) { e.printStackTrace(); } }; - UploadReceptor uploadReceptor = new UploadReceptor(consumeReader); - upload.setReceiver( uploadReceptor ); - upload.setCaption("Upload CSV"); - upload.addSucceededListener(uploadReceptor); + FileBasedUploadReceptor fileBasedUploadReceptor = new FileBasedUploadReceptor(consumeReader); + upload.setReceiver(fileBasedUploadReceptor); + upload.addSucceededListener(fileBasedUploadReceptor); saveButton = new Button("Save"); saveButton.addClickListener(clickEvent -> { - List entries = createEntries(grid.getContainerDataSource()); - saveEntries( entries ); + Container.Indexed container = grid.getContainerDataSource(); + if ( checkEntries(grid.getContainerDataSource()) ) { + List entries = createEntries(container); + saveEntries( entries ); + Notification.show( entries.size() + " entries have been imported."); + displayUpload(); + } else { + Notification.show("Can not save entries. Schema ( column names ) of CSV not correct.", Notification.Type.WARNING_MESSAGE); + } }); + + cancelButton = new Button("Cancel"); + cancelButton.addClickListener(clickEvent -> { + displayUpload(); + } + ); - grid.setVisible(false); - saveButton.setVisible(false); + displayUpload(); + } + + // ===== View related methods + public void displayUpload() { + removeAllComponents(); + + Label label = new Label("Please upload your CSV, that contains columns 'name', 'number' and 'email'"); + addComponent(header); + addComponent(label); addComponent(upload); + } + + public void displayUploadedData() { + removeAllComponents(); + + HorizontalLayout hl = new HorizontalLayout(); + hl.addComponent(cancelButton); + hl.addComponent(saveButton); + + HorizontalLayout bottom = new HorizontalLayout(); + bottom.setWidth(100, Unit.PERCENTAGE); + bottom.addComponent(hl); + bottom.setComponentAlignment(hl, Alignment.MIDDLE_RIGHT); + + addComponent(header); addComponent(grid); - addComponent(saveButton); + setExpandRatio(grid, 1); + addComponent(bottom); } - private List createEntries(Container.Indexed container ) { - String NAME = "name"; - String NUMBER = "number"; - String EMAIL = "email"; + @Override + public void enter(ViewChangeListener.ViewChangeEvent event) { + // nothing to do on enter + } + + // ===== Entry related methods + // TODO: To be moved to separate class, but left here to keep everything "close together". + + private String NAME = "name"; + private String NUMBER = "number"; + private String EMAIL = "email"; + + /** + * Check if all property ids NAME, NUMBER and EMAIL are present in given container (as read from CSV). + * @param containerDataSource + * @return true if so + */ + private boolean checkEntries(Container.Indexed containerDataSource) { + Collection pids = (Collection)containerDataSource.getContainerPropertyIds(); + + Set actualSchema = new HashSet<>(pids); + Set expectedSchema = new HashSet<>(Arrays.asList(NAME, NUMBER, EMAIL)); + + Set intersect = (new HashSet<>(actualSchema)); + intersect.retainAll(expectedSchema); + if (actualSchema.size() == 3) { + return true; // everything is fine + } else { + return false; + } + } + + /** + * Create entries based on container content. + * + * @param container use for construction of {@link PhoneBookEntry}s. + * @return list of entries + */ + private List createEntries(Container.Indexed container ) { + List entries = new ArrayList<>(); int n = container.size(); - // TODO: Improve hard-wired error-prone mapping + // TODO: Introduce real mapping functionality for ( Object itemId : container.getItemIds() ) { String name = (String) container.getContainerProperty(itemId, NAME).getValue(); String number = (String) container.getContainerProperty(itemId, NUMBER).getValue(); @@ -145,16 +167,15 @@ private List createEntries(Container.Indexed container ) { } return entries; } - - private void saveEntries( List entries ) { - System.out.println("Before saving entries."); + + /** + * Persist given {@link PhoneBookEntry}s. + * @param entries are the entries to persist. + */ + private int saveEntries( List entries ) { for (PhoneBookEntry entry : entries) { service.save(entry); } - System.out.println("After saving entries."); - } - - @Override - public void enter(ViewChangeListener.ViewChangeEvent event) { + return entries.size(); } } diff --git a/src/main/java/org/example/csv/CSVUtil.java b/src/main/java/org/example/csv/CSVReadUtil.java similarity index 98% rename from src/main/java/org/example/csv/CSVUtil.java rename to src/main/java/org/example/csv/CSVReadUtil.java index 3e6e9c5..9c3f6ee 100644 --- a/src/main/java/org/example/csv/CSVUtil.java +++ b/src/main/java/org/example/csv/CSVReadUtil.java @@ -11,7 +11,7 @@ import java.io.*; -public class CSVUtil { +public class CSVReadUtil { /** * Uses http://opencsv.sourceforge.net/ to read the entire contents of a CSV diff --git a/src/main/java/org/example/csv/FileBasedUploadReceptor.java b/src/main/java/org/example/csv/FileBasedUploadReceptor.java new file mode 100644 index 0000000..37055eb --- /dev/null +++ b/src/main/java/org/example/csv/FileBasedUploadReceptor.java @@ -0,0 +1,54 @@ +package org.example.csv; + +import com.vaadin.ui.Upload; + +import java.io.*; +import java.util.function.Consumer; + +/** + * Purpose of this class: A helper class to allow storage of upload (via com.vaadin.ui.Upload) in a file. + */ +public class FileBasedUploadReceptor implements Upload.Receiver, Upload.SucceededListener { + + private File tempFile; + + private Consumer readerConsumer; + + /** + * Constructor. + * @param readerConsumer is a (consuming) function of type ( Reader -> () ) that is called, when upload was successful. + */ + public FileBasedUploadReceptor(Consumer readerConsumer) { + this.readerConsumer = readerConsumer; + + try { + tempFile = File.createTempFile("temp", ".csv"); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException("Can't create temporary file."); + } + } + + @Override + public OutputStream receiveUpload(String s, String s1) { + try { + return new FileOutputStream(tempFile); + } catch (FileNotFoundException e) { + e.printStackTrace(); + throw new RuntimeException("Temporary file not found."); + } + } + + @Override + public void uploadSucceeded(Upload.SucceededEvent event) { + try { + System.out.println("Got file '" + event.getFilename() + + "' and mimetype '" + event.getMIMEType() + "'."); + FileReader fileReader = new FileReader(tempFile); + readerConsumer.accept( fileReader ); + } catch (FileNotFoundException e) { + e.printStackTrace(); + throw new RuntimeException("Problem with upload."); + } + } +} \ No newline at end of file From a7438140c9a226a1df16b88b748541266bd5bc4a Mon Sep 17 00:00:00 2001 From: Martin Senne Date: Sun, 21 Feb 2016 21:18:18 +0100 Subject: [PATCH 3/6] Improved user guidance if improper CSV is uploaded. --- src/main/java/org/example/CSVImportView.java | 30 ++++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/example/CSVImportView.java b/src/main/java/org/example/CSVImportView.java index 8dd6cbd..3ca3bb4 100644 --- a/src/main/java/org/example/CSVImportView.java +++ b/src/main/java/org/example/CSVImportView.java @@ -51,7 +51,11 @@ void init() { grid = new Grid(); grid.setWidth(100, Unit.PERCENTAGE); grid.setContainerDataSource(csvContainer); - displayUploadedData(); + boolean columnNamesValid = checkEntries(grid.getContainerDataSource()); + if ( !columnNamesValid ) { + Notification.show("Save is not possible. Schema ( column names ) of CSV not correct.", Notification.Type.WARNING_MESSAGE); + } + displayUploadedData( columnNamesValid ); } catch (IOException e) { e.printStackTrace(); @@ -65,14 +69,10 @@ void init() { saveButton = new Button("Save"); saveButton.addClickListener(clickEvent -> { Container.Indexed container = grid.getContainerDataSource(); - if ( checkEntries(grid.getContainerDataSource()) ) { - List entries = createEntries(container); - saveEntries( entries ); - Notification.show( entries.size() + " entries have been imported."); - displayUpload(); - } else { - Notification.show("Can not save entries. Schema ( column names ) of CSV not correct.", Notification.Type.WARNING_MESSAGE); - } + List entries = createEntries(container); + saveEntries( entries ); + Notification.show( entries.size() + " entries have been imported."); + displayUpload(); }); cancelButton = new Button("Cancel"); @@ -83,8 +83,10 @@ void init() { displayUpload(); } - + + // ========================================================== // ===== View related methods + // ========================================================== public void displayUpload() { removeAllComponents(); @@ -95,8 +97,10 @@ public void displayUpload() { addComponent(upload); } - public void displayUploadedData() { + public void displayUploadedData( boolean activateSave ) { removeAllComponents(); + + saveButton.setEnabled( activateSave ); HorizontalLayout hl = new HorizontalLayout(); hl.addComponent(cancelButton); @@ -117,8 +121,10 @@ public void displayUploadedData() { public void enter(ViewChangeListener.ViewChangeEvent event) { // nothing to do on enter } - + + // ========================================================== // ===== Entry related methods + // ========================================================== // TODO: To be moved to separate class, but left here to keep everything "close together". private String NAME = "name"; From 6a9592c1c8577fe02df57bddfd98a9887dfb5999 Mon Sep 17 00:00:00 2001 From: martin Date: Thu, 25 Feb 2016 13:29:08 +0100 Subject: [PATCH 4/6] Modification from "spreadsheet" branch integrated. --- src/main/java/org/example/CSVImportView.java | 8 ++-- .../example/csv/FileBasedUploadReceptor.java | 42 +++++++++++++------ 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/example/CSVImportView.java b/src/main/java/org/example/CSVImportView.java index 3ca3bb4..850360c 100644 --- a/src/main/java/org/example/CSVImportView.java +++ b/src/main/java/org/example/CSVImportView.java @@ -43,10 +43,10 @@ void init() { /** * External handler, after upload was successful. */ - Consumer consumeReader = reader -> { + Consumer consumeReader = reader -> { System.out.println(reader); try { - IndexedContainer csvContainer = CSVReadUtil.buildContainerFromCSV(reader); + IndexedContainer csvContainer = CSVReadUtil.buildContainerFromCSV(new FileReader(reader.getFile())); // this is intentional, as attaching new datasource to existing grid seems cause problems in this Vaadin version grid = new Grid(); grid.setWidth(100, Unit.PERCENTAGE); @@ -65,7 +65,9 @@ void init() { FileBasedUploadReceptor fileBasedUploadReceptor = new FileBasedUploadReceptor(consumeReader); upload.setReceiver(fileBasedUploadReceptor); upload.addSucceededListener(fileBasedUploadReceptor); - + upload.setImmediate(true); + upload.setButtonCaption("Upload CSV."); + saveButton = new Button("Save"); saveButton.addClickListener(clickEvent -> { Container.Indexed container = grid.getContainerDataSource(); diff --git a/src/main/java/org/example/csv/FileBasedUploadReceptor.java b/src/main/java/org/example/csv/FileBasedUploadReceptor.java index 37055eb..6fc0980 100644 --- a/src/main/java/org/example/csv/FileBasedUploadReceptor.java +++ b/src/main/java/org/example/csv/FileBasedUploadReceptor.java @@ -12,14 +12,38 @@ public class FileBasedUploadReceptor implements Upload.Receiver, Upload.Succeede private File tempFile; - private Consumer readerConsumer; + private Consumer fileConsumer; + + public static class FileAndInfo { + private File file; + private String filename; + private String mimetype; + + public FileAndInfo(File file, String filename, String mimetype) { + this.file = file; + this.filename = filename; + this.mimetype = mimetype; + } + + public File getFile() { + return file; + } + + public String getFilename() { + return filename; + } + + public String getMimetype() { + return mimetype; + } + } /** * Constructor. - * @param readerConsumer is a (consuming) function of type ( Reader -> () ) that is called, when upload was successful. + * @param fileConsumer is a (consuming) function of type ( Reader -> () ) that is called, when upload was successful. */ - public FileBasedUploadReceptor(Consumer readerConsumer) { - this.readerConsumer = readerConsumer; + public FileBasedUploadReceptor(Consumer fileConsumer) { + this.fileConsumer = fileConsumer; try { tempFile = File.createTempFile("temp", ".csv"); @@ -41,14 +65,8 @@ public OutputStream receiveUpload(String s, String s1) { @Override public void uploadSucceeded(Upload.SucceededEvent event) { - try { - System.out.println("Got file '" + event.getFilename() + + System.out.println("Got file '" + event.getFilename() + "' and mimetype '" + event.getMIMEType() + "'."); - FileReader fileReader = new FileReader(tempFile); - readerConsumer.accept( fileReader ); - } catch (FileNotFoundException e) { - e.printStackTrace(); - throw new RuntimeException("Problem with upload."); - } + fileConsumer.accept( new FileAndInfo(tempFile, event.getFilename(), event.getMIMEType()) ); } } \ No newline at end of file From ab9b6cfca61b9a5b9051233b14adeb9bd24fbdb2 Mon Sep 17 00:00:00 2001 From: martin Date: Thu, 25 Feb 2016 21:46:50 +0100 Subject: [PATCH 5/6] Intermediate ...... not ready for PR. At Matti: Will alter PR and eventually extract CSV import as a separate Add-On. --- src/main/java/org/example/CSVImportView.java | 2 +- .../java/org/example/csv/CSVReadUtil.java | 94 ++++++++++++++++++- .../upload}/FileBasedUploadReceptor.java | 2 +- 3 files changed, 91 insertions(+), 7 deletions(-) rename src/main/java/org/example/{csv => ui/upload}/FileBasedUploadReceptor.java (98%) diff --git a/src/main/java/org/example/CSVImportView.java b/src/main/java/org/example/CSVImportView.java index 850360c..4d3e3fe 100644 --- a/src/main/java/org/example/CSVImportView.java +++ b/src/main/java/org/example/CSVImportView.java @@ -10,7 +10,7 @@ import org.example.backend.PhoneBookEntry; import org.example.backend.PhoneBookService; import org.example.csv.CSVReadUtil; -import org.example.csv.FileBasedUploadReceptor; +import org.example.ui.upload.FileBasedUploadReceptor; import org.vaadin.cdiviewmenu.ViewMenuItem; import org.vaadin.viritin.label.Header; import org.vaadin.viritin.layouts.MVerticalLayout; diff --git a/src/main/java/org/example/csv/CSVReadUtil.java b/src/main/java/org/example/csv/CSVReadUtil.java index 9c3f6ee..624668c 100644 --- a/src/main/java/org/example/csv/CSVReadUtil.java +++ b/src/main/java/org/example/csv/CSVReadUtil.java @@ -3,12 +3,14 @@ import com.opencsv.CSVReader; import com.vaadin.data.Item; import com.vaadin.data.util.IndexedContainer; -import com.vaadin.ui.Table; -import com.vaadin.ui.Upload; -import com.vaadin.ui.VerticalLayout; -import com.vaadin.ui.Window; -import java.io.*; +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; public class CSVReadUtil { @@ -38,6 +40,88 @@ public static IndexedContainer buildContainerFromCSV(Reader reader) throws IOExc return container; } + public static class CSV { + private int columns; + private int rows; + + List header; + List> content; + + public CSV(List content ) { + this( generateHeader(content), content); + } + + public CSV(String[] header, List content ) { + this.header = Arrays.asList(header); + + this.content = new ArrayList<>(content.size()); + content.forEach( array -> this.content.add( Arrays.asList(array))); + } + + private static String[] generateHeader(List content) { + Optional optCols = checkEqualNumberOfColumns(content); + String[] header; + if (optCols.isPresent()) { + int n = optCols.get(); + header = new String[n]; + for (int i = 0; i < n; i++) { + header[i] = "Column " + Integer.toString(i); + } + } else { + throw new IllegalArgumentException(("Column size is not identical for each row.")); + } + return header; + } + + private static Optional checkEqualNumberOfColumns(List data) { + if (data.size() == 0) { + return Optional.empty(); + } + + int expected = data.get(0).length; + return data.stream().filter( p -> p.length != expected ).count() > 0 + ? Optional.of(expected) : Optional.empty(); + } + + public List getColumnNames() { + return header; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + + sb.append( fixedLengthFormat( header ) ); + content.forEach(row -> sb.append( fixedLengthFormat( row ) )); + + return sb.toString(); + + } + + public static String fixedLengthFormat(List ss) { + return ss.stream() + .map(s -> turnIntoFixedSize(s, 20)) + .collect(Collectors.joining("|", "", "")); + } + + public static String turnIntoFixedSize(String input, int s) { + int n = input.length(); + + return (n <= s) ? input + createNSpaces( n-s ) : input.substring(0, n - 1) + "…"; + } + + public static String createNSpaces( int n ) { + + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < n; i++) { + sb.append(" "); + } + return sb.toString(); + } + + } + /** * Set's up the item property ids for the container. Each is a String (of course, diff --git a/src/main/java/org/example/csv/FileBasedUploadReceptor.java b/src/main/java/org/example/ui/upload/FileBasedUploadReceptor.java similarity index 98% rename from src/main/java/org/example/csv/FileBasedUploadReceptor.java rename to src/main/java/org/example/ui/upload/FileBasedUploadReceptor.java index 6fc0980..f7dc680 100644 --- a/src/main/java/org/example/csv/FileBasedUploadReceptor.java +++ b/src/main/java/org/example/ui/upload/FileBasedUploadReceptor.java @@ -1,4 +1,4 @@ -package org.example.csv; +package org.example.ui.upload; import com.vaadin.ui.Upload; From c6b9f0a2d2c0adb57c78c39ad0b605498b5b22b2 Mon Sep 17 00:00:00 2001 From: martin Date: Thu, 25 Feb 2016 21:49:05 +0100 Subject: [PATCH 6/6] Error in string length. --- src/main/java/org/example/csv/CSVReadUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/example/csv/CSVReadUtil.java b/src/main/java/org/example/csv/CSVReadUtil.java index 624668c..7abaa65 100644 --- a/src/main/java/org/example/csv/CSVReadUtil.java +++ b/src/main/java/org/example/csv/CSVReadUtil.java @@ -107,7 +107,7 @@ public static String fixedLengthFormat(List ss) { public static String turnIntoFixedSize(String input, int s) { int n = input.length(); - return (n <= s) ? input + createNSpaces( n-s ) : input.substring(0, n - 1) + "…"; + return (n <= s) ? input + createNSpaces( n-s ) : input.substring(0, s - 1) + "…"; } public static String createNSpaces( int n ) {