Java tutorial
/* * This file is part of the Quanticate DataList Download project. * * This file is based on code taken from Alfresco, which is * Copyright (C) 2005-2012 Alfresco Software Limited. * * This is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this project. If not, see <http://www.gnu.org/licenses/>. */ package com.quanticate.opensource.datalistdownload; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import org.alfresco.model.ContentModel; import org.alfresco.model.DataListModel; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.dictionary.TypeDefinition; import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.site.SiteInfo; import org.alfresco.service.cmr.site.SiteService; import org.alfresco.service.namespace.InvalidQNameException; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.ISO8601DateFormat; import org.alfresco.util.Pair; import org.apache.commons.csv.CSVPrinter; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.DataFormat; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.springframework.beans.factory.InitializingBean; import org.springframework.extensions.webscripts.Status; import org.springframework.extensions.webscripts.WebScriptException; import org.springframework.extensions.webscripts.WebScriptRequest; /** * Data List Download * * Exports the contents of an Alfresco Data List in Spreadsheet formats. * The export can be as an Excel XLS, Excel XLSX or CSV. ODF should be * added shortly * * @author Nick Burch */ public class DataListDownloadWebScript extends DeclarativeSpreadsheetWebScript implements InitializingBean { // Logger private static final Log logger = LogFactory.getLog(DataListDownloadWebScript.class); private static final QName DATA_LIST_ITEM_TYPE = DataListModel.PROP_DATALIST_ITEM_TYPE; private NodeService nodeService; private SiteService siteService; private NamespaceService namespaceService; private Map<QName, List<QName>> modelOrder; private Map<String, String> rawModelOrder; public DataListDownloadWebScript() { this.filenameBase = "DataListExport"; } /** * @param nodeService */ public void setNodeService(NodeService nodeService) { this.nodeService = nodeService; } /** * @param siteService */ public void setSiteService(SiteService siteService) { this.siteService = siteService; } /** * @param namespaceService */ public void setNamespaceService(NamespaceService namespaceService) { this.namespaceService = namespaceService; } public void setModelOrder(Map<String, String> rawModelOrder) { this.rawModelOrder = rawModelOrder; } @Override public void afterPropertiesSet() throws Exception { modelOrder = new HashMap<QName, List<QName>>(); for (String key : rawModelOrder.keySet()) { QName model; List<QName> order = new ArrayList<QName>(); try { model = QName.createQName(key, namespaceService); } catch (InvalidQNameException e) { logger.warn("Skipping invalid model type " + key); continue; } StringTokenizer st = new StringTokenizer(rawModelOrder.get(key), ","); while (st.hasMoreTokens()) { order.add(QName.createQName(st.nextToken(), namespaceService)); } modelOrder.put(model, order); } } /** * Identify the datalist */ @Override protected Object identifyResource(String format, WebScriptRequest req) { // Try to find the datalist they requested NodeRef list; Map<String, String> args = req.getServiceMatch().getTemplateVars(); if (args.get("store_type") != null) { list = new NodeRef(args.get("store_type"), args.get("store_id"), args.get("id")); } else { // Get the site SiteInfo site = siteService.getSite(args.get("site")); if (site == null) { throw new WebScriptException(Status.STATUS_NOT_FOUND, "Site not found with supplied name"); } // Now find the data list container with in NodeRef container = nodeService.getChildByName(site.getNodeRef(), ContentModel.ASSOC_CONTAINS, args.get("container")); if (container == null) { throw new WebScriptException(Status.STATUS_NOT_FOUND, "Container not found within site"); } // Now get the data list itself list = nodeService.getChildByName(container, ContentModel.ASSOC_CONTAINS, args.get("list")); } if (list == null || !nodeService.exists(list)) { throw new WebScriptException(Status.STATUS_NOT_FOUND, "The Data List could not be found"); } return list; } /** * We don't have a HTML version */ @Override protected boolean allowHtmlFallback() { return false; } /** * Fetch the properties, in the requested order, from * the data list definition */ @Override protected List<Pair<QName, Boolean>> buildPropertiesForHeader(Object resource, String format, WebScriptRequest req) { NodeRef list = (NodeRef) resource; QName type = buildType(list); // Has the user given us rules for what to do // with this type? List<QName> props; if (modelOrder.containsKey(type)) { props = modelOrder.get(type); } else { // We'll have to try to guess it for them // For now, use any DataList properties for the type, // along with any non-standard ones TypeDefinition typeDef = dictionaryService.getType(type); Map<QName, PropertyDefinition> allProps = typeDef.getProperties(); props = new ArrayList<QName>(); boolean hasCustom = false; for (QName prop : allProps.keySet()) { String propNS = prop.getNamespaceURI(); if (NamespaceService.DATALIST_MODEL_1_0_URI.equals(propNS)) { props.add(prop); } else if (NamespaceService.ALFRESCO_URI.equals(propNS) || NamespaceService.CONTENT_MODEL_1_0_URI.equals(propNS) || NamespaceService.SYSTEM_MODEL_1_0_URI.equals(propNS)) { // Built in property, skip } else { // It's probably a custom one, include it props.add(prop); hasCustom = true; } } if (hasCustom) { logger.warn("No export definition found for type " + type + " - for best results you should define one to give order and include/exclude"); } } // Everything is required List<Pair<QName, Boolean>> properties = new ArrayList<Pair<QName, Boolean>>(); for (QName qname : props) { properties.add(new Pair<QName, Boolean>(qname, true)); } return properties; } /** * Verify it's a DataList, and build the type * @param list * @return */ private QName buildType(NodeRef list) { String typeS = (String) nodeService.getProperty(list, DATA_LIST_ITEM_TYPE); if (typeS.startsWith(NamespaceService.DATALIST_MODEL_PREFIX + ":")) { // Regular Alfresco DataList QName type = QName.createQName(NamespaceService.DATALIST_MODEL_1_0_URI, typeS.substring(typeS.indexOf(':') + 1)); return type; } else { // Check if it's a custom one QName type = QName.createQName(typeS, namespaceService); QName check = type; while (check != null) { check = dictionaryService.getType(check).getParentName(); if (check != null && check.getNamespaceURI().equals(NamespaceService.DATALIST_MODEL_1_0_URI)) { // It's a custom datalist, that's OK return type; } } } // It's neither a built-in nor a customised datalist throw new WebScriptException(Status.STATUS_NOT_IMPLEMENTED, "Unexpected list type " + typeS); } private List<NodeRef> getItems(NodeRef list) { Set<QName> typeSet = new HashSet<QName>(Arrays.asList(new QName[] { buildType(list) })); List<NodeRef> items = new ArrayList<NodeRef>(); for (ChildAssociationRef ca : nodeService.getChildAssocs(list, typeSet)) { items.add(ca.getChildRef()); } return items; } @Override protected void populateBody(Object resource, CSVPrinter csv, List<QName> properties) throws IOException { NodeRef list = (NodeRef) resource; List<NodeRef> items = getItems(list); for (NodeRef item : items) { for (QName prop : properties) { Pair<Object, String> valAndLink = identifyValueAndLink(item, prop); if (valAndLink == null) { // This property isn't set csv.print(""); } else { Object val = valAndLink.getFirst(); // Multi-line property? if (val instanceof String[]) { String[] lines = (String[]) val; StringBuffer text = new StringBuffer(); for (String line : lines) { if (text.length() > 0) { text.append('\n'); } text.append(line); } csv.print(text.toString()); } // Regular properties else if (val instanceof String) { csv.print((String) val); } else if (val instanceof Date) { csv.print(ISO8601DateFormat.format((Date) val)); } else if (val instanceof Integer || val instanceof Long) { csv.print(val.toString()); } else if (val instanceof Float || val instanceof Double) { csv.print(val.toString()); } else { System.err.println("TODO: Handle CSV output of " + val.getClass().getName() + " - " + val); } } } csv.println(); } } @Override protected void populateBody(Object resource, Workbook workbook, Sheet sheet, List<QName> properties) throws IOException { NodeRef list = (NodeRef) resource; List<NodeRef> items = getItems(list); // Our various formats DataFormat formatter = workbook.createDataFormat(); CellStyle styleInt = workbook.createCellStyle(); styleInt.setDataFormat(formatter.getFormat("0")); CellStyle styleDate = workbook.createCellStyle(); styleDate.setDataFormat(formatter.getFormat("yyyy-mm-dd")); CellStyle styleDouble = workbook.createCellStyle(); styleDouble.setDataFormat(formatter.getFormat("General")); CellStyle styleNewLines = workbook.createCellStyle(); styleNewLines.setWrapText(true); // Export the items int rowNum = 1, colNum = 0; for (NodeRef item : items) { Row r = sheet.createRow(rowNum); colNum = 0; for (QName prop : properties) { Cell c = r.createCell(colNum); Pair<Object, String> valAndLink = identifyValueAndLink(item, prop); if (valAndLink == null) { // This property isn't set c.setCellType(Cell.CELL_TYPE_BLANK); } else { Object val = valAndLink.getFirst(); // Multi-line property? if (val instanceof String[]) { String[] lines = (String[]) val; StringBuffer text = new StringBuffer(); for (String line : lines) { if (text.length() > 0) { text.append('\n'); } text.append(line); } String v = text.toString(); c.setCellValue(v); if (lines.length > 1) { c.setCellStyle(styleNewLines); r.setHeightInPoints(lines.length * sheet.getDefaultRowHeightInPoints()); } } // Regular properties else if (val instanceof String) { c.setCellValue((String) val); } else if (val instanceof Date) { c.setCellValue((Date) val); c.setCellStyle(styleDate); } else if (val instanceof Integer || val instanceof Long) { double v = 0.0; if (val instanceof Long) v = (double) (Long) val; if (val instanceof Integer) v = (double) (Integer) val; c.setCellValue(v); c.setCellStyle(styleInt); } else if (val instanceof Float || val instanceof Double) { double v = 0.0; if (val instanceof Float) v = (double) (Float) val; if (val instanceof Double) v = (double) (Double) val; c.setCellValue(v); c.setCellStyle(styleDouble); } else { // TODO System.err .println("TODO: Handle Excel output of " + val.getClass().getName() + " - " + val); } } colNum++; } rowNum++; } // Sensible column widths please! colNum = 0; for (QName prop : properties) { sheet.autoSizeColumn(colNum); colNum++; } } protected Pair<Object, String> identifyValueAndLink(NodeRef item, QName prop) { Serializable val = nodeService.getProperty(item, prop); if (val == null) { // Is it an association, or just missing? List<AssociationRef> assocs = nodeService.getTargetAssocs(item, prop); if (assocs.size() > 0) { ArrayList<String> text = new ArrayList<String>(); for (AssociationRef ref : assocs) { NodeRef child = ref.getTargetRef(); QName type = nodeService.getType(child); if (ContentModel.TYPE_PERSON.equals(type)) { text.add((String) nodeService.getProperty(child, ContentModel.PROP_USERNAME)); } else if (ContentModel.TYPE_CONTENT.equals(type)) { // TODO Link to the content text.add((String) nodeService.getProperty(child, ContentModel.PROP_TITLE)); } else { System.err.println("TODO: handle " + type + " for " + child); } } String[] lines = text.toArray(new String[text.size()]); return new Pair<Object, String>(lines, null); } else { // This property isn't set return null; } } else { // Regular property return new Pair<Object, String>(val, null); } } }