org.pentaho.reporting.platform.plugin.ParameterXmlContentHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.pentaho.reporting.platform.plugin.ParameterXmlContentHandler.java

Source

/*
 * This program is free software; you can redistribute it and/or modify it under the 
 * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software 
 * Foundation.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this 
 * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html 
 * or from the Free Software Foundation, Inc., 
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * This program 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.
 *
 * Copyright 2010-2013 Pentaho Corporation.  All rights reserved.
 */

package org.pentaho.reporting.platform.plugin;

import java.io.OutputStream;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.TimeZone;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.platform.api.engine.IParameterProvider;
import org.pentaho.platform.util.UUIDUtil;
import org.pentaho.reporting.engine.classic.core.AttributeNames;
import org.pentaho.reporting.engine.classic.core.MasterReport;
import org.pentaho.reporting.engine.classic.core.ReportDataFactoryException;
import org.pentaho.reporting.engine.classic.core.ReportElement;
import org.pentaho.reporting.engine.classic.core.Section;
import org.pentaho.reporting.engine.classic.core.function.Expression;
import org.pentaho.reporting.engine.classic.core.function.FormulaExpression;
import org.pentaho.reporting.engine.classic.core.modules.output.pageable.pdf.PdfPageableModule;
import org.pentaho.reporting.engine.classic.core.modules.output.pageable.plaintext.PlainTextPageableModule;
import org.pentaho.reporting.engine.classic.core.modules.output.table.csv.CSVTableModule;
import org.pentaho.reporting.engine.classic.core.modules.output.table.html.HtmlTableModule;
import org.pentaho.reporting.engine.classic.core.modules.output.table.rtf.RTFTableModule;
import org.pentaho.reporting.engine.classic.core.modules.output.table.xls.ExcelTableModule;
import org.pentaho.reporting.engine.classic.core.parameters.AbstractParameter;
import org.pentaho.reporting.engine.classic.core.parameters.DefaultParameterContext;
import org.pentaho.reporting.engine.classic.core.parameters.ListParameter;
import org.pentaho.reporting.engine.classic.core.parameters.ParameterAttributeNames;
import org.pentaho.reporting.engine.classic.core.parameters.ParameterContext;
import org.pentaho.reporting.engine.classic.core.parameters.ParameterContextWrapper;
import org.pentaho.reporting.engine.classic.core.parameters.ParameterDefinitionEntry;
import org.pentaho.reporting.engine.classic.core.parameters.ParameterValues;
import org.pentaho.reporting.engine.classic.core.parameters.PlainParameter;
import org.pentaho.reporting.engine.classic.core.parameters.ReportParameterDefinition;
import org.pentaho.reporting.engine.classic.core.parameters.StaticListParameter;
import org.pentaho.reporting.engine.classic.core.parameters.ValidationMessage;
import org.pentaho.reporting.engine.classic.core.parameters.ValidationResult;
import org.pentaho.reporting.engine.classic.core.style.ElementStyleKeys;
import org.pentaho.reporting.libraries.base.util.NullOutputStream;
import org.pentaho.reporting.engine.classic.core.util.ReportParameterValues;
import org.pentaho.reporting.engine.classic.core.util.beans.BeanException;
import org.pentaho.reporting.engine.classic.core.util.beans.ConverterRegistry;
import org.pentaho.reporting.engine.classic.core.util.beans.ValueConverter;
import org.pentaho.reporting.engine.classic.extensions.drilldown.DrillDownProfile;
import org.pentaho.reporting.engine.classic.extensions.drilldown.DrillDownProfileMetaData;
import org.pentaho.reporting.libraries.base.util.StringUtils;
import org.pentaho.reporting.libraries.formula.DefaultFormulaContext;
import org.pentaho.reporting.libraries.formula.lvalues.DataTable;
import org.pentaho.reporting.libraries.formula.lvalues.FormulaFunction;
import org.pentaho.reporting.libraries.formula.lvalues.LValue;
import org.pentaho.reporting.libraries.formula.lvalues.StaticValue;
import org.pentaho.reporting.libraries.formula.parser.FormulaParser;
import org.pentaho.reporting.platform.plugin.messages.Messages;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

public class ParameterXmlContentHandler {
    private static class OutputParameterCollector {
        private OutputParameterCollector() {
        }

        public String[] collectParameter(final MasterReport reportDefinition) {
            final LinkedHashSet<String> parameter = new LinkedHashSet<String>();

            inspectElement(reportDefinition, parameter);
            traverseSection(reportDefinition, parameter);

            return parameter.toArray(new String[parameter.size()]);
        }

        private void traverseSection(final Section section, final LinkedHashSet<String> parameter) {
            final int count = section.getElementCount();
            for (int i = 0; i < count; i++) {
                final ReportElement element = section.getElement(i);
                inspectElement(element, parameter);
                if (element instanceof Section) {
                    traverseSection((Section) element, parameter);
                }
            }
        }

        private void inspectElement(final ReportElement element, final LinkedHashSet<String> parameter) {
            try {
                final Expression expression = element.getStyleExpression(ElementStyleKeys.HREF_TARGET);
                if (expression instanceof FormulaExpression == false) {
                    // DrillDown only works with the formula function of the same name
                    return;
                }

                final FormulaExpression fe = (FormulaExpression) expression;
                final String formulaText = fe.getFormulaExpression();
                if (StringUtils.isEmpty(formulaText)) {
                    // DrillDown only works with the formula function of the same name
                    return;
                }

                if (formulaText.startsWith("DRILLDOWN") == false) // NON-NLS
                {
                    // DrillDown only works if the function is the only element. Everything else is beyond our control.
                    return;
                }
                final FormulaParser formulaParser = new FormulaParser();
                final LValue value = formulaParser.parse(formulaText);
                if (value instanceof FormulaFunction == false) {
                    // Not a valid formula or a complex term - we do not handle that
                    return;
                }
                final DefaultFormulaContext context = new DefaultFormulaContext();
                value.initialize(context);

                final FormulaFunction fn = (FormulaFunction) value;
                final LValue[] params = fn.getChildValues();
                if (params.length != 3) {
                    // Malformed formula: Need 3 parameter
                    return;
                }
                final String config = extractText(params[0]);
                if (config == null) {
                    // Malformed formula: No statically defined config profile
                    return;
                }

                final DrillDownProfile profile = DrillDownProfileMetaData.getInstance().getDrillDownProfile(config);
                if (profile == null) {
                    // Malformed formula: Unknown drilldown profile
                    return;
                }

                if ("pentaho".equals(profile.getAttribute("group")) == false) // NON-NLS
                {
                    // Only 'pentaho' drill-down profiles can be used. Filters out all other third party drilldowns
                    return;
                }

                if (params[2] instanceof DataTable == false) {
                    // Malformed formula: Not a parameter table
                    return;
                }
                final DataTable dataTable = (DataTable) params[2];
                final int rowCount = dataTable.getRowCount();
                final int colCount = dataTable.getColumnCount();
                if (colCount != 2) {
                    // Malformed formula: Parameter table is invalid. Must be two cols, many rows ..
                    return;
                }

                for (int i = 0; i < rowCount; i++) {
                    final LValue valueAt = dataTable.getValueAt(i, 0);
                    final String name = extractText(valueAt);
                    if (name == null) {
                        continue;
                    }
                    parameter.add(name);
                }
            } catch (Exception e) {
                // ignore ..
            }
        }

        private String extractText(final LValue value) {
            if (value == null) {
                return null;
            }
            if (value.isConstant()) {
                if (value instanceof StaticValue) {
                    final StaticValue staticValue = (StaticValue) value;
                    final Object o = staticValue.getValue();
                    if (o == null) {
                        return null; // NON-NLS
                    }
                    return String.valueOf(o);
                }
            }
            return null; // NON-NLS

        }

    }

    private static final Log logger = LogFactory.getLog(ParameterXmlContentHandler.class);
    public static final String SYS_PARAM_ACCEPTED_PAGE = "accepted-page";

    private Map<String, ParameterDefinitionEntry> systemParameter;

    private Document document;
    private IParameterProvider requestParameters;
    private Map<String, Object> inputs;

    public static final String SYS_PARAM_RENDER_MODE = "renderMode";
    private static final String SYS_PARAM_OUTPUT_TARGET = SimpleReportingComponent.OUTPUT_TARGET;
    private static final String SYS_PARAM_DESTINATION = "destination";
    public static final String SYS_PARAM_CONTENT_LINK = "::cl";
    public static final String SYS_PARAM_SESSION_ID = "::session";
    private static final String GROUP_SYSTEM = "system";
    private static final String GROUP_PARAMETERS = "parameters";
    private static final String SYS_PARAM_TAB_NAME = "::TabName";
    private static final String SYS_PARAM_TAB_ACTIVE = "::TabActive";

    private static final String SYS_PARAM_HTML_PROPORTIONAL_WIDTH = "htmlProportionalWidth";
    private static final String CONFIG_PARAM_HTML_PROPORTIONAL_WIDTH = "org.pentaho.reporting.engine.classic.core.modules.output.table.html.ProportionalColumnWidths";

    public ParameterXmlContentHandler(final ParameterContentGenerator contentGenerator) {
        this.inputs = contentGenerator.createInputs();
        this.requestParameters = contentGenerator.getRequestParameters();
    }

    private IParameterProvider getRequestParameters() {
        return requestParameters;
    }

    private Map<String, ParameterDefinitionEntry> getSystemParameter() {
        if (systemParameter == null) {
            final Map<String, ParameterDefinitionEntry> parameter = new LinkedHashMap<String, ParameterDefinitionEntry>();
            parameter.put(SYS_PARAM_OUTPUT_TARGET, createOutputParameter());
            parameter.put(SYS_PARAM_CONTENT_LINK, createContentLinkingParameter()); // NON-NLS
            parameter.put(SYS_PARAM_TAB_NAME, createGenericSystemParameter(SYS_PARAM_TAB_NAME, false, true)); // NON-NLS
            parameter.put(SYS_PARAM_TAB_ACTIVE,
                    createGenericBooleanSystemParameter(SYS_PARAM_TAB_ACTIVE, false, true)); // NON-NLS
            //    parameter.put("solution", createGenericSystemParameter("solution", false, false)); // NON-NLS
            parameter.put("yield-rate", createGenericIntSystemParameter("yield-rate", false, false)); // NON-NLS
            parameter.put(SYS_PARAM_ACCEPTED_PAGE,
                    createGenericIntSystemParameter(SYS_PARAM_ACCEPTED_PAGE, false, false)); // NON-NLS
            parameter.put(SYS_PARAM_SESSION_ID, createGenericSystemParameter(SYS_PARAM_SESSION_ID, false, false)); // NON-NLS
            parameter.put("path", createGenericSystemParameter("path", false, false)); // NON-NLS
            //    parameter.put("name", createGenericSystemParameter("name", false, false)); // NON-NLS
            //    parameter.put("action", createGenericSystemParameter("action", true, false)); // NON-NLS
            parameter.put("id", createGenericSystemParameter("id", false, false)); // NON-NLS
            parameter.put("output-type", createGenericSystemParameter("output-type", true, false)); // NON-NLS
            parameter.put("layout", createGenericSystemParameter("layout", true, false)); // NON-NLS
            parameter.put("content-handler-pattern",
                    createGenericSystemParameter("content-handler-pattern", true, false)); // NON-NLS
            parameter.put("autoSubmit", createGenericBooleanSystemParameter("autoSubmit", true, true)); // NON-NLS
            parameter.put("autoSubmitUI", createGenericBooleanSystemParameter("autoSubmitUI", true, true)); // NON-NLS
            parameter.put("dashboard-mode", createGenericBooleanSystemParameter("dashboard-mode", false, true)); // NON-NLS
            parameter.put("showParameters", createGenericBooleanSystemParameter("showParameters", true, true)); // NON-NLS
            parameter.put("paginate", createGenericBooleanSystemParameter("paginate", true, false)); // NON-NLS
            parameter.put("ignoreDefaultDates",
                    createGenericBooleanSystemParameter("ignoreDefaultDates", true, false)); // NON-NLS
            parameter.put("print", createGenericBooleanSystemParameter("print", false, false)); // NON-NLS
            parameter.put("printer-name", createGenericSystemParameter("printer-name", false, false)); // NON-NLS
            parameter.put(SYS_PARAM_RENDER_MODE, createRenderModeSystemParameter()); // NON-NLS
            parameter.put(SYS_PARAM_HTML_PROPORTIONAL_WIDTH,
                    createGenericBooleanSystemParameter(SYS_PARAM_HTML_PROPORTIONAL_WIDTH, false, true));

            systemParameter = Collections.unmodifiableMap(parameter);
        }

        return systemParameter;
    }

    /**
     * Defines whether parameter with display-type "datepicker" that have no default value set shall
     * default to "today". This setting generates a default value for the parameter UI, but has no effect
     * otherwise. It is flawed from the very beginning and should not be used.
     *
     * @return whether we generate default dates.
     */
    private boolean isGenerateDefaultDates() {
        final Object value = inputs.get("ignoreDefaultDates"); // NON-NLS
        if (value == null) {
            // we do not generate default dates until it is explicitly requested.
            // if the users want default values for parameters then let them define those in the parameter
            return false;
        }

        return "true".equals(value);
    }

    public void createParameterContent(final OutputStream outputStream, final Serializable fileId,
            final String path, boolean overrideOutputType, MasterReport report) throws Exception {
        final Object rawSessionId = inputs.get(ParameterXmlContentHandler.SYS_PARAM_SESSION_ID);
        if ((rawSessionId instanceof String) == false || "".equals(rawSessionId)) {
            inputs.put(ParameterXmlContentHandler.SYS_PARAM_SESSION_ID, UUIDUtil.getUUIDAsString());
        }

        this.document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();

        final IParameterProvider requestParams = getRequestParameters();

        final SimpleReportingComponent reportComponent = new SimpleReportingComponent();
        reportComponent.setReportFileId(fileId);
        if (report != null) {
            reportComponent.setReport(report);
        }
        reportComponent.setPaginateOutput(true);

        final boolean isMobile = "true".equals(requestParams.getStringParameter("mobile", "false")); //$NON-NLS-1$ //$NON-NLS-2$

        if (isMobile) {
            overrideOutputType = true;
            reportComponent.setForceDefaultOutputTarget(true);
        } else {
            reportComponent.setForceDefaultOutputTarget(overrideOutputType);
        }
        reportComponent.setDefaultOutputTarget(HtmlTableModule.TABLE_HTML_PAGE_EXPORT_TYPE);

        if (path.endsWith(".prpti")) {
            reportComponent.setForceUnlockPreferredOutput(true);
        }

        reportComponent.setInputs(inputs);

        report = reportComponent.getReport();

        final DefaultParameterContext parameterContext = new DefaultParameterContext(report);
        final ValidationResult vr;
        final Element parameters;
        try {
            // apply inputs to parameters
            final ValidationResult validationResult = ReportContentUtil.applyInputsToReportParameters(report,
                    parameterContext, inputs, new ValidationResult());

            final ReportParameterDefinition reportParameterDefinition = report.getParameterDefinition();
            vr = reportParameterDefinition.getValidator().validate(validationResult, reportParameterDefinition,
                    parameterContext);

            parameters = document.createElement(GROUP_PARAMETERS); //$NON-NLS-1$
            parameters.setAttribute("is-prompt-needed", String.valueOf(vr.isEmpty() == false)); //$NON-NLS-1$ //$NON-NLS-2$
            parameters.setAttribute("ignore-biserver-5538", "true");

            // check if pagination is allowed and turned on

            final Boolean autoSubmitFlag = requestFlag("autoSubmit", report, AttributeNames.Core.NAMESPACE,
                    AttributeNames.Core.AUTO_SUBMIT_PARAMETER,
                    "org.pentaho.reporting.engine.classic.core.ParameterAutoSubmit");
            if (Boolean.TRUE.equals(autoSubmitFlag)) {
                parameters.setAttribute("autoSubmit", "true");
            } else if (Boolean.FALSE.equals(autoSubmitFlag)) {
                parameters.setAttribute("autoSubmit", "false");
            }

            final Boolean autoSubmitUiFlag = requestFlag("autoSubmitUI", report, // NON-NLS
                    AttributeNames.Core.NAMESPACE, AttributeNames.Core.AUTO_SUBMIT_DEFAULT,
                    "org.pentaho.reporting.engine.classic.core.ParameterAutoSubmitUI");
            if (Boolean.FALSE.equals(autoSubmitUiFlag)) {
                parameters.setAttribute("autoSubmitUI", "false"); // NON-NLS
            } else {
                parameters.setAttribute("autoSubmitUI", "true"); // NON-NLS
            }

            parameters.setAttribute("layout", requestConfiguration("layout", report, // NON-NLS
                    AttributeNames.Core.NAMESPACE, AttributeNames.Core.PARAMETER_UI_LAYOUT,
                    "org.pentaho.reporting.engine.classic.core.ParameterUiLayout"));

            final ParameterDefinitionEntry[] parameterDefinitions = reportParameterDefinition
                    .getParameterDefinitions();
            // Collect all parameter, but allow user-parameter to override system parameter.
            // It is the user's problem if the types do not match and weird errors occur, but
            // there are sensible usecases where this should be allowed.
            // System parameter must come last in the list, as this is how it was done in the original
            // version and this is how people expect it to be now.
            final LinkedHashMap<String, ParameterDefinitionEntry> reportParameters = new LinkedHashMap<String, ParameterDefinitionEntry>();
            for (final ParameterDefinitionEntry parameter : parameterDefinitions) {
                reportParameters.put(parameter.getName(), parameter);
            }
            for (final Map.Entry<String, ParameterDefinitionEntry> entry : getSystemParameter().entrySet()) {
                if (reportParameters.containsKey(entry.getKey()) == false) {
                    reportParameters.put(entry.getKey(), entry.getValue());
                }
            }

            if (overrideOutputType) {
                final ParameterDefinitionEntry definitionEntry = reportParameters
                        .get(SimpleReportingComponent.OUTPUT_TARGET);
                if (definitionEntry instanceof AbstractParameter) {
                    final AbstractParameter parameter = (AbstractParameter) definitionEntry;
                    parameter.setHidden(true);
                    parameter.setMandatory(false);
                }
            } else {
                hideOutputParameterIfLocked(report, reportParameters);
            }

            final Map<String, Object> inputs = computeRealInput(parameterContext, reportParameters,
                    reportComponent.getComputedOutputTarget(), vr);

            final Boolean showParameterUI = requestFlag("showParameters", report, // NON-NLS
                    AttributeNames.Core.NAMESPACE, AttributeNames.Core.SHOW_PARAMETER_UI, null);
            if (Boolean.FALSE.equals(showParameterUI)) {
                inputs.put("showParameters", Boolean.FALSE); // NON-NLS
            } else {
                inputs.put("showParameters", Boolean.TRUE); // NON-NLS
            }

            // Adding proportional width config parameter
            String proportionalWidth = report.getReportConfiguration()
                    .getConfigProperty(CONFIG_PARAM_HTML_PROPORTIONAL_WIDTH);
            inputs.put(SYS_PARAM_HTML_PROPORTIONAL_WIDTH, Boolean.valueOf(proportionalWidth));

            for (final ParameterDefinitionEntry parameter : reportParameters.values()) {
                final Object selections = inputs.get(parameter.getName());
                final ParameterContextWrapper wrapper = new ParameterContextWrapper(parameterContext,
                        vr.getParameterValues());
                parameters.appendChild(createParameterElement(parameter, wrapper, selections));
            }

            if (vr.isEmpty() == false) {
                parameters.appendChild(createErrorElements(vr));
            }

            final String[] outputParameter = new OutputParameterCollector().collectParameter(report);
            for (int i = 0; i < outputParameter.length; i++) {
                final String outputParameterName = outputParameter[i];
                //  <output-parameter displayName="Territory" id="[Markets].[Territory]"/>
                final Element element = document.createElement("output-parameter");// NON-NLS
                element.setAttribute("displayName", outputParameterName);// NON-NLS
                element.setAttribute("id", outputParameterName);// NON-NLS
                parameters.appendChild(element);
            }

            if (vr.isEmpty() && reportComponent.getComputedOutputTarget()
                    .equals(HtmlTableModule.TABLE_HTML_PAGE_EXPORT_TYPE)) //$NON-NLS-1$ //$NON-NLS-2$
            {
                appendPageCount(reportComponent, parameters);
            }
            document.appendChild(parameters);

            final DOMSource source = new DOMSource(document);
            final StreamResult result = new StreamResult(outputStream);
            final Transformer transformer = TransformerFactory.newInstance().newTransformer();
            transformer.transform(source, result);
            // close parameter context
        } finally {
            parameterContext.close();
        }
    }

    private Map<String, Object> computeRealInput(final ParameterContext parameterContext,
            final LinkedHashMap<String, ParameterDefinitionEntry> reportParameters,
            final String computedOutputTarget, final ValidationResult result) {
        final Map<String, Object> realInputs = new HashMap<String, Object>();
        realInputs.put(SYS_PARAM_DESTINATION, lookupDestination());

        final ReportParameterValues parameterValues = result.getParameterValues();

        for (final ParameterDefinitionEntry parameter : reportParameters.values()) {
            final String parameterName = parameter.getName();
            final Object parameterFromReport = parameterValues.get(parameterName);
            if (parameterFromReport != null) {
                // always prefer the report parameter. The user's input has been filtered already and values
                // may have been replaced by a post-processing formula.
                //
                realInputs.put(parameterName, parameterFromReport);
                continue;
            }

            // the parameter values collection only contains declared parameter. So everything else will
            // be handled now. This is also the time to handle rejected parameter. For these parameter,
            // the calculated value for the report is <null>.
            final Object value = inputs.get(parameterName);
            if (value == null) {
                // have no value, so we use the default value ..
                realInputs.put(parameterName, null);
                continue;
            }

            try {
                final Object translatedValue = ReportContentUtil.computeParameterValue(parameterContext, parameter,
                        value);
                if (translatedValue != null) {
                    realInputs.put(parameterName, translatedValue);
                } else {
                    realInputs.put(parameterName, null);
                }
            } catch (Exception be) {
                if (logger.isDebugEnabled()) {
                    logger.debug(Messages.getInstance().getString("ReportPlugin.debugParameterCannotBeConverted",
                            parameter.getName(), String.valueOf(value)), be);
                }
            }
        }

        // thou cannot override the output target with invalid values ..
        realInputs.put(SYS_PARAM_OUTPUT_TARGET, computedOutputTarget);
        return realInputs;
    }

    private void hideOutputParameterIfLocked(final MasterReport report,
            final Map<String, ParameterDefinitionEntry> reportParameters) {
        final boolean lockOutputType = Boolean.TRUE.equals(
                report.getAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.LOCK_PREFERRED_OUTPUT_TYPE));
        final ParameterDefinitionEntry definitionEntry = reportParameters
                .get(SimpleReportingComponent.OUTPUT_TARGET);
        if (definitionEntry instanceof AbstractParameter) {
            final AbstractParameter parameter = (AbstractParameter) definitionEntry;
            parameter.setHidden(lockOutputType);
            parameter.setMandatory(!lockOutputType);
        }
    }

    private Element createParameterElement(final ParameterDefinitionEntry parameter,
            final ParameterContext parameterContext, final Object selections)
            throws BeanException, ReportDataFactoryException {
        try {
            final Element parameterElement = document.createElement("parameter"); //$NON-NLS-1$
            parameterElement.setAttribute("name", parameter.getName()); //$NON-NLS-1$
            final Class<?> valueType = parameter.getValueType();
            parameterElement.setAttribute("type", valueType.getName()); //$NON-NLS-1$
            parameterElement.setAttribute("is-mandatory", String.valueOf(parameter.isMandatory())); //$NON-NLS-1$ //$NON-NLS-2$

            final String[] namespaces = parameter.getParameterAttributeNamespaces();
            for (int i = 0; i < namespaces.length; i++) {
                final String namespace = namespaces[i];
                final String[] attributeNames = parameter.getParameterAttributeNames(namespace);
                for (final String attributeName : attributeNames) {
                    final String attributeValue = parameter.getParameterAttribute(namespace, attributeName,
                            parameterContext);
                    // expecting: label, parameter-render-type, parameter-layout
                    // but others possible as well, so we set them all
                    final Element attributeElement = document.createElement("attribute"); // NON-NLS
                    attributeElement.setAttribute("namespace", namespace); // NON-NLS
                    attributeElement.setAttribute("name", attributeName); // NON-NLS
                    attributeElement.setAttribute("value", attributeValue); // NON-NLS

                    parameterElement.appendChild(attributeElement);
                }
            }

            final Class<?> elementValueType;
            if (valueType.isArray()) {
                elementValueType = valueType.getComponentType();
            } else {
                elementValueType = valueType;
            }

            if (Date.class.isAssignableFrom(elementValueType)) {
                parameterElement.setAttribute("timzone-hint", computeTimeZoneHint(parameter, parameterContext));//$NON-NLS-1$
            }

            final LinkedHashSet<Object> selectionSet = new LinkedHashSet<Object>();
            if (selections != null) {
                if (selections.getClass().isArray()) {
                    final int length = Array.getLength(selections);
                    for (int i = 0; i < length; i++) {
                        final Object value = Array.get(selections, i);
                        selectionSet.add(resolveSelectionValue(value));
                    }
                } else {
                    selectionSet.add(resolveSelectionValue(selections));
                }
            } else {
                final String type = parameter.getParameterAttribute(ParameterAttributeNames.Core.NAMESPACE,
                        ParameterAttributeNames.Core.TYPE, parameterContext);
                if (ParameterAttributeNames.Core.TYPE_DATEPICKER.equals(type)
                        && Date.class.isAssignableFrom(valueType)) {
                    if (isGenerateDefaultDates()) {
                        selectionSet.add(new Date());
                    }
                }
            }

            @SuppressWarnings("rawtypes")
            final LinkedHashSet handledValues = (LinkedHashSet) selectionSet.clone();

            if (parameter instanceof ListParameter) {
                final ListParameter asListParam = (ListParameter) parameter;
                parameterElement.setAttribute("is-multi-select", //$NON-NLS-1$
                        String.valueOf(asListParam.isAllowMultiSelection())); //$NON-NLS-2$
                parameterElement.setAttribute("is-strict", String.valueOf(asListParam.isStrictValueCheck())); //$NON-NLS-1$ //$NON-NLS-2$
                parameterElement.setAttribute("is-list", "true"); //$NON-NLS-1$ //$NON-NLS-2$

                final Element valuesElement = document.createElement("values"); //$NON-NLS-1$
                parameterElement.appendChild(valuesElement);

                final ParameterValues possibleValues = asListParam.getValues(parameterContext);
                for (int i = 0; i < possibleValues.getRowCount(); i++) {
                    final Object key = possibleValues.getKeyValue(i);
                    final Object value = possibleValues.getTextValue(i);

                    final Element valueElement = document.createElement("value"); //$NON-NLS-1$
                    valuesElement.appendChild(valueElement);

                    valueElement.setAttribute("label", String.valueOf(value)); //$NON-NLS-1$ //$NON-NLS-2$
                    valueElement.setAttribute("type", elementValueType.getName()); //$NON-NLS-1$

                    if (key instanceof Number) {
                        final BigDecimal bd = new BigDecimal(String.valueOf(key));
                        valueElement.setAttribute("selected", String.valueOf(selectionSet.contains(bd)));//$NON-NLS-1$
                        handledValues.remove(bd);
                    } else if (key == null) {
                        if (selections == null || selectionSet.contains(null)) {
                            valueElement.setAttribute("selected", "true");//$NON-NLS-1$
                            handledValues.remove(null);
                        }
                    } else {
                        valueElement.setAttribute("selected", String.valueOf(selectionSet.contains(key)));//$NON-NLS-1$
                        handledValues.remove(key);
                    }
                    if (key == null) {
                        valueElement.setAttribute("null", "true"); //$NON-NLS-1$ //$NON-NLS-2$
                    } else {
                        valueElement.setAttribute("null", "false"); //$NON-NLS-1$ //$NON-NLS-2$
                        valueElement.setAttribute("value",
                                convertParameterValueToString(parameter, parameterContext, key, elementValueType)); //$NON-NLS-1$ //$NON-NLS-2$
                    }

                }

                // Only add invalid values to the selection list for non-strict parameters
                if (!asListParam.isStrictValueCheck()) {
                    for (final Object key : handledValues) {
                        final Element valueElement = document.createElement("value"); //$NON-NLS-1$
                        valuesElement.appendChild(valueElement);

                        valueElement.setAttribute("label", Messages.getInstance() //$NON-NLS-1$
                                .getString("ReportPlugin.autoParameter", String.valueOf(key))); //$NON-NLS-1$
                        valueElement.setAttribute("type", elementValueType.getName()); //$NON-NLS-1$

                        if (key instanceof Number) {
                            BigDecimal bd = new BigDecimal(String.valueOf(key));
                            valueElement.setAttribute("selected", String.valueOf(selectionSet.contains(bd)));//$NON-NLS-1$
                        } else {
                            valueElement.setAttribute("selected", String.valueOf(selectionSet.contains(key)));//$NON-NLS-1$
                        }

                        if (key == null) {
                            valueElement.setAttribute("null", "true"); //$NON-NLS-1$ //$NON-NLS-2$
                        } else {
                            valueElement.setAttribute("null", "false"); //$NON-NLS-1$ //$NON-NLS-2$
                            valueElement.setAttribute("value", convertParameterValueToString(parameter,
                                    parameterContext, key, elementValueType)); //$NON-NLS-1$ //$NON-NLS-2$
                        }

                    }
                }
            } else if (parameter instanceof PlainParameter) {
                // apply defaults, this is the easy case
                parameterElement.setAttribute("is-multi-select", "false"); //$NON-NLS-1$ //$NON-NLS-2$
                parameterElement.setAttribute("is-strict", "false"); //$NON-NLS-1$ //$NON-NLS-2$
                parameterElement.setAttribute("is-list", "false"); //$NON-NLS-1$ //$NON-NLS-2$

                if (selections != null) {
                    final Element valuesElement = document.createElement("values"); //$NON-NLS-1$
                    parameterElement.appendChild(valuesElement);

                    final Element valueElement = document.createElement("value"); //$NON-NLS-1$
                    valuesElement.appendChild(valueElement);
                    valueElement.setAttribute("type", valueType.getName()); //$NON-NLS-1$
                    valueElement.setAttribute("selected", "true");//$NON-NLS-1$
                    valueElement.setAttribute("null", "false"); //$NON-NLS-1$ //$NON-NLS-2$
                    final String value = convertParameterValueToString(parameter, parameterContext, selections,
                            valueType);
                    valueElement.setAttribute("value", value); //$NON-NLS-1$ //$NON-NLS-2$
                    valueElement.setAttribute("label", value); //$NON-NLS-1$ //$NON-NLS-2$
                }
            }
            return parameterElement;
        } catch (BeanException be) {
            logger.error(Messages.getInstance().getString("ReportPlugin.errorFailedToGenerateParameter",
                    parameter.getName(), String.valueOf(selections)), be);
            throw be;
        }
    }

    private Object resolveSelectionValue(Object value) {
        // convert all numerics to BigDecimals for cross-numeric-class matching
        if (value instanceof Number) {
            return new BigDecimal(String.valueOf(value.toString()));
        } else {
            return value;
        }

    }

    private String computeTimeZoneHint(final ParameterDefinitionEntry parameter,
            final ParameterContext parameterContext) {
        // add a timezone hint ..
        final String timezoneSpec = parameter.getParameterAttribute(ParameterAttributeNames.Core.NAMESPACE,
                ParameterAttributeNames.Core.TIMEZONE, parameterContext);
        if ("client".equals(timezoneSpec))//$NON-NLS-1$
        {
            return ("");
        } else {
            final TimeZone timeZone;
            final StringBuffer value = new StringBuffer();
            if (timezoneSpec == null || "server".equals(timezoneSpec))//$NON-NLS-1$
            {
                timeZone = TimeZone.getDefault();
            } else if ("utc".equals(timezoneSpec))//$NON-NLS-1$
            {
                timeZone = TimeZone.getTimeZone("UTC");//$NON-NLS-1$
            } else {
                timeZone = TimeZone.getTimeZone(timezoneSpec);
            }

            final int rawOffset = timeZone.getRawOffset();
            if (rawOffset < 0) {
                value.append("-");
            } else {
                value.append("+");
            }

            final int seconds = Math.abs(rawOffset / 1000);
            final int minutesRaw = seconds / 60;
            final int hours = minutesRaw / 60;
            final int minutes = minutesRaw % 60;
            if (hours < 10) {
                value.append("0");
            }
            value.append(hours);
            if (minutes < 10) {
                value.append("0");
            }
            value.append(minutes);
            return value.toString();
        }
    }

    public static String convertParameterValueToString(final ParameterDefinitionEntry parameter,
            final ParameterContext context, final Object value, final Class<?> type) throws BeanException {
        if (value == null) {
            return null;
        }

        // PIR-652
        if (value instanceof Object[]) {
            Object[] o = (Object[]) value;
            if (o.length == 1) {
                return String.valueOf(o[0]);
            }
        }

        // PIR-724 - Convert String arrays to a single string with values separated by '|'
        if (value instanceof String[]) {
            String[] o = (String[]) value;
            return org.apache.commons.lang.StringUtils.join(o, '|');
        }

        final ValueConverter valueConverter = ConverterRegistry.getInstance().getValueConverter(type);
        if (valueConverter == null) {
            return String.valueOf(value);
        }
        if (Date.class.isAssignableFrom(type)) {
            if (value instanceof Date == false) {
                throw new BeanException(
                        Messages.getInstance().getString("ReportPlugin.errorNonDateParameterValue"));
            }

            final String timezone = parameter.getParameterAttribute(ParameterAttributeNames.Core.NAMESPACE,
                    ParameterAttributeNames.Core.TIMEZONE, context);
            final DateFormat dateFormat;
            if (timezone == null || "server".equals(timezone) || //$NON-NLS-1$
                    "client".equals(timezone))//$NON-NLS-1$
            {
                // nothing needed ..
                // for server: Just print it as it is, including the server timezone.
                dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");//$NON-NLS-1$
            } else {
                // for convinience for the clients we send the date in the correct timezone.
                final TimeZone timeZoneObject;
                if ("utc".equals(timezone))//$NON-NLS-1$
                {
                    timeZoneObject = TimeZone.getTimeZone("UTC");//$NON-NLS-1$
                } else {
                    timeZoneObject = TimeZone.getTimeZone(timezone);
                }
                dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");//$NON-NLS-1$
                dateFormat.setTimeZone(timeZoneObject);
            }
            final Date d = (Date) value;
            return dateFormat.format(d);
        }
        if (Number.class.isAssignableFrom(type)) {
            final ValueConverter numConverter = ConverterRegistry.getInstance().getValueConverter(BigDecimal.class);
            return numConverter.toAttributeValue(new BigDecimal(String.valueOf(value)));
        }
        return valueConverter.toAttributeValue(value);
    }

    private Element createErrorElements(final ValidationResult vr) {
        final Element errors = document.createElement("errors"); //$NON-NLS-1$
        for (final String property : vr.getProperties()) {
            for (final ValidationMessage message : vr.getErrors(property)) {
                final Element error = document.createElement("error"); //$NON-NLS-1$
                error.setAttribute("parameter", property);//$NON-NLS-1$
                error.setAttribute("message", message.getMessage());//$NON-NLS-1$
                errors.appendChild(error);
            }
        }
        final ValidationMessage[] globalMessages = vr.getErrors();
        for (int i = 0; i < globalMessages.length; i++) {
            final ValidationMessage globalMessage = globalMessages[i];
            final Element error = document.createElement("global-error"); //$NON-NLS-1$
            error.setAttribute("message", globalMessage.getMessage());//$NON-NLS-1$
            errors.appendChild(error);
        }
        return errors;
    }

    private static void appendPageCount(final SimpleReportingComponent reportComponent, final Element parameters)
            throws Exception {
        reportComponent.setOutputStream(new NullOutputStream());

        // so that we don't actually produce anything, we'll accept no pages in this mode
        final int acceptedPage = reportComponent.getAcceptedPage();
        reportComponent.setAcceptedPage(-1);

        // we can ONLY get the # of pages by asking the report to run
        if (reportComponent.validate()) {
            if (!reportComponent.outputSupportsPagination()) {
                return;
            }
            final int totalPageCount = reportComponent.paginate();
            parameters.setAttribute(SimpleReportingComponent.PAGINATE_OUTPUT, "true"); //$NON-NLS-1$
            parameters.setAttribute("page-count", String.valueOf(totalPageCount)); //$NON-NLS-1$ //$NON-NLS-2$
            // use the saved value (we changed it to -1 for performance)
            parameters.setAttribute(SimpleReportingComponent.ACCEPTED_PAGE, String.valueOf(acceptedPage)); //$NON-NLS-1$
        }
    }

    private PlainParameter createGenericSystemParameter(final String parameterName, final boolean deprecated,
            final boolean preferredParameter) {
        return createGenericSystemParameter(parameterName, deprecated, preferredParameter, String.class);
    }

    private PlainParameter createGenericSystemParameter(final String parameterName, final boolean deprecated,
            final boolean preferredParameter, final Class<?> type) {
        final PlainParameter destinationParameter = new PlainParameter(parameterName, type);
        destinationParameter.setMandatory(false);
        destinationParameter.setHidden(true);
        destinationParameter.setRole(ParameterAttributeNames.Core.ROLE_SYSTEM_PARAMETER);
        destinationParameter.setParameterAttribute(ParameterAttributeNames.Core.NAMESPACE,
                ParameterAttributeNames.Core.PREFERRED, String.valueOf(preferredParameter));
        destinationParameter.setParameterAttribute(ParameterAttributeNames.Core.NAMESPACE,
                ParameterAttributeNames.Core.PARAMETER_GROUP, GROUP_SYSTEM);
        destinationParameter.setParameterAttribute(ParameterAttributeNames.Core.NAMESPACE,
                ParameterAttributeNames.Core.PARAMETER_GROUP_LABEL,
                Messages.getInstance().getString("ReportPlugin.SystemParameters"));
        destinationParameter.setParameterAttribute(ParameterAttributeNames.Core.NAMESPACE,
                ParameterAttributeNames.Core.LABEL, parameterName);
        destinationParameter.setParameterAttribute(ParameterAttributeNames.Core.NAMESPACE,
                ParameterAttributeNames.Core.TYPE, ParameterAttributeNames.Core.TYPE_TEXTBOX);
        destinationParameter.setDeprecated(deprecated);
        return destinationParameter;
    }

    private PlainParameter createGenericBooleanSystemParameter(final String parameterName, final boolean deprecated,
            final boolean preferredParameter) {
        return createGenericSystemParameter(parameterName, deprecated, preferredParameter, Boolean.class);
    }

    private PlainParameter createGenericIntSystemParameter(final String parameterName, final boolean deprecated,
            final boolean preferredParameter) {
        return createGenericSystemParameter(parameterName, deprecated, preferredParameter, Integer.class);
    }

    private StaticListParameter createContentLinkingParameter() {

        final StaticListParameter parameter = new StaticListParameter(SYS_PARAM_CONTENT_LINK, true, false,
                String[].class);
        parameter.setMandatory(false);
        parameter.setHidden(true);
        parameter.setRole(ParameterAttributeNames.Core.ROLE_SYSTEM_PARAMETER);
        parameter.setParameterAttribute(ParameterAttributeNames.Core.NAMESPACE,
                ParameterAttributeNames.Core.PREFERRED, "false");
        parameter.setParameterAttribute(ParameterAttributeNames.Core.NAMESPACE,
                ParameterAttributeNames.Core.PARAMETER_GROUP, GROUP_SYSTEM);
        parameter.setParameterAttribute(ParameterAttributeNames.Core.NAMESPACE,
                ParameterAttributeNames.Core.PARAMETER_GROUP_LABEL,
                Messages.getInstance().getString("ReportPlugin.SystemParameters"));
        parameter.setParameterAttribute(ParameterAttributeNames.Core.NAMESPACE, ParameterAttributeNames.Core.LABEL,
                Messages.getInstance().getString("ReportPlugin.ContentLinking"));
        parameter.setParameterAttribute(ParameterAttributeNames.Core.NAMESPACE, ParameterAttributeNames.Core.TYPE,
                ParameterAttributeNames.Core.TYPE_LIST);
        return parameter;
    }

    private Object lookupDestination() {
        return inputs.get(SYS_PARAM_DESTINATION);
    }

    private StaticListParameter createOutputParameter() {

        final StaticListParameter listParameter = new StaticListParameter(SYS_PARAM_OUTPUT_TARGET, false, true,
                String.class);
        listParameter.setParameterAttribute(ParameterAttributeNames.Core.NAMESPACE,
                ParameterAttributeNames.Core.PREFERRED, String.valueOf(true));
        listParameter.setParameterAttribute(ParameterAttributeNames.Core.NAMESPACE,
                ParameterAttributeNames.Core.PARAMETER_GROUP, GROUP_PARAMETERS);
        listParameter.setParameterAttribute(ParameterAttributeNames.Core.NAMESPACE,
                ParameterAttributeNames.Core.PARAMETER_GROUP_LABEL,
                Messages.getInstance().getString("ReportPlugin.ReportParameters"));
        listParameter.setParameterAttribute(ParameterAttributeNames.Core.NAMESPACE,
                ParameterAttributeNames.Core.LABEL, Messages.getInstance().getString("ReportPlugin.OutputType"));
        listParameter.setParameterAttribute(ParameterAttributeNames.Core.NAMESPACE,
                ParameterAttributeNames.Core.TYPE, ParameterAttributeNames.Core.TYPE_DROPDOWN);
        listParameter.setRole(ParameterAttributeNames.Core.ROLE_SYSTEM_PARAMETER);
        listParameter.addValues(HtmlTableModule.TABLE_HTML_PAGE_EXPORT_TYPE,
                Messages.getInstance().getString("ReportPlugin.outputHTMLPaginated"));
        listParameter.addValues(HtmlTableModule.TABLE_HTML_STREAM_EXPORT_TYPE,
                Messages.getInstance().getString("ReportPlugin.outputHTMLStream"));
        listParameter.addValues(PdfPageableModule.PDF_EXPORT_TYPE,
                Messages.getInstance().getString("ReportPlugin.outputPDF"));
        listParameter.addValues(ExcelTableModule.EXCEL_FLOW_EXPORT_TYPE,
                Messages.getInstance().getString("ReportPlugin.outputXLS"));
        listParameter.addValues(ExcelTableModule.XLSX_FLOW_EXPORT_TYPE,
                Messages.getInstance().getString("ReportPlugin.outputXLSX"));
        listParameter.addValues(CSVTableModule.TABLE_CSV_STREAM_EXPORT_TYPE,
                Messages.getInstance().getString("ReportPlugin.outputCSV"));
        listParameter.addValues(RTFTableModule.TABLE_RTF_FLOW_EXPORT_TYPE,
                Messages.getInstance().getString("ReportPlugin.outputRTF"));
        listParameter.addValues(PlainTextPageableModule.PLAINTEXT_EXPORT_TYPE,
                Messages.getInstance().getString("ReportPlugin.outputTXT"));
        return listParameter;
    }

    private ParameterDefinitionEntry createRenderModeSystemParameter() {
        final StaticListParameter listParameter = new StaticListParameter(SYS_PARAM_RENDER_MODE, false, true,
                String.class);
        listParameter.setHidden(true);
        listParameter.setParameterAttribute(ParameterAttributeNames.Core.NAMESPACE,
                ParameterAttributeNames.Core.PREFERRED, String.valueOf(false));
        listParameter.setParameterAttribute(ParameterAttributeNames.Core.NAMESPACE,
                ParameterAttributeNames.Core.PARAMETER_GROUP, GROUP_SYSTEM);
        listParameter.setParameterAttribute(ParameterAttributeNames.Core.NAMESPACE,
                ParameterAttributeNames.Core.PARAMETER_GROUP_LABEL,
                Messages.getInstance().getString("ReportPlugin.SystemParameters"));
        listParameter.setParameterAttribute(ParameterAttributeNames.Core.NAMESPACE,
                ParameterAttributeNames.Core.LABEL, SYS_PARAM_RENDER_MODE);
        listParameter.setParameterAttribute(ParameterAttributeNames.Core.NAMESPACE,
                ParameterAttributeNames.Core.TYPE, ParameterAttributeNames.Core.TYPE_DROPDOWN);
        listParameter.setRole(ParameterAttributeNames.Core.ROLE_SYSTEM_PARAMETER);
        listParameter.addValues("XML", "XML"); // NON-NLS
        listParameter.addValues("REPORT", "REPORT"); // NON-NLS
        listParameter.addValues("DOWNLOAD", "DOWNLOAD"); // NON-NLS
        listParameter.addValues("PARAMETER", "PARAMETER"); // NON-NLS
        return listParameter;
    }

    private Boolean requestFlag(final String parameter, final MasterReport report, final String attributeNamespace,
            final String attributeName, final String configurationKey) {
        if (parameter != null) {
            final IParameterProvider parameters = getRequestParameters();
            final String parameterValue = parameters.getStringParameter(parameter, "");
            if ("true".equals(parameterValue)) {
                return Boolean.TRUE;
            }
            if ("false".equals(parameterValue)) {
                return Boolean.FALSE;
            }
        }

        if (attributeNamespace != null && attributeName != null) {
            final Object attr = report.getAttribute(attributeNamespace, attributeName);
            if (Boolean.TRUE.equals(attr) || "true".equals(attr)) {
                return Boolean.TRUE;
            }
            if (Boolean.FALSE.equals(attr) || "false".equals(attr)) {
                return Boolean.FALSE;
            }
        }

        if (configurationKey != null) {
            final String configProperty = report.getConfiguration().getConfigProperty(configurationKey);
            if ("true".equals(configProperty)) {
                return Boolean.TRUE;
            }
            if ("false".equals(configProperty)) {
                return Boolean.FALSE;
            }
        }
        return null;
    }

    private String requestConfiguration(final String parameter, final MasterReport report,
            final String attributeNamespace, final String attributeName, final String configurationKey) {
        if (parameter != null) {
            final IParameterProvider parameters = getRequestParameters();
            final String parameterValue = parameters.getStringParameter(parameter, "");
            if (StringUtils.isEmpty(parameterValue) == false) {
                return parameterValue;
            }
        }

        if (attributeNamespace != null && attributeName != null) {
            final Object attr = report.getAttribute(attributeNamespace, attributeName);
            if (attr != null && StringUtils.isEmpty(String.valueOf(attr)) == false) {
                return String.valueOf(attr);
            }
        }

        if (configurationKey != null) {
            final String configProperty = report.getConfiguration().getConfigProperty(configurationKey);
            if (StringUtils.isEmpty(configProperty) == false) {
                return configProperty;
            }
        }
        return null;
    }
}