/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.components.excel.common.internal; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.poi.POIXMLDocument; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.ss.formula.eval.NotImplementedException; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellValue; import org.apache.poi.ss.usermodel.DateUtil; import org.apache.poi.ss.usermodel.FormulaEvaluator; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.WorkbookFactory; import de.rcenvironment.components.excel.common.ExcelAddress; import de.rcenvironment.components.excel.common.ExcelException; import de.rcenvironment.components.excel.common.ExcelService; import de.rcenvironment.components.excel.common.ExcelUtils; import de.rcenvironment.core.datamodel.api.TypedDatum; import de.rcenvironment.core.datamodel.api.TypedDatumFactory; import de.rcenvironment.core.datamodel.api.TypedDatumService; import de.rcenvironment.core.datamodel.types.api.BooleanTD; import de.rcenvironment.core.datamodel.types.api.DateTimeTD; import de.rcenvironment.core.datamodel.types.api.FloatTD; import de.rcenvironment.core.datamodel.types.api.IntegerTD; import de.rcenvironment.core.datamodel.types.api.ShortTextTD; import de.rcenvironment.core.datamodel.types.api.SmallTableTD; /** * Excel file representation with access to its data. * * @author Markus Kunde */ public class ExcelServicePOI implements ExcelService { protected static final Log LOGGER = LogFactory.getLog(ExcelServicePOI.class); protected static final int BLOCKING_ITERATIONMAX = 600; // regarding POI interface unstable behavior protected static final int BLOCKING_SLEEP = 50; // regarding POI interface unstable behavior /* Exception messages. */ private static final String EXCMSG_EXCEL_FILE_IS_NOT_FOUND_OR_CANNOT_BE_OPENED = "Excel file is not found or cannot be opened."; private static final String EXCMSG_EXCEL_FILE_HAS_AN_INVALID_FORMAT = "Excel file has an invalid format."; private static final String EXCMSG_EXCEL_FILE_NOT_FOUND = "Excel file not found."; private static final String EXCMSG_CANNOT_SAVE_FILE_WITH_RESULT_DATA = "Cannot save file with result data."; private static final String EXCMSG_EXCEL_FILE_CANNOT_CLOSED = "Excel file access cannot be closed."; private static TypedDatumFactory typedDatumFactory; /** * Default Constructor. * */ public ExcelServicePOI() {} /** * Constructor to get typedDatumFactory not from RCE-service into ExcelService class. * * @param typedDatumFactory the typed datum factory */ public ExcelServicePOI(final TypedDatumFactory typedDatumFactory) { ExcelServicePOI.typedDatumFactory = typedDatumFactory; } protected void bindTypedDatumService(TypedDatumService newTypedDatumService) { typedDatumFactory = newTypedDatumService.getFactory(); } protected void unbindTypedDatumService(TypedDatumService oldTypedDatumService) {} /** * Just a simple test method if Excel file is really an Excel file. * * @param xlFile Excel file * @throws ExcelException thrown if not a real Excel file */ protected void initialTest(final File xlFile) throws ExcelException { try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(xlFile))) { boolean isXlsx = POIXMLDocument.hasOOXMLHeader(bis); bis.reset(); boolean isXls = POIFSFileSystem.hasPOIFSHeader(bis); if (!isXlsx && !isXls) { throw new ExcelException("Given file seems to be no Excel file"); } } catch (FileNotFoundException e) { throw new ExcelException(EXCMSG_EXCEL_FILE_NOT_FOUND, e); } catch (IllegalArgumentException e) { throw new ExcelException("Given file seems to be no Excel file", e); } catch (IOException e) { throw new ExcelException(EXCMSG_EXCEL_FILE_IS_NOT_FOUND_OR_CANNOT_BE_OPENED, e); } } @Override public void setValues(File xlFile, ExcelAddress addr, SmallTableTD values) throws ExcelException { setValues(xlFile, xlFile, addr, values); } @Override public void setValues(File xlFile, File newFile, ExcelAddress addr, SmallTableTD values) throws ExcelException { try { if (xlFile != null) { InputStream inp = null; FileOutputStream fileOut = null; inp = new FileInputStream(xlFile); try { // Setting values in Excel file org.apache.poi.ss.usermodel.Workbook wb = WorkbookFactory.create(inp); Sheet sheet = wb.getSheet(addr.getWorkSheetName()); int addressRowCorrection = addr.getBeginningRowNumber() - 1; // Excel address is // 1-based while POI is // 0-based int addressColumnCorrection = addr.getBeginningColumnNumber() - 1; // Excel address // is 1-based // while POI is // 0-based for (int row = addressRowCorrection; row < addressRowCorrection + addr.getNumberOfRows() && row <= addressRowCorrection + (values.getRowCount() - 1); row++) { Row r = sheet.getRow(row); if (r == null) { r = sheet.createRow(row); } for (int col = addressColumnCorrection; col < addressColumnCorrection + addr.getNumberOfColumns() && col <= addressColumnCorrection + (values.getColumnCount() - 1); col++) { Cell cell = r.createCell(col); TypedDatum data = values.getTypedDatumOfCell(row - addressRowCorrection, col - addressColumnCorrection); if (data == null) { continue; } switch (data.getDataType()) { case ShortText: cell.setCellType(Cell.CELL_TYPE_STRING); cell.setCellValue(((ShortTextTD) data).getShortTextValue()); break; case Float: cell.setCellType(Cell.CELL_TYPE_NUMERIC); cell.setCellValue(((FloatTD) data).getFloatValue()); break; case Integer: cell.setCellType(Cell.CELL_TYPE_NUMERIC); cell.setCellValue(((IntegerTD) data).getIntValue()); break; case Boolean: cell.setCellType(Cell.CELL_TYPE_BOOLEAN); cell.setCellValue(((BooleanTD) data).getBooleanValue()); break; case DateTime: cell.setCellType(Cell.CELL_TYPE_STRING); cell.setCellValue(((DateTimeTD) data).getDateTime().getTime()); break; case Empty: cell.setCellType(Cell.CELL_TYPE_BLANK); break; default: break; } } } /* * Solves temporarily the problem with reading from I-Stream and writing to O-Stream with the same file handle. Causes * sometimes exceptions if I-Stream is blocked when trying to write. Should be reported to Apache POI. */ for (int i = 0; i < BLOCKING_ITERATIONMAX; i++) { try { if (newFile != null) { // Write to file fileOut = new FileOutputStream(newFile); wb.write(fileOut); break; } } catch (FileNotFoundException e) { LOGGER.debug("File not found. (Method: setValueOfCells). Iteration: " + i + ". Retrying."); if (i == (BLOCKING_ITERATIONMAX - 1)) { // Last iteration was not successful throw new ExcelException(EXCMSG_CANNOT_SAVE_FILE_WITH_RESULT_DATA, e); } } catch (IOException e) { throw new ExcelException(EXCMSG_CANNOT_SAVE_FILE_WITH_RESULT_DATA, e); } try { Thread.sleep(BLOCKING_SLEEP); } catch (InterruptedException e) { LOGGER.error(e); } } } finally { if (inp != null) { try { inp.close(); } catch (IOException e) { LOGGER.error("Failed to close output stream", e); } } if (fileOut != null) { try { fileOut.flush(); fileOut.close(); } catch (IOException e) { LOGGER.error("Failed to flush or close output stream", e); } } // Not nice, but workbook-object will not released. ExcelUtils.destroyGarbage(); } // Recalculate formulas recalculateFormulas(xlFile); } } catch (FileNotFoundException e) { throw new ExcelException(EXCMSG_EXCEL_FILE_NOT_FOUND, e); } catch (InvalidFormatException e) { throw new ExcelException(EXCMSG_EXCEL_FILE_HAS_AN_INVALID_FORMAT, e); } catch (IOException e) { throw new ExcelException(EXCMSG_EXCEL_FILE_IS_NOT_FOUND_OR_CANNOT_BE_OPENED, e); } } @Override public SmallTableTD getValueOfCells(File xlFile, ExcelAddress addr) throws ExcelException { // recalculate Formulas recalculateFormulas(xlFile); // Read with POI SmallTableTD retValues = null; if (xlFile != null) { // Reads with POI InputStream inp = null; try { inp = new FileInputStream(xlFile); org.apache.poi.ss.usermodel.Workbook wb = WorkbookFactory.create(inp); Sheet sheet = wb.getSheet(addr.getWorkSheetName()); retValues = typedDatumFactory.createSmallTable(addr.getNumberOfRows(), addr.getNumberOfColumns()); int addressRowCorrection = addr.getBeginningRowNumber() - 1; // Excel address is 1-based while POI is 0-based int addressColumnCorrection = addr.getBeginningColumnNumber() - 1; // Excel address is 1-based // while POI is 0-based for (int row = addressRowCorrection; row < addressRowCorrection + addr.getNumberOfRows(); row++) { Row r = sheet.getRow(row); if (r == null) { r = sheet.createRow(row); } for (int col = addressColumnCorrection; col < addressColumnCorrection + addr.getNumberOfColumns(); col++) { Cell cell = r.getCell(col); TypedDatum data; // If cell is empty if (cell == null) { data = typedDatumFactory.createEmpty(); retValues.setTypedDatumForCell(data, row - addressRowCorrection, col - addressColumnCorrection); continue; } switch (cell.getCellType()) { case Cell.CELL_TYPE_STRING: data = typedDatumFactory.createShortText(cell.getRichStringCellValue().getString()); retValues.setTypedDatumForCell(data, row - addressRowCorrection, col - addressColumnCorrection); break; case Cell.CELL_TYPE_NUMERIC: if (DateUtil.isCellDateFormatted(cell)) { data = typedDatumFactory.createDateTime(cell.getDateCellValue().getTime()); retValues.setTypedDatumForCell(data, row - addressRowCorrection, col - addressColumnCorrection); } else { double rawNumber = cell.getNumericCellValue(); long lRawNumber = (long) rawNumber; if ((rawNumber - lRawNumber) == 0) { // For discovering if value may be of type 'long' data = typedDatumFactory.createInteger(lRawNumber); } else { data = typedDatumFactory.createFloat(rawNumber); } retValues.setTypedDatumForCell(data, row - addressRowCorrection, col - addressColumnCorrection); } break; case Cell.CELL_TYPE_BOOLEAN: data = typedDatumFactory.createBoolean(cell.getBooleanCellValue()); retValues.setTypedDatumForCell(data, row - addressRowCorrection, col - addressColumnCorrection); break; case Cell.CELL_TYPE_FORMULA: FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator(); try { CellValue cellValue = evaluator.evaluate(cell); switch (cellValue.getCellType()) { case Cell.CELL_TYPE_BOOLEAN: data = typedDatumFactory.createBoolean(cellValue.getBooleanValue()); retValues.setTypedDatumForCell(data, row - addressRowCorrection, col - addressColumnCorrection); break; case Cell.CELL_TYPE_NUMERIC: data = typedDatumFactory.createFloat(cellValue.getNumberValue()); retValues.setTypedDatumForCell(data, row - addressRowCorrection, col - addressColumnCorrection); break; case Cell.CELL_TYPE_STRING: data = typedDatumFactory.createShortText(cellValue.getStringValue()); retValues.setTypedDatumForCell(data, row - addressRowCorrection, col - addressColumnCorrection); break; default: data = typedDatumFactory.createEmpty(); retValues.setTypedDatumForCell(data, row - addressRowCorrection, col - addressColumnCorrection); break; } } catch (org.apache.poi.ss.formula.eval.NotImplementedException e) { // In case evaluator.evalualte(cell) does not implement a specific formula switch (cell.getCachedFormulaResultType()) { case Cell.CELL_TYPE_BOOLEAN: data = typedDatumFactory.createBoolean(cell.getBooleanCellValue()); retValues.setTypedDatumForCell(data, row - addressRowCorrection, col - addressColumnCorrection); break; case Cell.CELL_TYPE_NUMERIC: if (DateUtil.isCellDateFormatted(cell)) { data = typedDatumFactory.createDateTime(cell.getDateCellValue().getTime()); retValues.setTypedDatumForCell(data, row - addressRowCorrection, col - addressColumnCorrection); } else { double rawNumber = cell.getNumericCellValue(); long lRawNumber = (long) rawNumber; if ((rawNumber - lRawNumber) == 0) { // For discovering if value may be of type 'long' data = typedDatumFactory.createInteger(lRawNumber); } else { data = typedDatumFactory.createFloat(rawNumber); } retValues.setTypedDatumForCell(data, row - addressRowCorrection, col - addressColumnCorrection); } break; case Cell.CELL_TYPE_STRING: data = typedDatumFactory.createShortText(cell.getStringCellValue()); retValues.setTypedDatumForCell(data, row - addressRowCorrection, col - addressColumnCorrection); break; default: data = typedDatumFactory.createEmpty(); retValues.setTypedDatumForCell(data, row - addressRowCorrection, col - addressColumnCorrection); break; } } break; default: data = typedDatumFactory.createEmpty(); retValues.setTypedDatumForCell(data, row - addressRowCorrection, col - addressColumnCorrection); break; } } } } catch (FileNotFoundException e) { throw new ExcelException(EXCMSG_EXCEL_FILE_NOT_FOUND, e); } catch (InvalidFormatException e) { throw new ExcelException(EXCMSG_EXCEL_FILE_HAS_AN_INVALID_FORMAT, e); } catch (IOException e) { throw new ExcelException(EXCMSG_EXCEL_FILE_IS_NOT_FOUND_OR_CANNOT_BE_OPENED, e); } finally { if (inp != null) { try { inp.close(); } catch (IOException e) { LOGGER.error("Failed to close input stream", e); } } // Not nice, but workbook-object will not released. ExcelUtils.destroyGarbage(); } } return retValues; } @Override public ExcelAddress[] getUserDefinedCellNames(File xlFile) throws ExcelException { InputStream inp = null; ExcelAddress[] names; try { inp = new FileInputStream(xlFile); org.apache.poi.ss.usermodel.Workbook wb = WorkbookFactory.create(inp); int noNames = wb.getNumberOfNames(); names = new ExcelAddress[noNames]; for (int i = 0; i < noNames; i++) { names[i] = new ExcelAddress(xlFile, wb.getNameAt(i).getNameName()); } } catch (FileNotFoundException e) { throw new ExcelException(EXCMSG_EXCEL_FILE_NOT_FOUND, e); } catch (InvalidFormatException e) { throw new ExcelException(EXCMSG_EXCEL_FILE_HAS_AN_INVALID_FORMAT, e); } catch (IOException e) { throw new ExcelException(EXCMSG_EXCEL_FILE_IS_NOT_FOUND_OR_CANNOT_BE_OPENED, e); } catch (IllegalArgumentException e) { throw new ExcelException(EXCMSG_EXCEL_FILE_IS_NOT_FOUND_OR_CANNOT_BE_OPENED, e); } finally { if (inp != null) { try { inp.close(); } catch (IOException e) { throw new ExcelException(EXCMSG_EXCEL_FILE_CANNOT_CLOSED, e); } } // Not nice, but workbook-object will not released. ExcelUtils.destroyGarbage(); } return names; } @Override public String[] getMacros(File xlFile) throws ExcelException { throw new ExcelException("Excel is using POI implementation only. Cannot receive macro names."); } @Override public void runMacro(File xlFile, String macroname) throws ExcelException { throw new ExcelException("Excel is using POI implementation only. Cannot execute macro."); } @Override public void recalculateFormulas(File xlFile) throws ExcelException { InputStream inp = null; try { inp = new FileInputStream(xlFile); org.apache.poi.ss.usermodel.Workbook wb = WorkbookFactory.create(inp); FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator(); for (int sheetNum = 0; sheetNum < wb.getNumberOfSheets(); sheetNum++) { Sheet sheet = wb.getSheetAt(sheetNum); for (Row r : sheet) { for (Cell c : r) { if (c.getCellType() == Cell.CELL_TYPE_FORMULA) { evaluator.evaluateFormulaCell(c); } } } } } catch (NotImplementedException e) { throw new ExcelException("Tried to evaluate unknow formula", e); } catch (FileNotFoundException e) { throw new ExcelException(EXCMSG_EXCEL_FILE_NOT_FOUND, e); } catch (InvalidFormatException e) { throw new ExcelException(EXCMSG_EXCEL_FILE_HAS_AN_INVALID_FORMAT, e); } catch (IOException e) { throw new ExcelException(EXCMSG_EXCEL_FILE_IS_NOT_FOUND_OR_CANNOT_BE_OPENED, e); } finally { if (inp != null) { try { inp.close(); } catch (IOException e) { throw new ExcelException(EXCMSG_EXCEL_FILE_CANNOT_CLOSED, e); } } // Not nice, but workbook-object will not released. ExcelUtils.destroyGarbage(); } } @Override public boolean isValidExcelFile(File xlFile) { if (xlFile == null) { return false; } try { initialTest(xlFile); } catch (ExcelException e) { return false; } return true; } }