/*
* File : $Source: /usr/local/cvs/alkacon/com.alkacon.opencms.formgenerator/src/com/alkacon/opencms/formgenerator/CmsFormReport.java,v $
* Date : $Date: 2011-05-24 13:42:21 $
* Version: $Revision: 1.5 $
*
* This file is part of the Alkacon OpenCms Add-On Module Package
*
* Copyright (c) 2010 Alkacon Software GmbH (http://www.alkacon.com)
*
* The Alkacon OpenCms Add-On Module Package is free software:
* you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alkacon OpenCms Add-On Module Package 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with the Alkacon OpenCms Add-On Module Package.
* If not, see http://www.gnu.org/licenses/.
*
* For further information about Alkacon Software GmbH, please see the
* company website: http://www.alkacon.com.
*
* For further information about OpenCms, please see the
* project website: http://www.opencms.org.
*/
package com.alkacon.opencms.formgenerator;
import com.alkacon.opencms.formgenerator.database.CmsFormDataAccess;
import com.alkacon.opencms.formgenerator.database.CmsFormDataBean;
import com.alkacon.opencms.formgenerator.database.CmsFormDatabaseFilter;
import org.opencms.file.CmsFile;
import org.opencms.i18n.CmsEncoder;
import org.opencms.i18n.CmsMessages;
import org.opencms.json.JSONArray;
import org.opencms.json.JSONException;
import org.opencms.json.JSONObject;
import org.opencms.jsp.CmsJspActionElement;
import org.opencms.main.CmsException;
import org.opencms.main.CmsRuntimeException;
import org.opencms.main.OpenCms;
import org.opencms.util.CmsStringUtil;
import org.opencms.workplace.CmsWorkplace;
import org.opencms.xml.content.CmsXmlContent;
import org.opencms.xml.content.CmsXmlContentFactory;
import java.text.CollationKey;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.PageContext;
/**
* Provides the methods to generate the form report output page.<p>
*
* @author Andreas Zahner
*/
public class CmsFormReport extends CmsJspActionElement {
/**
* This comparator compares the values of the field columns, it is needed for dynamic data load.<p>
*/
private class FieldComparator implements Comparator<CmsFormDataBean> {
/** Needed for String comparison. */
private Collator m_collator;
/** The name of the field to compare. */
private String m_field;
/** Holds the collation keys for faster String comparison. */
private Map<String, CollationKey> m_keys;
/** The current sort order, either ascending or descending. */
private String m_sortOrder;
/**
* Constructor, with parameters.<p>
*
* @param field the name of the field to compare
* @param sortOrder the current sort order, either ascending or descending
* @param locale the current Locale, used for String comparison
*/
public FieldComparator(String field, String sortOrder, Locale locale) {
m_field = field;
m_sortOrder = sortOrder;
m_collator = Collator.getInstance(locale);
m_keys = new HashMap<String, CollationKey>();
}
/**
* Compares the two form data beans according to the field and sort state settings.<p>
*
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*
* @param o1 the first object to be compared
* @param o2 the second object to be compared
*
* @return a negative integer, zero, or a positive integer
*/
public int compare(CmsFormDataBean o1, CmsFormDataBean o2) {
if (o1 == o2) {
return 0;
}
if (COLUMN_ID_DATE.equals(m_field)) {
// sort by creation date
Long date1 = Long.valueOf(o1.getDateCreated());
Long date2 = Long.valueOf(o2.getDateCreated());
if ("asc".equals(m_sortOrder)) {
return date1.compareTo(date2);
} else {
return date2.compareTo(date1);
}
} else {
// sort by specific column (treated as String)
String val1 = o1.getFieldValue(m_field);
String val2 = o2.getFieldValue(m_field);
// get or generate collation keys for the values
CollationKey key1, key2;
if (m_keys.containsKey(val1)) {
key1 = m_keys.get(val1);
} else {
key1 = m_collator.getCollationKey(val1);
m_keys.put(val1, key1);
}
if (m_keys.containsKey(val2)) {
key2 = m_keys.get(val2);
} else {
key2 = m_collator.getCollationKey(val2);
m_keys.put(val2, key2);
}
if ("asc".equals(m_sortOrder)) {
return key1.compareTo(key2);
} else {
return key2.compareTo(key1);
}
}
}
}
/** Column ID of the creation date column. */
public static final String COLUMN_ID_DATE = "creationdate";
/** The node name for the node: show menu. */
public static final String NODE_COLWIDTH = "ColWidth";
/** The node name for the node: entries. */
public static final String NODE_ENTRIES = "Entries";
/** The node name for the node: fields. */
public static final String NODE_FIELDS = "Fields";
/** The node name for the node: grid height. */
public static final String NODE_GRIDHEIGHT = "GridHeight";
/** The node name for the node: grid height. */
public static final String NODE_LOADDYNAMIC = "LoadDynamic";
/** The node name for the node: show date. */
public static final String NODE_SHOWDATE = "ShowDate";
/** The node name for the node: show labels. */
public static final String NODE_SHOWLABELS = "ShowLabels";
/** The node name for the node: show menu. */
public static final String NODE_SHOWMENU = "ShowMenu";
/** The node name for the node: skin. */
public static final String NODE_SKIN = "Skin";
/** The node name for the node: URI. */
public static final String NODE_URI = "URI";
/** Request parameter name for the action to get report data. */
public static final String PARAM_ACTION = "_gt_json";
/** Value for automatic height calculation of the report data grid. */
public static final String VALUE_HEIGHT_AUTO = "auto";
/** The VFS path to the folder containing the static resources to generate the report grid. */
public static final String VFS_PATH_GRIDRESOURCES = CmsWorkplace.VFS_PATH_MODULES
+ CmsForm.MODULE_NAME
+ "/resources/grid/grid/";
/** The displayed form report columns generated from the form fields, with the column ID as key. */
private Map<String, CmsFormReportColumn> m_columnMappings;
/** The displayed columns generated from the form fields, including sub fields. */
private List<CmsFormReportColumn> m_columns;
/** The initial width of a report column. */
private int m_columnWidth;
/** The XML content that configures the report output. */
private CmsXmlContent m_content;
/** The PageContext that calls the report output. */
private PageContext m_pageContext;
/** The number of entries per page that is preselected. */
private int m_entriesPerPage;
/** The form configuration object. */
private CmsFormHandler m_formHandler;
/** The submitted form data. */
private List<CmsFormDataBean> m_forms;
/** Indicates if the report output height should be calculated automatically. */
private Boolean m_heightAuto;
/** Indicates if the report data should be loaded dynamically. */
private Boolean m_loadDynamic;
/** Indicates if the submission date column should be shown. */
private Boolean m_showDate;
/** Indicates if the labels of the fields should be shown. */
private Boolean m_showLabels;
/** Indicates if the main report output should be generated. */
private Boolean m_showReport;
/**
* Constructor, creates the necessary form report configuration objects.<p>
*
* @param context the JSP page context object
* @param req the JSP request
* @param res the JSP response
*/
public CmsFormReport(PageContext context, HttpServletRequest req, HttpServletResponse res) {
super(context, req, res);
initReport(context, req, res, getRequestContext().getUri());
}
/**
* Constructor, creates the necessary form report configuration objects.<p>
*
* @param context the JSP page context object
* @param req the JSP request
* @param res the JSP response
* @param reportUri URI of the report configuration file
*/
public CmsFormReport(PageContext context, HttpServletRequest req, HttpServletResponse res, String reportUri) {
super(context, req, res);
initReport(context, req, res, reportUri);
}
/**
* Returns the initial column with for the report.<p>
*
* @return the initial column with for the report
*/
public int getColumnWidth() {
if (m_columnWidth < 1) {
String widthStr = getReportContent().getStringValue(
getCmsObject(),
NODE_COLWIDTH,
getRequestContext().getLocale());
m_columnWidth = 80;
try {
m_columnWidth = Integer.parseInt(widthStr);
} catch (Exception e) {
// no number specified, fall back to default value
}
}
return m_columnWidth;
}
/**
* Returns the preselected number of entries per page of the report.<p>
*
* @return the preselected number of entries per page of the report
*/
public int getEntriesPerPage() {
if (m_entriesPerPage < 1) {
m_entriesPerPage = 100;
if (isHeightAuto()) {
m_entriesPerPage = getForms().size();
} else {
String entriesStr = getReportContent().getStringValue(
getCmsObject(),
NODE_ENTRIES,
getRequestContext().getLocale());
try {
m_entriesPerPage = Integer.parseInt(entriesStr);
} catch (Exception e) {
// no number specified, fall back to default value
}
}
}
return m_entriesPerPage;
}
/**
* Returns the form configuration.<p>
*
* @return the form configuration
*/
public CmsForm getFormConfiguration() {
return m_formHandler.getFormConfiguration();
}
/**
* Returns the height of the report grid.<p>
*
* If the height is set to {@link #VALUE_HEIGHT_AUTO} in the report configuration, the height is calculated
* for the current number of form items.<p>
*
* @return the height of the report grid
*/
public int getGridHeight() {
String heightStr = getReportContent().getStringValue(
getCmsObject(),
NODE_GRIDHEIGHT,
getRequestContext().getLocale());
int height = 350;
if (VALUE_HEIGHT_AUTO.equalsIgnoreCase(heightStr)) {
// automatic height calculation
m_heightAuto = Boolean.TRUE;
// height: header + footer + eventual scroll bar + height of items (each item: 22px)
height = 57 + 28 + 15 + (getForms().size() * 22);
} else {
// fixed height
try {
height = Integer.parseInt(heightStr);
} catch (Exception e) {
// no number specified, fall back to default value
}
}
return height;
}
/**
* Returns the localized messages necessary to create the report output.<p>
*
* @return the localized messages necessary to create the report output
*/
public CmsMessages getMessages() {
return m_formHandler.getMessages();
}
/**
* Returns the report data as JSON array String.<p>
*
* Note: <i>all</i> data entries are returned, should not be used for large result sets.<p>
*
* @return the report data as JSON array String
*/
public String getReportData() {
return getDataRows(getForms()).toString();
}
/**
* Returns the dynamic report data for the current page as JSON array String.<p>
*
* Note: only currently visible data entries are returned, should be used for large result sets.<p>
*
* @return the dynamic report data as JSON array String
*/
public String getReportDataDynamic() {
JSONObject data = new JSONObject();
try {
JSONObject allInfo = new JSONObject(getRequest().getParameter(PARAM_ACTION));
JSONObject pageInfoReceived = allInfo.getJSONObject("pageInfo");
JSONObject sortInfo = new JSONObject();
try {
// the sort info is provided as array containing an object, really strange...
sortInfo = allInfo.getJSONArray("sortInfo").getJSONObject(0);
} catch (JSONException e) {
// sort info is not provided, this is the case on initial call
}
int pageSize = pageInfoReceived.getInt("pageSize");
int startRow = pageInfoReceived.getInt("startRowNum");
int endRow = pageInfoReceived.getInt("endRowNum");
if (endRow < 0) {
endRow = (startRow + pageSize) - 1;
}
// determine the sort column ID
String sortColumn = "";
if (sortInfo.has("columnId")) {
sortColumn = sortInfo.getString("columnId");
}
// determine the sort order
String sortOrder = "asc";
if (sortInfo.has("sortOrder")) {
sortOrder = sortInfo.getString("sortOrder");
}
// get all stored forms
List<CmsFormDataBean> forms = getForms();
// calculate start and end index
int startIndex = startRow - 1;
int endIndex = endRow - 1;
if (endIndex > (forms.size() - 1)) {
endIndex = forms.size() - 1;
}
if (CmsStringUtil.isNotEmpty(sortColumn)) {
// sort the data according to column and order
String fieldName = sortColumn;
if (!COLUMN_ID_DATE.equals(sortColumn)) {
// determine the matching field DB label to the given column ID
CmsFormReportColumn col = getShownColumnMappings().get(sortColumn);
if (col != null) {
fieldName = col.getColumnDbLabel();
}
}
Collections.sort(forms, new FieldComparator(fieldName, sortOrder, getRequestContext().getLocale()));
}
// fill the returned object with data
data.put("data", getDataRows(forms.subList(startIndex, endIndex + 1)));
JSONObject pageInfo = new JSONObject();
pageInfo.put("totalRowNum", forms.size());
data.put("pageInfo", pageInfo);
data.put("recordType", "array");
} catch (Exception e) {
// error creating data
}
return data.toString();
}
/**
* Returns the displayed form report columns generated from the form fields, with the column ID as key.<p>
*
* @return the displayed form report columns
*/
public Map<String, CmsFormReportColumn> getShownColumnMappings() {
if (m_columnMappings == null) {
m_columnMappings = new HashMap<String, CmsFormReportColumn>(getShownColumns().size());
for (Iterator<CmsFormReportColumn> i = getShownColumns().iterator(); i.hasNext();) {
CmsFormReportColumn col = i.next();
m_columnMappings.put(col.getColumnId(), col);
}
}
return m_columnMappings;
}
/**
* Returns the displayed columns generated from the form fields, including sub fields.<p>
*
* @return the displayed columns
*/
public List<CmsFormReportColumn> getShownColumns() {
return m_columns;
}
/**
* Returns the name of the skin to use, either <code>default</code>, <code>mac</code>, <code>vista</code> or <code>pink</code>.<p>
*
* @return the name of the skin to use
*/
public String getSkin() {
String skin = getReportContent().getStringValue(getCmsObject(), NODE_SKIN, getRequestContext().getLocale());
if (CmsStringUtil.isEmpty(skin)) {
skin = "mac";
}
return skin;
}
/**
* Returns the unsubstituted VFS path to the localized messages JS for the grid.<p>
*
* @return the unsubstituted VFS path to the localized messages JS
*/
public String getVfsPathGridMessages() {
String locale = getRequestContext().getLocale().toString();
String fileName = VFS_PATH_GRIDRESOURCES + "gt_msg_" + locale + ".js";
if (getCmsObject().existsResource(fileName)) {
// found JS messages for current locale
return fileName;
}
// use default English localization
return VFS_PATH_GRIDRESOURCES + "gt_msg_en.js";
}
/**
* Returns if the report output height should be calculated automatically.<p>
*
* @return <code>true</code> if the report output height should be calculated automatically, otherwise <code>false</code>
*/
public boolean isHeightAuto() {
if (m_heightAuto == null) {
m_heightAuto = Boolean.valueOf(VALUE_HEIGHT_AUTO.equalsIgnoreCase(getReportContent().getStringValue(
getCmsObject(),
NODE_GRIDHEIGHT,
getRequestContext().getLocale())));
}
return m_heightAuto.booleanValue();
}
/**
* Returns if the CSS style sheets can be included in the form output.<p>
*
* <b>Important</b>: to generate valid XHTML code, specify <code>false</code> as module
* parameter value of {@link CmsForm#MODULE_PARAM_CSS} and include the CSS in your template head manually.<br/>
* In this case, you can include the JSP <code>report_css.jsp</code>
* in the <code>elements/</code> sub folder of the form generator module.<p>
*
* @return <code>true</code> if the CSS style sheets can be included in the form output, otherwise <code>false</code>
*/
public boolean isIncludeStyleSheet() {
String cssParam = OpenCms.getModuleManager().getModule(CmsForm.MODULE_NAME).getParameter(
CmsForm.MODULE_PARAM_CSS);
if (CmsStringUtil.FALSE.equalsIgnoreCase(cssParam)) {
return false;
}
return true;
}
/**
* Returns if the report data should be loaded dynamically.<p>
*
* @return <code>true</code> if the report data should be loaded dynamically, otherwise <code>false</code>
*/
public boolean isLoadDynamic() {
if (m_loadDynamic == null) {
m_loadDynamic = Boolean.valueOf(getReportContent().getStringValue(
getCmsObject(),
NODE_LOADDYNAMIC,
getRequestContext().getLocale()));
}
return m_loadDynamic.booleanValue();
}
/**
* Returns if the labels of the fields should be shown.<p>
*
* @return <code>true</code> if the labels of the fields should be shown, otherwise <code>false</code>
*/
public boolean isShowDate() {
if (m_showDate == null) {
m_showDate = Boolean.valueOf(getReportContent().getStringValue(
getCmsObject(),
NODE_SHOWDATE,
getRequestContext().getLocale()));
}
return m_showDate.booleanValue();
}
/**
* Returns if the labels of the fields should be shown.<p>
*
* @return <code>true</code> if the labels of the fields should be shown, otherwise <code>false</code>
*/
public boolean isShowLabels() {
if (m_showLabels == null) {
m_showLabels = Boolean.valueOf(getReportContent().getStringValue(
getCmsObject(),
NODE_SHOWLABELS,
getRequestContext().getLocale()));
}
return m_showLabels.booleanValue();
}
/**
* Returns if the grid menu button should be shown.<p>
*
* @return <code>true</code> if the grid menu button should be shown, otherwise <code>false</code>
*/
public boolean isShowMenu() {
String menu = getReportContent().getStringValue(getCmsObject(), NODE_SHOWMENU, getRequestContext().getLocale());
return Boolean.valueOf(menu).booleanValue();
}
/**
* Returns if the main report output should be generated.<p>
*
* @return <code>true</code> if the main report output should be generated, otherwise <code>false</code>
*/
public boolean isShowReport() {
// return getRequest().getParameter(PARAM_ACTION) == null;
m_showReport = (getRequest().getParameter(PARAM_ACTION) == null);
return m_showReport;
}
/**
* Returns the JSON array with data generated from the given list of forms.<p>
*
* @param forms the forms to generate the data from
*
* @return the JSON array with data
*/
protected JSONArray getDataRows(List<CmsFormDataBean> forms) {
JSONArray data = new JSONArray();
for (Iterator<CmsFormDataBean> i = forms.iterator(); i.hasNext();) {
CmsFormDataBean dataBean = i.next();
JSONArray row = new JSONArray();
if (isShowDate()) {
// add submission date to row data
row.put(dataBean.getDateCreated());
}
for (Iterator<CmsFormReportColumn> k = getShownColumns().iterator(); k.hasNext();) {
String val = dataBean.getFieldValue(k.next().getColumnDbLabel());
if (CmsStringUtil.isEmpty(val)) {
// also store empty values
row.put("");
} else {
row.put(CmsEncoder.escapeXml(val));
}
}
data.put(row);
}
return data;
}
/**
* Returns all forms stored in the database.<p>
*
* @return all forms stored in the database
*/
protected List<CmsFormDataBean> getForms() {
if (m_forms == null) {
try {
CmsFormDatabaseFilter filter = CmsFormDatabaseFilter.DEFAULT;
filter = filter.filterFormId(getFormConfiguration().getFormId());
m_forms = CmsFormDataAccess.getInstance().readForms(filter);
} catch (Exception e) {
// error reading form data
m_forms = Collections.emptyList();
}
}
return m_forms;
}
/**
* Returns the report XML content.<p>
*
* @return the report XML content
*/
protected CmsXmlContent getReportContent() {
if (m_content == null) {
try {
CmsFile file = getCmsObject().readFile((String)m_pageContext.getAttribute("uri"));
m_content = CmsXmlContentFactory.unmarshal(getCmsObject(), file);
} catch (CmsException e) {
// should not happen
}
}
return m_content;
}
/**
* Initializes the report configuration.<p>
*
* @param context the JSP page context object
* @param req the JSP request
* @param res the JSP response
* @param reportUri URI of the report configuration file
*/
protected void initReport(PageContext context, HttpServletRequest req, HttpServletResponse res, String reportUri) {
m_columns = new ArrayList<CmsFormReportColumn>();
m_pageContext = context;
try {
CmsFile file = getCmsObject().readFile(reportUri);
m_content = CmsXmlContentFactory.unmarshal(getCmsObject(), file);
// get the web form URI
String formUri = m_content.getStringValue(getCmsObject(), NODE_URI, getRequestContext().getLocale());
m_formHandler = CmsFormHandlerFactory.create(context, req, res, formUri);
String checkedFieldsStr = m_content.getStringValue(
getCmsObject(),
NODE_FIELDS,
getRequestContext().getLocale());
List<String> checkedFields = new ArrayList<String>();
boolean showAllFields = CmsStringUtil.isEmptyOrWhitespaceOnly(checkedFieldsStr);
if (!showAllFields) {
checkedFields = CmsStringUtil.splitAsList(checkedFieldsStr, CmsReportCheckFieldsWidget.SEPARATOR_FIELDS);
}
// loop configured form fields to get the displayed fields
for (Iterator<I_CmsField> i = getFormConfiguration().getFields().iterator(); i.hasNext();) {
I_CmsField field = i.next();
if (field.getType().equals(CmsPagingField.getStaticType())
|| field.getType().equals(CmsEmptyField.getStaticType())) {
continue;
}
if (showAllFields || checkedFields.contains(field.getDbLabel())) {
// this field has to be shown, add it to the columns and check sub fields
m_columns.add(new CmsFormReportColumn(field));
if (field.isHasSubFields()) {
Iterator<Entry<String, List<I_CmsField>>> k = field.getSubFields().entrySet().iterator();
while (k.hasNext()) {
Map.Entry<String, List<I_CmsField>> entry = k.next();
m_columns.addAll(CmsFormReportColumn.getColumnsFromFields(entry.getValue()));
}
}
}
}
} catch (Exception e) {
// something went wrong, create a new exception
throw new CmsRuntimeException(Messages.get().container(Messages.ERR_REPORT_NO_FORM_URI_0), e);
}
}
}