From f37bb280dcc944091cf8b431e5fe1f8f966b40b5 Mon Sep 17 00:00:00 2001 From: dmicol Date: Sat, 4 Nov 2017 17:04:10 +0100 Subject: [PATCH] Implement export functionality #21 added base support for exporters added base support for excel exporter --- pom.xml | 5 + .../org/vaadin/crudui/crud/AbstractCrud.java | 30 +- .../org/vaadin/crudui/crud/impl/GridCrud.java | 38 ++- .../crudui/support/BeanExcelBuilder.java | 285 ++++++++++++++++++ .../support/ExcelOnDemandStreamResource.java | 41 +++ 5 files changed, 388 insertions(+), 11 deletions(-) create mode 100644 src/main/java/org/vaadin/crudui/support/BeanExcelBuilder.java create mode 100644 src/main/java/org/vaadin/crudui/support/ExcelOnDemandStreamResource.java diff --git a/pom.xml b/pom.xml index 39d877b..99b1ee5 100644 --- a/pom.xml +++ b/pom.xml @@ -140,6 +140,11 @@ 4.8.1 test + + org.apache.poi + poi-ooxml + 3.17 + diff --git a/src/main/java/org/vaadin/crudui/crud/AbstractCrud.java b/src/main/java/org/vaadin/crudui/crud/AbstractCrud.java index 2580c58..f35d800 100755 --- a/src/main/java/org/vaadin/crudui/crud/AbstractCrud.java +++ b/src/main/java/org/vaadin/crudui/crud/AbstractCrud.java @@ -1,10 +1,17 @@ package org.vaadin.crudui.crud; -import com.vaadin.ui.Composite; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.vaadin.crudui.form.CrudFormFactory; import org.vaadin.crudui.layout.CrudLayout; +import org.vaadin.crudui.support.BeanExcelBuilder; +import org.vaadin.crudui.support.ExcelOnDemandStreamResource; -import java.util.Collections; +import com.vaadin.server.Resource; +import com.vaadin.ui.Composite; /** * @author Alejandro Duarte @@ -17,6 +24,7 @@ public abstract class AbstractCrud extends Composite implements Crud { protected AddOperationListener addOperation = t -> null; protected UpdateOperationListener updateOperation = t -> null; protected DeleteOperationListener deleteOperation = t -> { }; + protected Map exportOperations = new HashMap<>(); protected CrudLayout crudLayout; protected CrudFormFactory crudFormFactory; @@ -25,6 +33,13 @@ public AbstractCrud(Class domainType, CrudLayout crudLayout, CrudFormFactory< this.domainType = domainType; this.crudLayout = crudLayout; this.crudFormFactory = crudFormFactory; + exportOperations.put("EXCEL", new ExcelOnDemandStreamResource() { + + @Override + protected XSSFWorkbook getWorkbook() { + return new BeanExcelBuilder(domainType).createExcelDocument(findAllOperation.findAll()); + } + }); if (crudListener != null) { setCrudListener(crudListener); @@ -89,4 +104,15 @@ public void setCrudListener(CrudListener crudListener) { setFindAllOperation(crudListener::findAll); } + public void addExporter(String name, Resource exporter) { + exportOperations.put(name, exporter); + } + + public void removeExporter(String name) { + exportOperations.remove(name); + } + + public Resource getExporter(String name) { + return exportOperations.get(name); + } } diff --git a/src/main/java/org/vaadin/crudui/crud/impl/GridCrud.java b/src/main/java/org/vaadin/crudui/crud/impl/GridCrud.java index 538994c..83083fa 100755 --- a/src/main/java/org/vaadin/crudui/crud/impl/GridCrud.java +++ b/src/main/java/org/vaadin/crudui/crud/impl/GridCrud.java @@ -1,11 +1,8 @@ package org.vaadin.crudui.crud.impl; -import com.vaadin.data.provider.Query; -import com.vaadin.icons.VaadinIcons; -import com.vaadin.ui.Button; -import com.vaadin.ui.Component; -import com.vaadin.ui.Grid; -import com.vaadin.ui.Notification; +import java.util.Collection; +import java.util.LinkedHashMap; + import org.vaadin.crudui.crud.AbstractCrud; import org.vaadin.crudui.crud.CrudListener; import org.vaadin.crudui.crud.CrudOperation; @@ -15,7 +12,14 @@ import org.vaadin.crudui.layout.CrudLayout; import org.vaadin.crudui.layout.impl.WindowBasedCrudLayout; -import java.util.Collection; +import com.vaadin.data.provider.Query; +import com.vaadin.icons.VaadinIcons; +import com.vaadin.server.FileDownloader; +import com.vaadin.server.FontAwesome; +import com.vaadin.ui.Button; +import com.vaadin.ui.Component; +import com.vaadin.ui.Grid; +import com.vaadin.ui.Notification; /** * @author Alejandro Duarte @@ -32,6 +36,7 @@ public class GridCrud extends AbstractCrud { protected Button deleteButton; protected Grid grid; + protected LinkedHashMap exporterButtons = new LinkedHashMap<>(); protected Collection items; public GridCrud(Class domainType) { @@ -64,7 +69,7 @@ protected void initLayout() { findAllButton.setDescription("Refresh list"); findAllButton.setIcon(VaadinIcons.REFRESH); crudLayout.addToolbarComponent(findAllButton); - + addButton = new Button("", e -> addButtonClicked()); addButton.setDescription("Add"); addButton.setIcon(VaadinIcons.PLUS); @@ -84,6 +89,10 @@ protected void initLayout() { grid.setSizeFull(); grid.addSelectionListener(e -> gridSelectionChanged()); crudLayout.setMainComponent(grid); + + Button btn = new Button(FontAwesome.FILE_EXCEL_O.getHtml()); + btn.setCaptionAsHtml(true); + addExporterMenu("EXCEL", btn); updateButtons(); } @@ -249,9 +258,20 @@ public void setRowCountCaption(String rowCountCaption) { public void setSavedMessage(String savedMessage) { this.savedMessage = savedMessage; } - + public void setDeletedMessage(String deletedMessage) { this.deletedMessage = deletedMessage; } + + public Button getExporterMenu(String name) { + return exporterButtons.get(name); + } + public GridCrud addExporterMenu(String name, Button exporterButton) { + exporterButtons.put(name, exporterButton); + new FileDownloader(getExporter(name)).extend(exporterButton); + crudLayout.addToolbarComponent(exporterButton); + + return this; + } } \ No newline at end of file diff --git a/src/main/java/org/vaadin/crudui/support/BeanExcelBuilder.java b/src/main/java/org/vaadin/crudui/support/BeanExcelBuilder.java new file mode 100644 index 0000000..216a9f4 --- /dev/null +++ b/src/main/java/org/vaadin/crudui/support/BeanExcelBuilder.java @@ -0,0 +1,285 @@ +package org.vaadin.crudui.support; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.CreationHelper; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.RichTextString; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFCell; +import org.apache.poi.xssf.usermodel.XSSFRow; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +import com.vaadin.data.BeanPropertySet; +import com.vaadin.data.PropertyDefinition; +import com.vaadin.data.PropertySet; +import com.vaadin.shared.util.SharedUtil; + +public class BeanExcelBuilder { + + public static final String dateCellStyleFormat = "m/d/yy"; + public static final String dateTimeCellStyleFormat = "m/d/yy hh:mm:ss"; + + private Class clazz; + private int startCol; + private int startRow; + private int currCol; + private String sheetName; + + private List properties; + private List columnsHeaders; + + private Map formats = new HashMap<>(); + + public BeanExcelBuilder(Class clazz) { + this.clazz = clazz; + } + + protected XSSFSheet buildSheet(XSSFWorkbook wb) { + if (sheetName!=null) { + return wb.createSheet(sheetName); + } else { + return wb.createSheet(); + } + } + + protected XSSFWorkbook buildWorkBook() { + return new XSSFWorkbook(); + } + + public XSSFWorkbook createExcelDocument(Collection beans) { + XSSFWorkbook wb = buildWorkBook(); + XSSFSheet s = buildSheet(wb); + PropertySet propertySet = BeanPropertySet.get(clazz); + + int rn=0; + + int cn=0; + XSSFRow r = s.createRow(rn++); + + List headers = columnsHeaders; + if (headers==null) { + headers = propertySet.getProperties().map(pd -> SharedUtil.propertyIdToHumanFriendly(pd.getName())).collect(Collectors.toList()); + } + startHeaderRow(r); + for (String ch : headers) { + XSSFCell cell = r.createCell(cn++); + cell.setCellValue(ch); + setHeaderStyle(cell, ch); + } + endHeaderRow(r); + + for(T bean: beans) { + r = s.createRow(rn++); + cn=0; + + currCol = startCol; + doStarRow(bean, r); + List props = properties; + if (props==null) { + props = propertySet.getProperties().map(pd -> pd.getName()).collect(Collectors.toList()); + } + for(String propertyName : props) { + + PropertyDefinition definition = propertySet + .getProperty(propertyName) + .orElseThrow(() -> new IllegalArgumentException( + "Could not resolve property name " + propertyName + )); + + Object value = definition.getGetter().apply(bean); + + Cell cell = buildCell(r, currCol, definition); + setCellValue(cell, value, definition); + setCellStyle(cell, value, definition, formats.get(propertyName)); + currCol++; + + } + doEndRow(bean, r); + + } + + return wb; + } + + protected void endHeaderRow(XSSFRow r) { + } + + protected void startHeaderRow(XSSFRow r) { + } + + protected void setHeaderStyle(XSSFCell cell, String headerName) { + } + + protected void doEndRow(T object, Row row) { + + } + + protected Cell buildCell(Row row, int colNumber, PropertyDefinition definition) { + Cell cell = row.createCell(colNumber); + + return cell; + } + + protected void doStarRow(T object, Row row) { + } + + protected CellStyle getCellStyle(Cell cell, String format) { + Workbook wb = cell.getRow().getSheet().getWorkbook(); + CreationHelper createHelper = wb.getCreationHelper(); + CellStyle dateCellStyle = wb.createCellStyle(); + dateCellStyle.setDataFormat(createHelper.createDataFormat().getFormat(format)); + + return dateCellStyle; + } + + protected void setCellValue(Cell cell, Object value, PropertyDefinition definition) { + + if (String.class.isAssignableFrom(definition.getType())) { + cell.setCellType(CellType.STRING); + } else if (Date.class.isAssignableFrom(definition.getType())) { + cell.setCellType(CellType.NUMERIC); + } else if (Double.class.isAssignableFrom(definition.getType())) { + cell.setCellType(CellType.NUMERIC); + } else if (Number.class.isAssignableFrom(definition.getType())) { + cell.setCellType(CellType.NUMERIC); + } else if (Boolean.class.isAssignableFrom(definition.getType())) { + cell.setCellType(CellType.BOOLEAN); + } else if (LocalDate.class.isAssignableFrom(definition.getType())) { + cell.setCellType(CellType.NUMERIC); + } else if (LocalDateTime.class.isAssignableFrom(definition.getType())) { + cell.setCellType(CellType.NUMERIC); + } else if (definition.getType().isEnum()) { + cell.setCellType(CellType.STRING); + } else if (Calendar.class.isAssignableFrom(definition.getType())) { + cell.setCellType(CellType.NUMERIC); + } else if (RichTextString.class.isAssignableFrom(definition.getType())) { + cell.setCellType(CellType.STRING); + } + + + if (value==null) + return; + + if (String.class.isInstance(value)) { + cell.setCellValue((String) value); + } else if (Date.class.isInstance(value)) { + cell.setCellValue((Date) value); + } else if (Double.class.isInstance(value)) { + cell.setCellValue((Double) value); + } else if (Number.class.isInstance(value)) { + cell.setCellValue(((Number) value).doubleValue()); + } else if (Boolean.class.isInstance(value)) { + cell.setCellValue((Boolean) value); + + } else if (LocalDate.class.isInstance(value)) { + cell.setCellValue(DateUtil.getExcelDate(java.sql.Date.valueOf((LocalDate)value))); + } else if (LocalDateTime.class.isInstance(value)) { + cell.setCellValue(DateUtil.getExcelDate(java.sql.Timestamp.valueOf((LocalDateTime)value))); + + } else if (definition.getType().isEnum()) { + cell.setCellValue(((Enum)value).name()); + + } else if (Calendar.class.isInstance(value)) { + cell.setCellValue((Calendar) value); + } else if (RichTextString.class.isInstance(value)) { + cell.setCellValue((RichTextString) value); + } + + } + + protected void setCellStyle(Cell cell, Object value, PropertyDefinition definition, String format) { + + CellStyle cs = null; + + if (format==null) { + + if (String.class.isAssignableFrom(definition.getType())) { + } else if (Date.class.isAssignableFrom(definition.getType())) { + cs = getCellStyle(cell, dateTimeCellStyleFormat); + } else if (Double.class.isAssignableFrom(definition.getType())) { + } else if (Number.class.isAssignableFrom(definition.getType())) { + } else if (Boolean.class.isAssignableFrom(definition.getType())) { + } else if (LocalDate.class.isAssignableFrom(definition.getType())) { + cs = getCellStyle(cell, dateCellStyleFormat); + } else if (LocalDateTime.class.isAssignableFrom(definition.getType())) { + cs = getCellStyle(cell, dateTimeCellStyleFormat); + } else if (definition.getType().isEnum()) { + } else if (Calendar.class.isAssignableFrom(definition.getType())) { + cs = getCellStyle(cell, dateTimeCellStyleFormat); + } else if (RichTextString.class.isAssignableFrom(definition.getType())) { + } + } else { + cs = getCellStyle(cell, format); + } + + if (cs!=null) + cell.setCellStyle(cs); + } + + public int getStartCol() { + return startCol; + } + + public void setStartCol(int startCol) { + this.startCol = startCol; + } + + public int getStartRow() { + return startRow; + } + + public void setStartRow(int startRow) { + this.startRow = startRow; + } + + public Collection getProperties() { + return properties; + } + + public void setProperties(List properties) { + this.properties = properties; + } + + public void setProperties(String... properties) { + this.properties = new ArrayList<>(); + Collections.addAll(this.properties, properties); + } + + public Collection getColumnsHeaders() { + return columnsHeaders; + } + + public void setColumnsHeaders(List columnsHeaders) { + this.columnsHeaders = columnsHeaders; + } + + public void setColumnsHeaders(String... columnsHeaders) { + this.columnsHeaders = new ArrayList<>(); + Collections.addAll(this.columnsHeaders, columnsHeaders); + } + + public Map getFormats() { + return formats; + } + + public void setFormats(Map formats) { + this.formats = formats; + } + +} diff --git a/src/main/java/org/vaadin/crudui/support/ExcelOnDemandStreamResource.java b/src/main/java/org/vaadin/crudui/support/ExcelOnDemandStreamResource.java new file mode 100644 index 0000000..784eb13 --- /dev/null +++ b/src/main/java/org/vaadin/crudui/support/ExcelOnDemandStreamResource.java @@ -0,0 +1,41 @@ +package org.vaadin.crudui.support; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +import com.vaadin.server.ConnectorResource; +import com.vaadin.server.DownloadStream; + +public abstract class ExcelOnDemandStreamResource implements ConnectorResource { + + protected abstract XSSFWorkbook getWorkbook(); + + @Override + public DownloadStream getStream() { + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + getWorkbook().write(baos); + baos.flush(); + } catch (IOException e) { + e.printStackTrace(); + } + + return new DownloadStream(new ByteArrayInputStream(baos.toByteArray()), getMIMEType(), getFilename()); + } + + @Override + public String getFilename() { + return "export.xlsx"; + } + + @Override + public String getMIMEType() { + return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + } + + +}