Data List export as Excel and CSV webscript

Also updates the User CSV Upload template generation to use a new DeclarativeSpreadsheetWebScript parent class, which takes care of most of the fiddly bits of generating a spreadsheet (csv, xls, xlsx) with headings based on some data model fields


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@27992 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Nick Burch
2011-05-23 22:09:23 +00:00
parent ab444657ff
commit 7117ff4722
7 changed files with 910 additions and 264 deletions

View File

@@ -0,0 +1,376 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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.
*
* Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.web.scripts;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.Pair;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.csv.CSVStrategy;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.ClientAnchor;
import org.apache.poi.ss.usermodel.Comment;
import org.apache.poi.ss.usermodel.Drawing;
import org.apache.poi.ss.usermodel.Font;
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.xssf.usermodel.XSSFWorkbook;
import org.springframework.extensions.webscripts.DeclarativeWebScript;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse;
/**
* Parent of Declarative Webscripts that generate Excel files,
* usually based on some sort of dictionary model.
*
* @author Nick Burch
*/
public abstract class DeclarativeSpreadsheetWebScript extends DeclarativeWebScript
{
public static final String MODEL_CSV = "csv";
public static final String MODEL_EXCEL = "excel";
protected DictionaryService dictionaryService;
protected String filenameBase;
/**
* @param dictionaryService the DictionaryService to set
*/
public void setDictionaryService(DictionaryService dictionaryService)
{
this.dictionaryService = dictionaryService;
}
/**
* Identifies the resource for the webscript.
*/
protected abstract Object identifyResource(String format, WebScriptRequest req);
/**
* If the format is requested as HTML, should an exception be raised,
* or should an HTML version be called?
*/
protected abstract boolean allowHtmlFallback();
/**
* Returns the QNames of the model properties to be output in
* the header, and if they're required or not
*/
protected abstract List<Pair<QName, Boolean>> buildPropertiesForHeader(Object resource, String format, WebScriptRequest req);
/**
* Populates the body of the Excel Workbook, once the header has been
* output.
* This is called if the format is .xls or .xlsx
*/
protected abstract void populateBody(Object resource, Workbook workbook, Sheet sheet, List<QName> properties)
throws IOException;
/**
* Populates the body of the CSV file, once the header has been
* output.
* This is called if the format is .csv
*/
protected abstract void populateBody(Object resource, CSVPrinter csv, List<QName> properties)
throws IOException;
/**
* @see org.alfresco.web.scripts.DeclarativeWebScript#executeImpl(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.Status)
*/
@Override
protected Map<String, Object> executeImpl(WebScriptRequest req, Status status)
{
Map<String, Object> model = new HashMap<String, Object>();
model.put("success", Boolean.TRUE);
// What format are they after?
String format = req.getFormat();
if("csv".equals(format) || "xls".equals(format) ||
"xlsx".equals(format) || "excel".equals(format))
{
// Identify the thing to process
Object resource = identifyResource(format, req);
// Generate the spreadsheet
try
{
generateSpreadsheet(resource, format, req, status, model);
return model;
}
catch(IOException e)
{
throw new WebScriptException(Status.STATUS_BAD_REQUEST,
"Unable to generate template file", e);
}
}
// If we get here, then it isn't a spreadsheet version
if(allowHtmlFallback())
{
// There's some sort of help / upload form
return model;
}
else
{
throw new WebScriptException("Web Script format '" + format + "' is not supported");
}
}
/**
* Generates the spreadsheet, based on the properties in the header
* and a callback for the body.
*/
public void generateSpreadsheet(Object resource, String format, WebScriptRequest req,
Status status, Map<String, Object> model) throws IOException
{
Pattern qnameMunger = Pattern.compile("([A-Z][a-z]+)([A-Z].*)");
// Build up the details of the header
List<Pair<QName, Boolean>> propertyDetails = buildPropertiesForHeader(resource, format, req);
String[] headings = new String[propertyDetails.size()];
String[] descriptions = new String[propertyDetails.size()];
boolean[] required = new boolean[propertyDetails.size()];
for(int i=0; i<headings.length; i++)
{
Pair<QName, Boolean> property = propertyDetails.get(i);
if(property == null || property.getFirst() == null)
{
headings[i] = "";
required[i] = false;
}
else
{
QName column = property.getFirst();
required[i] = property.getSecond();
// Ask the dictionary service nicely for the details
PropertyDefinition pd = dictionaryService.getProperty(column);
if(pd != null && pd.getTitle() != null)
{
// Use the friendly titles, which may even be localised!
headings[i] = pd.getTitle();
descriptions[i] = pd.getDescription();
}
else
{
// Nothing friendly found, try to munge the raw qname into
// something we can show to a user...
String raw = column.getLocalName();
raw = raw.substring(0, 1).toUpperCase() + raw.substring(1);
Matcher m = qnameMunger.matcher(raw);
if(m.matches())
{
headings[i] = m.group(1) + " " + m.group(2);
}
else
{
headings[i] = raw;
}
}
}
}
// Build a list of just the properties
List<QName> properties = new ArrayList<QName>(propertyDetails.size());
for(Pair<QName,Boolean> p : propertyDetails)
{
QName qn = null;
if(p != null)
{
qn = p.getFirst();
}
properties.add(qn);
}
// Output
if("csv".equals(format))
{
StringWriter sw = new StringWriter();
CSVPrinter csv = new CSVPrinter(sw, CSVStrategy.EXCEL_STRATEGY);
csv.println(headings);
populateBody(resource, csv, properties);
model.put(MODEL_CSV, sw.toString());
}
else
{
Workbook wb;
if("xlsx".equals(format))
{
wb = new XSSFWorkbook();
// TODO Properties
}
else
{
wb = new HSSFWorkbook();
// TODO Properties
}
// Add our header row
Sheet sheet = wb.createSheet("Export");
Row hr = sheet.createRow(0);
sheet.createFreezePane(0, 1);
Font fb = wb.createFont();
fb.setBoldweight(Font.BOLDWEIGHT_BOLD);
Font fi = wb.createFont();
fi.setBoldweight(Font.BOLDWEIGHT_BOLD);
fi.setItalic(true);
CellStyle csReq = wb.createCellStyle();
csReq.setFont(fb);
CellStyle csOpt = wb.createCellStyle();
csOpt.setFont(fi);
// Populate the header
Drawing draw = null;
for(int i=0; i<headings.length; i++)
{
Cell c = hr.createCell(i);
c.setCellValue(headings[i]);
if(required[i])
{
c.setCellStyle(csReq);
}
else
{
c.setCellStyle(csOpt);
}
if(headings[i].length() == 0)
{
sheet.setColumnWidth(i, 3*250);
}
else
{
sheet.setColumnWidth(i, 18*250);
}
if(descriptions[i] != null && descriptions[i].length() > 0)
{
// Add a description for it too
if(draw == null)
{
draw = sheet.createDrawingPatriarch();
}
ClientAnchor ca = wb.getCreationHelper().createClientAnchor();
ca.setCol1(c.getColumnIndex());
ca.setCol2(c.getColumnIndex()+1);
ca.setRow1(hr.getRowNum());
ca.setRow2(hr.getRowNum()+2);
Comment cmt = draw.createCellComment(ca);
cmt.setAuthor("");
cmt.setString(wb.getCreationHelper().createRichTextString(descriptions[i]));
cmt.setVisible(false);
c.setCellComment(cmt);
}
}
// Have the contents populated
populateBody(resource, wb, sheet, properties);
// Save it for the template
ByteArrayOutputStream baos = new ByteArrayOutputStream();
wb.write(baos);
model.put(MODEL_EXCEL, baos.toByteArray());
}
}
@Override
protected Map<String, Object> createTemplateParameters(WebScriptRequest req, WebScriptResponse res,
Map<String, Object> customParams)
{
Map<String, Object> model = super.createTemplateParameters(req, res, customParams);
// We sometimes need to monkey around to do the binary output...
model.put("req", req);
model.put("res", res);
model.put("writeExcel", new WriteExcel(res, model, req.getFormat(), filenameBase));
return model;
}
public static class WriteExcel
{
private String format;
private String filenameBase;
private WebScriptResponse res;
private Map<String, Object> model;
private WriteExcel(WebScriptResponse res, Map<String, Object> model, String format, String filenameBase)
{
this.res = res;
this.model = model;
this.format = format;
}
public void write() throws IOException
{
String filename = filenameBase + "." + format;
// If it isn't a CSV, reset so we can send binary
if(! "csv".equals(format))
{
res.reset();
}
// Tell the browser it's a file download
res.addHeader("Content-Disposition", "attachment; filename=" + filename);
// Now send that data
if("csv".equals(format))
{
res.getWriter().append((String)model.get(MODEL_CSV));
}
else
{
// Set the mimetype, as we've reset
if("xlsx".equals(format))
{
res.setContentType(MimetypeMap.MIMETYPE_OPENXML_SPREADSHEET);
} else {
res.setContentType(MimetypeMap.MIMETYPE_EXCEL);
}
// Send the raw excel bytes
byte[] excel = (byte[])model.get(MODEL_EXCEL);
res.getOutputStream().write(excel);
}
}
}
}

View File

@@ -0,0 +1,410 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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.
*
* Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.web.scripts.datalist;
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.repo.web.scripts.DeclarativeSpreadsheetWebScript;
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.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 a Data List as an Excel file
*
* @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 =
QName.createQName(NamespaceService.DATALIST_MODEL_1_0_URI, "dataListItemType");
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 nodeService
*/
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, just use DataList properties for the type
TypeDefinition typeDef = dictionaryService.getType(type);
Map<QName, PropertyDefinition> allProps = typeDef.getProperties();
props = new ArrayList<QName>();
for(QName prop : allProps.keySet())
{
if(NamespaceService.DATALIST_MODEL_1_0_URI.equals(prop.getNamespaceURI()))
{
props.add(prop);
}
}
}
// 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;
}
private QName buildType(NodeRef list)
{
String typeS = (String)nodeService.getProperty(list, DATA_LIST_ITEM_TYPE);
if(! typeS.startsWith(NamespaceService.DATALIST_MODEL_PREFIX + ":"))
{
throw new WebScriptException(Status.STATUS_NOT_IMPLEMENTED, "Unexpected list type " + typeS);
}
QName type = QName.createQName(NamespaceService.DATALIST_MODEL_1_0_URI, typeS.substring(typeS.indexOf(':')+1));
return type;
}
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 {
throw new WebScriptException(Status.STATUS_BAD_REQUEST, "CSV not currently supported");
}
@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);
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)
{
StringBuffer text = new StringBuffer();
int lines = 1;
for(AssociationRef ref : assocs)
{
NodeRef child = ref.getTargetRef();
QName type = nodeService.getType(child);
if(ContentModel.TYPE_PERSON.equals(type))
{
if(text.length() > 0) {
text.append('\n');
lines++;
}
text.append(nodeService.getProperty(
child, ContentModel.PROP_USERNAME
));
}
else if(ContentModel.TYPE_CONTENT.equals(type))
{
// TODO Link to the content
if(text.length() > 0) {
text.append('\n');
lines++;
}
text.append(nodeService.getProperty(
child, ContentModel.PROP_TITLE
));
}
else
{
System.err.println("TODO: handle " + type + " for " + child);
}
}
String v = text.toString();
c.setCellValue( v );
if(lines > 1)
{
c.setCellStyle(styleNewLines);
r.setHeightInPoints( lines*sheet.getDefaultRowHeightInPoints() );
}
}
else
{
// This property isn't set
c.setCellType(Cell.CELL_TYPE_BLANK);
}
}
else
{
// Regular property, set
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 " + val.getClass().getName() + " - " + val);
}
}
colNum++;
}
rowNum++;
}
// Sensible column widths please!
colNum = 0;
for(QName prop : properties)
{
sheet.autoSizeColumn(colNum);
colNum++;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -18,36 +18,17 @@
*/
package org.alfresco.repo.web.scripts.person;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.ArrayList;
import java.util.List;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.repo.web.scripts.DeclarativeSpreadsheetWebScript;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.Pair;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.csv.CSVStrategy;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.ClientAnchor;
import org.apache.poi.ss.usermodel.Comment;
import org.apache.poi.ss.usermodel.Drawing;
import org.apache.poi.ss.usermodel.Font;
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.xssf.usermodel.XSSFWorkbook;
import org.springframework.extensions.webscripts.DeclarativeWebScript;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse;
/**
@@ -55,247 +36,65 @@ import org.springframework.extensions.webscripts.WebScriptResponse;
* users via a CSV.
*
* @author Nick Burch
* @since 3.5
*/
public class UserCSVUploadGet extends DeclarativeWebScript
public class UserCSVUploadGet extends DeclarativeSpreadsheetWebScript
{
public static final String MODEL_CSV = "csv";
public static final String MODEL_EXCEL = "excel";
private DictionaryService dictionaryService;
/**
* @param dictionaryService the DictionaryService to set
*/
public void setDictionaryService(DictionaryService dictionaryService)
{
this.dictionaryService = dictionaryService;
}
/**
* @see org.alfresco.web.scripts.DeclarativeWebScript#executeImpl(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.Status)
*/
@Override
protected Map<String, Object> executeImpl(WebScriptRequest req, Status status)
{
Map<String, Object> model = new HashMap<String, Object>();
model.put("success", Boolean.TRUE);
// What format are they after?
String format = req.getFormat();
if ("csv".equals(format) || "xls".equals(format) ||
"xlsx".equals(format) || "excel".equals(format))
{
try
{
generateTemplateFile(format, req, status, model);
return model;
}
catch (IOException e)
{
throw new WebScriptException(Status.STATUS_BAD_REQUEST,
"Unable to generate template file", e);
}
}
// Give them the help and the upload form
return model;
}
/**
* Generates a template file to help the user figure out what to
* put into their CSV
*/
private void generateTemplateFile(String format, WebScriptRequest req, Status status, Map<String, Object> model)
throws IOException
{
Pattern p = Pattern.compile("([A-Z][a-z]+)([A-Z].*)");
String[] headings = new String[UserCSVUploadPost.COLUMNS.length];
String[] descriptions = new String[UserCSVUploadPost.COLUMNS.length];
for (int i=0; i<headings.length; i++)
{
QName column = UserCSVUploadPost.COLUMNS[i];
if (column == null)
{
headings[i] = "";
}
else
{
// Ask the dictionary service nicely
PropertyDefinition pd = dictionaryService.getProperty(column);
if (pd != null && pd.getTitle() != null)
{
// Use the friendly titles, which may even be localised!
headings[i] = pd.getTitle();
descriptions[i] = pd.getDescription();
}
else
{
// Nothing friendly found, try to munge the raw qname into
// something we can show to a user...
String raw = column.getLocalName();
raw = raw.substring(0, 1).toUpperCase() + raw.substring(1);
Matcher m = p.matcher(raw);
if (m.matches())
{
headings[i] = m.group(1) + " " + m.group(2);
}
else
{
headings[i] = raw;
}
}
}
}
if ("csv".equals(format))
{
StringWriter sw = new StringWriter();
CSVPrinter csv = new CSVPrinter(sw, CSVStrategy.EXCEL_STRATEGY);
csv.println(headings);
csv.println();
model.put(MODEL_CSV, sw.toString());
}
else
{
Workbook wb;
if ("xlsx".equals(format))
{
wb = new XSSFWorkbook();
}
else
{
wb = new HSSFWorkbook();
}
// Add our header row
Sheet sheet = wb.createSheet("UsersToAdd");
Row hr = sheet.createRow(0);
sheet.createFreezePane(0, 1);
Font fb = wb.createFont();
fb.setBoldweight(Font.BOLDWEIGHT_BOLD);
Font fi = wb.createFont();
fi.setBoldweight(Font.BOLDWEIGHT_BOLD);
fi.setItalic(true);
CellStyle csReq = wb.createCellStyle();
csReq.setFont(fb);
CellStyle csOpt = wb.createCellStyle();
csOpt.setFont(fi);
// Populate the header
CellStyle cs = csReq;
Drawing draw = null;
for (int i=0; i<headings.length; i++)
{
Cell c = hr.createCell(i);
c.setCellStyle(cs);
c.setCellValue(headings[i]);
if (headings[i].length() == 0)
{
sheet.setColumnWidth(i, 3*250);
cs = csOpt;
}
else
{
sheet.setColumnWidth(i, 18*250);
}
if (descriptions[i] != null && descriptions[i].length() > 0)
{
// Add a description for it too
if (draw == null)
{
draw = sheet.createDrawingPatriarch();
}
ClientAnchor ca = wb.getCreationHelper().createClientAnchor();
ca.setCol1(c.getColumnIndex());
ca.setCol2(c.getColumnIndex()+1);
ca.setRow1(hr.getRowNum());
ca.setRow2(hr.getRowNum()+2);
Comment cmt = draw.createCellComment(ca);
cmt.setAuthor("");
cmt.setString(wb.getCreationHelper().createRichTextString(descriptions[i]));
cmt.setVisible(false);
c.setCellComment(cmt);
}
}
// Add an empty data row
sheet.createRow(1);
// Save it for the template
ByteArrayOutputStream baos = new ByteArrayOutputStream();
wb.write(baos);
model.put(MODEL_EXCEL, baos.toByteArray());
}
}
public UserCSVUploadGet()
{
this.filenameBase = "ExampleUserUpload";
}
/**
* We have a HTML version
*/
@Override
protected Map<String, Object> createTemplateParameters(WebScriptRequest req, WebScriptResponse res,
Map<String, Object> customParams)
{
Map<String, Object> model = super.createTemplateParameters(req, res, customParams);
// We sometimes need to monkey around to do the binary output...
model.put("req", req);
model.put("res", res);
model.put("writeExcel", new WriteExcel(res, model, req.getFormat()));
return model;
}
public static class WriteExcel
{
private String format;
private WebScriptResponse res;
private Map<String, Object> model;
private WriteExcel(WebScriptResponse res, Map<String, Object> model, String format)
{
this.res = res;
this.model = model;
this.format = format;
}
public void write() throws IOException
{
String filename = "ExampleUserUpload." + format;
// If it isn't a CSV, reset so we can send binary
if (!"csv".equals(format))
{
res.reset();
}
// Tell the browser it's a file download
res.addHeader("Content-Disposition", "attachment; filename=" + filename);
// Now send that data
if ("csv".equals(format))
{
res.getWriter().append((String)model.get(MODEL_CSV));
}
else
{
// Set the mimetype, as we've reset
if ("xlsx".equals(format))
{
res.setContentType(MimetypeMap.MIMETYPE_OPENXML_SPREADSHEET);
}
else
{
res.setContentType(MimetypeMap.MIMETYPE_EXCEL);
}
// Send the raw excel bytes
byte[] excel = (byte[])model.get(MODEL_EXCEL);
res.getOutputStream().write(excel);
}
}
}
}
protected boolean allowHtmlFallback() {
return true;
}
/**
* We don't have a resource
*/
@Override
protected Object identifyResource(String format, WebScriptRequest req) {
return null;
}
@Override
protected List<Pair<QName, Boolean>> buildPropertiesForHeader(
Object resource, String format, WebScriptRequest req) {
List<Pair<QName,Boolean>> properties =
new ArrayList<Pair<QName,Boolean>>(UserCSVUploadPost.COLUMNS.length);
boolean required = true;
for(QName qname : UserCSVUploadPost.COLUMNS)
{
Pair<QName,Boolean> p = null;
if(qname != null)
{
p = new Pair<QName, Boolean>(qname, required);
}
else
{
required = false;
}
properties.add(p);
}
return properties;
}
@Override
protected void populateBody(Object resource, CSVPrinter csv, List<QName> properties) throws IOException {
// Just a blank line is needed
csv.println();
}
@Override
protected void populateBody(Object resource, Workbook workbook, Sheet sheet,
List<QName> properties) throws IOException {
// Set the sheet name
workbook.setSheetName(0, "UsersToAdd");
// Add a blank line
sheet.createRow(1);
}
}