diff --git a/MANIFEST.MF b/MANIFEST.MF new file mode 100644 index 0000000..f69515c --- /dev/null +++ b/MANIFEST.MF @@ -0,0 +1,5 @@ +Manifest-Version: 1.0 +Deploy-Class: +Plugin-Name: Excel Reader +Plugin-Version: 1.0 +Author: Xander diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..6402f86 --- /dev/null +++ b/build.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/conf/toolbox-ext.xml b/conf/toolbox-ext.xml new file mode 100644 index 0000000..57518c1 --- /dev/null +++ b/conf/toolbox-ext.xml @@ -0,0 +1,6 @@ + + excel + application + nl.isaac.dotcms.excelreader.viewtool.ExcelReaderTool + + diff --git a/lib/poi-ooxml-3.6-20091214.jar b/lib/poi-ooxml-3.6-20091214.jar new file mode 100644 index 0000000..c986646 Binary files /dev/null and b/lib/poi-ooxml-3.6-20091214.jar differ diff --git a/src/nl/isaac/dotcms/excelreader/shared/CacheGroupHandler.java b/src/nl/isaac/dotcms/excelreader/shared/CacheGroupHandler.java new file mode 100644 index 0000000..eeedbd5 --- /dev/null +++ b/src/nl/isaac/dotcms/excelreader/shared/CacheGroupHandler.java @@ -0,0 +1,86 @@ +package nl.isaac.dotcms.excelreader.shared; +/** +* ExcelReader by ISAAC - The Full Service Internet Agency is licensed +* under a Creative Commons Attribution 3.0 Unported License +* - http://creativecommons.org/licenses/by/3.0/ +* - http://www.geekyplugins.com/ +* +* @copyright Copyright (c) 2011 ISAAC Software Solutions B.V. (http://www.isaac.nl) +*/ + +import java.util.Map; +import java.util.Map.Entry; + +import com.dotmarketing.business.CacheLocator; +import com.dotmarketing.business.DotCacheAdministrator; +import com.dotmarketing.business.DotCacheException; +import com.dotmarketing.util.Logger; +/** + * Class that handles the dotCMS cache. It uses an ItemHandler to retrieve items that aren't stored in the cache yet + * + * @author xander + * + * @param + */ +public class CacheGroupHandler { + private String groupName; + protected ItemHandler itemHandler; + + public CacheGroupHandler(String groupName, ItemHandler itemHandler) { + this.groupName = groupName; + this.itemHandler = itemHandler; + } + + public T get(String key) { + DotCacheAdministrator cache = CacheLocator.getCacheAdministrator(); + Object o = null; + + if(!itemHandler.isChanged(key)) { + try { + o = cache.get(key, groupName); + } catch (DotCacheException e) { + Logger.info(this.getClass(), String.format("DotCacheException for Group '%s', key '%s', message: %s", groupName, key, e.getMessage())); + } + } + + if(o == null) { + T t = itemHandler.get(key); + put(key, t); + return t; + } else { + return (T)o; + } + } + + public void put(String key, T t) { + DotCacheAdministrator cache = CacheLocator.getCacheAdministrator(); + cache.put(key, t, groupName); + } + + /** + * Updates the given key by calling the itemhandler's get method + */ + public void updateWithItemHandler(String key) { + remove(key); + put(key, itemHandler.get(key)); + } + + public void remove(String key) { + DotCacheAdministrator cache = CacheLocator.getCacheAdministrator(); + cache.remove(key, groupName); + } + + public void fillInitialCache() { + removeAll(); + Map initialCache = itemHandler.getInitialCache(); + for(Entry entry: initialCache.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + public void removeAll() { + DotCacheAdministrator cache = CacheLocator.getCacheAdministrator(); + cache.flushGroup(groupName); + } + +} diff --git a/src/nl/isaac/dotcms/excelreader/shared/ItemHandler.java b/src/nl/isaac/dotcms/excelreader/shared/ItemHandler.java new file mode 100644 index 0000000..974c13c --- /dev/null +++ b/src/nl/isaac/dotcms/excelreader/shared/ItemHandler.java @@ -0,0 +1,24 @@ +package nl.isaac.dotcms.excelreader.shared; +/** +* ExcelReader by ISAAC - The Full Service Internet Agency is licensed +* under a Creative Commons Attribution 3.0 Unported License +* - http://creativecommons.org/licenses/by/3.0/ +* - http://www.geekyplugins.com/ +* +* @copyright Copyright (c) 2011 ISAAC Software Solutions B.V. (http://www.isaac.nl) +*/ + +import java.util.Map; + +/** + * Interface for handling an item that is not available in the cache + * + * @author xander + * + * @param + */ +public interface ItemHandler { + public T get(String key); + public boolean isChanged(String key); + public Map getInitialCache(); +} \ No newline at end of file diff --git a/src/nl/isaac/dotcms/excelreader/util/DefaultRowStrategy.java b/src/nl/isaac/dotcms/excelreader/util/DefaultRowStrategy.java new file mode 100644 index 0000000..725fb60 --- /dev/null +++ b/src/nl/isaac/dotcms/excelreader/util/DefaultRowStrategy.java @@ -0,0 +1,33 @@ +package nl.isaac.dotcms.excelreader.util; +/** +* ExcelReader by ISAAC - The Full Service Internet Agency is licensed +* under a Creative Commons Attribution 3.0 Unported License +* - http://creativecommons.org/licenses/by/3.0/ +* - http://www.geekyplugins.com/ +* +* @copyright Copyright (c) 2011 ISAAC Software Solutions B.V. (http://www.isaac.nl) +*/ + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import nl.isaac.dotcms.excelreader.util.ExcelUtil.RowStrategy; +/** + * This row strategy just returns given row + * + * @author xander + * + */ +public class DefaultRowStrategy implements RowStrategy { + private List> data = new ArrayList>(); + + public void executeRow(Map row) throws Exception { + data.add(row); + } + + public List> getData() { + return data; + } + +} diff --git a/src/nl/isaac/dotcms/excelreader/util/ExcelReaderCacheGroupHandler.java b/src/nl/isaac/dotcms/excelreader/util/ExcelReaderCacheGroupHandler.java new file mode 100644 index 0000000..50b6f19 --- /dev/null +++ b/src/nl/isaac/dotcms/excelreader/util/ExcelReaderCacheGroupHandler.java @@ -0,0 +1,36 @@ +package nl.isaac.dotcms.excelreader.util; +/** +* ExcelReader by ISAAC - The Full Service Internet Agency is licensed +* under a Creative Commons Attribution 3.0 Unported License +* - http://creativecommons.org/licenses/by/3.0/ +* - http://www.geekyplugins.com/ +* +* @copyright Copyright (c) 2011 ISAAC Software Solutions B.V. (http://www.isaac.nl) +*/ + +import java.util.List; +import java.util.Map; + +import nl.isaac.dotcms.excelreader.shared.CacheGroupHandler; +/** + * This class handles the cache for the Excelreader plugin + * + * @author xander + * + */ +public class ExcelReaderCacheGroupHandler extends CacheGroupHandler>> { + private static ExcelReaderCacheGroupHandler cache; + + private ExcelReaderCacheGroupHandler() { + super("ExcelReader_plugin", new ExcelReaderItemHandler()); + } + + public static ExcelReaderCacheGroupHandler getInstance() { + if(cache == null) { + cache = new ExcelReaderCacheGroupHandler(); + } + return cache; + } + + +} diff --git a/src/nl/isaac/dotcms/excelreader/util/ExcelReaderItemHandler.java b/src/nl/isaac/dotcms/excelreader/util/ExcelReaderItemHandler.java new file mode 100644 index 0000000..520a8be --- /dev/null +++ b/src/nl/isaac/dotcms/excelreader/util/ExcelReaderItemHandler.java @@ -0,0 +1,73 @@ +package nl.isaac.dotcms.excelreader.util; +/** +* ExcelReader by ISAAC - The Full Service Internet Agency is licensed +* under a Creative Commons Attribution 3.0 Unported License +* - http://creativecommons.org/licenses/by/3.0/ +* - http://www.geekyplugins.com/ +* +* @copyright Copyright (c) 2011 ISAAC Software Solutions B.V. (http://www.isaac.nl) +*/ + +import java.io.File; +import java.io.FileInputStream; +import java.util.Calendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.dotmarketing.util.Logger; + +import nl.isaac.dotcms.excelreader.shared.ItemHandler; + +/** + * A class that handles the importing of a single Excel file. Using the CacheGroupHandler/ItemHandler classes, the result is stored in the dotCMS cache. + * + * @author xander + */ + +public class ExcelReaderItemHandler implements ItemHandler>> { + private Map lastModDates = new HashMap(); + + /** + * @return whether the key has changed since it was last requested + */ + public boolean isChanged(String key) { + try { + if(lastModDates.containsKey(key)) { + File file = new File(key); + return file == null || !lastModDates.get(key).equals(file.lastModified()); + } else { + return true; + } + } catch (Throwable t) { + Logger.warn(this.getClass(), "Exception while checking date in file '" + key + "'", t); + return false; + } + } + + /** + * @return an excel sheet as a List of Maps. The key is the location of the file (on the harddisk) + */ + public List> get(String key) { + Calendar start = Calendar.getInstance(); + try { + File file = new File(key); + FileInputStream fis = new FileInputStream(file); + DefaultRowStrategy strategy = new DefaultRowStrategy(); + ExcelUtilStatus status = new ExcelUtilStatus(); + ExcelUtil.executeStrategyOnExcelSheet(fis, strategy, status); + Logger.info(this.getClass(), "Reading of excel '" + key + "' took " + (Calendar.getInstance().getTimeInMillis() - start.getTimeInMillis()) + "ms"); + lastModDates.put(key, file.lastModified()); + return strategy.getData(); + } catch (Throwable t) { + Logger.error(this.getClass(), "Error while reading excel", t); + return null; + } + } + + + public Map>> getInitialCache() { + return new HashMap>>(); + } + +} diff --git a/src/nl/isaac/dotcms/excelreader/util/ExcelUtil.java b/src/nl/isaac/dotcms/excelreader/util/ExcelUtil.java new file mode 100644 index 0000000..84df166 --- /dev/null +++ b/src/nl/isaac/dotcms/excelreader/util/ExcelUtil.java @@ -0,0 +1,133 @@ +package nl.isaac.dotcms.excelreader.util; +/** +* ExcelReader by ISAAC - The Full Service Internet Agency is licensed +* under a Creative Commons Attribution 3.0 Unported License +* - http://creativecommons.org/licenses/by/3.0/ +* - http://www.geekyplugins.com/ +* +* @copyright Copyright (c) 2011 ISAAC Software Solutions B.V. (http://www.isaac.nl) +*/ + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; + +import com.dotmarketing.util.Logger; + +public class ExcelUtil { + + /** + * Parses an excel sheet (in the FileInputStream). The first row is read and stored in a Map, + * all the other rows are parsed using the given RowStrategy. All thrown exceptions in the row + * are stored in the returned ExcelUtilStatus and returned after executing the stategy. + * + * @param fis The FileInputStream containing the excel sheet (.xls, not .xlsx) + * @param rowStrategy The method to perform on all the rows except for the header row + * @return an ExcelUtilStatus containing info about the executed rows + * @throws IOException when there's a problem with the excel sheet file + */ + public static void executeStrategyOnExcelSheet(FileInputStream fis, RowStrategy rowStrategy, ExcelUtilStatus status) throws IOException { + Map headerMapping = new HashMap(); + try { + Workbook workbook = WorkbookFactory.create(fis); + Sheet sheet = workbook.getSheetAt(0); + status.setTotalNumberOfRows(sheet.getLastRowNum()); + Iterator rowIterator = sheet.rowIterator(); + while (rowIterator.hasNext()) { + status.newRow(); + Row row = rowIterator.next(); + if(status.getCurrentRowNumber() == 1) { + headerMapping = readHeaderMapping(row); + } else { + try { + Map content = getRowAsMap(row, headerMapping); + rowStrategy.executeRow(content); + status.addSuccesfulRow(); + } catch (Exception e) { + status.addFailedRowWithException(e); + } + } + } + } catch(InvalidFormatException e) { + Logger.error(ExcelUtil.class, "Can't read Excel file", e); + } finally { + fis.close(); + } + + status.setFinished(); + + } + + /** + * Read a row and use that row as the headers + * @return + */ + public static Map readHeaderMapping(Row row) { + List rowAsList = getRowAsList(row); + Map headerMapping = new HashMap(); + for(int i=0; i getRowAsList(Row row) { + List content = new ArrayList(); + Iterator cellIterator = row.cellIterator(); + while (cellIterator.hasNext()) { + Cell cell = cellIterator.next(); + switch(cell.getCellType()) { + case Cell.CELL_TYPE_BLANK: content.add(null); break; + case Cell.CELL_TYPE_BOOLEAN: content.add(Boolean.valueOf(cell.getBooleanCellValue())); break; + case Cell.CELL_TYPE_NUMERIC: if(DateUtil.isCellDateFormatted(cell)) {content.add(cell.getDateCellValue());} else {content.add(Double.valueOf(cell.getNumericCellValue()));}; break; + default: content.add(cell.getStringCellValue()); + } + } + + return content; + } + + /** + * @return a row as a Map, based on a headerMapping (columnName, columnIndex) + */ + public static Map getRowAsMap(Row row, Map headerMapping) throws IllegalArgumentException { + List content = getRowAsList(row); + if(content.size() != headerMapping.size()) { + throw new IllegalArgumentException("The size of the row is not the correct size. Header size = " + headerMapping.size() + " and row size = " + content.size()); + } + + Map rowMap = new HashMap(); + for(Entry mapping: headerMapping.entrySet()) { + rowMap.put(mapping.getKey(), content.get(mapping.getValue())); + } + + return rowMap; + } + + /** + * A strategy that can be performed on a given row. + * An exception can be thrown when there's something wrong with the data. + */ + public interface RowStrategy { + public void executeRow(Map row) throws Exception; + } +} diff --git a/src/nl/isaac/dotcms/excelreader/util/ExcelUtilStatus.java b/src/nl/isaac/dotcms/excelreader/util/ExcelUtilStatus.java new file mode 100644 index 0000000..c365e6a --- /dev/null +++ b/src/nl/isaac/dotcms/excelreader/util/ExcelUtilStatus.java @@ -0,0 +1,82 @@ +package nl.isaac.dotcms.excelreader.util; +/** +* ExcelReader by ISAAC - The Full Service Internet Agency is licensed +* under a Creative Commons Attribution 3.0 Unported License +* - http://creativecommons.org/licenses/by/3.0/ +* - http://www.geekyplugins.com/ +* +* @copyright Copyright (c) 2011 ISAAC Software Solutions B.V. (http://www.isaac.nl) +*/ + +import java.util.Map; +import java.util.TreeMap; + +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; + +/** + * The ExcelUtilStatus can be used to store import status messages + * + */ +public class ExcelUtilStatus { + private int currentRowNumber = 0; + private int numberOfImportedRows = 0; + private int totalNumberOfRows = 0; + private Map exceptions = new TreeMap(); + private boolean finished = false; + + + void addFailedRowWithException(Exception exception) { + exceptions.put(currentRowNumber, exception); + } + + void addSuccesfulRow() { + numberOfImportedRows++; + } + + void newRow() { + currentRowNumber++; + } + + void setFinished() { + this.finished = true; + } + + void setTotalNumberOfRows(int numberOfRows) { + this.totalNumberOfRows = numberOfRows; + } + + public int getCurrentRowNumber() { + return currentRowNumber; + } + + public int getNumberOfImportedRows() { + return numberOfImportedRows; + } + + public int getNumberOfFailedRows() { + return exceptions.size(); + } + + public Map getMapWithRowNumbersAndExceptions() { + return exceptions; + } + + public boolean isFinished() { + return finished; + } + + public JSONObject toJSON() throws JSONException { + JSONObject status = new JSONObject(); + status.put("currentRowNumber", Integer.valueOf(currentRowNumber)); + status.put("totalNumberOfRows", Integer.valueOf(totalNumberOfRows)); + status.put("numberOfImportedRows", Integer.valueOf(numberOfImportedRows)); + status.put("finished", Boolean.valueOf(finished)); + return status; + } + + public int getTotalNumberOfRows() { + return this.totalNumberOfRows; + } + +} diff --git a/src/nl/isaac/dotcms/excelreader/viewtool/ExcelReaderTool.java b/src/nl/isaac/dotcms/excelreader/viewtool/ExcelReaderTool.java new file mode 100644 index 0000000..c4a3869 --- /dev/null +++ b/src/nl/isaac/dotcms/excelreader/viewtool/ExcelReaderTool.java @@ -0,0 +1,84 @@ +package nl.isaac.dotcms.excelreader.viewtool; +/** +* ExcelReader by ISAAC - The Full Service Internet Agency is licensed +* under a Creative Commons Attribution 3.0 Unported License +* - http://creativecommons.org/licenses/by/3.0/ +* - http://www.geekyplugins.com/ +* +* @copyright Copyright (c) 2011 ISAAC Software Solutions B.V. (http://www.isaac.nl) +*/ + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import nl.isaac.dotcms.excelreader.util.ExcelReaderCacheGroupHandler; + +import org.apache.velocity.tools.view.tools.ViewTool; +/** + * A ViewTool to get the information out of Excel files + * + * @author xander + * + */ +public class ExcelReaderTool implements ViewTool { + + public void init(Object arg0) { + + } + + /** + * Get the information of an Excel file as a list of maps. + */ + public static List> readExcel(String file) { + return ExcelReaderCacheGroupHandler.getInstance().get(file); + } + + /** + * Filter on a particular field with a particular value. It filters on row.get(key).toString().toLowerCase()).contains(value.toString().toLowerCase()) + */ + public List> filter(List> sheet, String key, Object value) { + List> result = new ArrayList>(); + Iterator> iterator = sheet.iterator(); + while(iterator.hasNext()) { + Map row = iterator.next(); + if(row.get(key) != null && (row.get(key).toString().toLowerCase()).contains(value.toString().toLowerCase())) { + result.add(row); + } + } + return result; + } + + /** + * Sort a result on the field with the given key + */ + public void sort(List> sheet, String key) { + Collections.sort(sheet, new FieldComparator(key)); + } + + private class FieldComparator implements Comparator> { + private String field; + + public FieldComparator(String field) { + this.field = field; + } + + public int compare(Map m1, Map m2) { + Object o1 = m1.get(field); + Object o2 = m2.get(field); + if(o1 == o2) { + return 0; + } else if(o1 == null) { + return -1; + } else if(o2 == null) { + return 1; + } else { + return o1.toString().compareTo(o2.toString()); + } + + } + } +}