org.saiku.adhoc.service.report.SaikuAdhocPreProcessor.java Source code

Java tutorial

Introduction

Here is the source code for org.saiku.adhoc.service.report.SaikuAdhocPreProcessor.java

Source

/*
 * Copyright (C) 2011 Marius Giepz
 *
 * This program 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 2 of the License, or (at your option)
 * any later version.
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */

package org.saiku.adhoc.service.report;

import java.awt.Color;
import java.awt.Image;
import java.sql.Blob;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.engine.classic.core.AbstractReportDefinition;
import org.pentaho.reporting.engine.classic.core.AttributeNames;
import org.pentaho.reporting.engine.classic.core.Band;
import org.pentaho.reporting.engine.classic.core.Element;
import org.pentaho.reporting.engine.classic.core.Group;
import org.pentaho.reporting.engine.classic.core.GroupBody;
import org.pentaho.reporting.engine.classic.core.GroupFooter;
import org.pentaho.reporting.engine.classic.core.GroupHeader;
import org.pentaho.reporting.engine.classic.core.MasterReport;
import org.pentaho.reporting.engine.classic.core.MetaAttributeNames;
import org.pentaho.reporting.engine.classic.core.RelationalGroup;
import org.pentaho.reporting.engine.classic.core.ReportElement;
import org.pentaho.reporting.engine.classic.core.ReportFooter;
import org.pentaho.reporting.engine.classic.core.ReportPreProcessor;
import org.pentaho.reporting.engine.classic.core.ReportProcessingException;
import org.pentaho.reporting.engine.classic.core.Section;
import org.pentaho.reporting.engine.classic.core.SubGroupBody;
import org.pentaho.reporting.engine.classic.core.SubReport;
import org.pentaho.reporting.engine.classic.core.filter.types.ContentFieldType;
import org.pentaho.reporting.engine.classic.core.filter.types.DateFieldType;
import org.pentaho.reporting.engine.classic.core.filter.types.LabelType;
import org.pentaho.reporting.engine.classic.core.filter.types.MessageType;
import org.pentaho.reporting.engine.classic.core.filter.types.NumberFieldType;
import org.pentaho.reporting.engine.classic.core.filter.types.TextFieldType;
import org.pentaho.reporting.engine.classic.core.function.ProcessingContext;
import org.pentaho.reporting.engine.classic.core.function.StructureFunction;
import org.pentaho.reporting.engine.classic.core.metadata.ElementType;
import org.pentaho.reporting.engine.classic.core.states.datarow.DefaultFlowController;
import org.pentaho.reporting.engine.classic.core.style.BandStyleKeys;
import org.pentaho.reporting.engine.classic.core.style.BorderStyle;
import org.pentaho.reporting.engine.classic.core.style.ElementStyleKeys;
import org.pentaho.reporting.engine.classic.core.style.ElementStyleSheet;
import org.pentaho.reporting.engine.classic.core.wizard.AutoGeneratorUtility;
import org.pentaho.reporting.engine.classic.core.wizard.DataAttributes;
import org.pentaho.reporting.engine.classic.core.wizard.DefaultDataAttributeContext;
import org.pentaho.reporting.engine.classic.core.wizard.RelationalAutoGeneratorPreProcessor;
import org.pentaho.reporting.engine.classic.wizard.WizardOverrideFormattingFunction;
import org.pentaho.reporting.engine.classic.wizard.WizardProcessorUtil;
import org.pentaho.reporting.engine.classic.wizard.model.DetailFieldDefinition;
import org.pentaho.reporting.engine.classic.wizard.model.FieldDefinition;
import org.pentaho.reporting.engine.classic.wizard.model.GroupDefinition;
import org.pentaho.reporting.engine.classic.wizard.model.GroupType;
import org.pentaho.reporting.engine.classic.wizard.model.Length;
import org.pentaho.reporting.engine.classic.wizard.model.RootBandDefinition;
import org.pentaho.reporting.engine.classic.wizard.model.WizardSpecification;
import org.pentaho.reporting.libraries.base.util.StringUtils;
import org.pentaho.reporting.libraries.resourceloader.ResourceManager;
import org.saiku.adhoc.model.master.SaikuColumn;
import org.saiku.adhoc.model.master.SaikuGroup;
import org.saiku.adhoc.model.master.SaikuMasterModel;
import org.saiku.adhoc.service.report.tasks.SaikuUpdateDetailsHeaderTask;
import org.saiku.adhoc.service.report.tasks.SaikuUpdateDetailsTask;
import org.saiku.adhoc.service.report.tasks.SaikuUpdateGroupFooterTask;
import org.saiku.adhoc.service.report.tasks.SaikuUpdateGroupHeaderTask;
import org.saiku.adhoc.service.report.tasks.SaikuUpdateMessagesTask;
import org.saiku.adhoc.service.report.tasks.SaikuUpdateReportFooterTask;
import org.saiku.adhoc.service.report.tasks.SaikuUpdateReportHeaderTask;
import org.saiku.adhoc.service.report.tasks.UpdateTask;

public class SaikuAdhocPreProcessor implements ReportPreProcessor {

    private static final long serialVersionUID = 6383038273801168593L;

    private static final int MIN_WIDTH = 1;

    private Log log = LogFactory.getLog(SaikuAdhocPreProcessor.class);

    private String RPT_HEADER_MSG = "rpt-rhd-";
    private String PAGE_HEADER_MSG = "rpt-phd-";
    private String RPT_SUMMARY_MSG = "rpt-sum-";
    private String RPT_FOOTER_MSG = "rpt-rft-";
    private String PAGE_FOOTER_MSG = "rpt-pft-";

    private SaikuMasterModel model;
    private AbstractReportDefinition definition;
    private DefaultFlowController flowController;
    private WizardSpecification wizardSpecification;

    private DefaultDataAttributeContext attributeContext;

    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public void setSaikuMasterModel(SaikuMasterModel model) {
        this.model = model;

    }

    @Override
    public MasterReport performPreProcessing(final MasterReport definition,
            final DefaultFlowController flowController) throws ReportProcessingException {

        try {
            return (MasterReport) performCommonPreProcessing(definition, flowController,
                    definition.getResourceManager());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

        return definition;

    }

    @Override
    public SubReport performPreProcessing(SubReport paramSubReport,
            DefaultFlowController paramDefaultFlowController) throws ReportProcessingException {
        // TODO Auto-generated method stub
        return null;
    }

    private AbstractReportDefinition performCommonPreProcessing(AbstractReportDefinition definition,
            DefaultFlowController flowController, ResourceManager resourceManager)
            throws ReportProcessingException, CloneNotSupportedException {

        try {
            this.wizardSpecification = WizardProcessorUtil.loadWizardSpecification(definition, resourceManager);
            if (wizardSpecification == null) {
                return definition;
            }

            final StructureFunction[] functions = definition.getStructureFunctions();

            boolean hasOverrideFunction = false;
            for (int i = 0; i < functions.length; i++) {
                final StructureFunction function = functions[i];
                if (function instanceof WizardOverrideFormattingFunction) {
                    hasOverrideFunction = true;
                    break;
                }
            }
            if (hasOverrideFunction == false) {
                definition.addStructureFunction(new WizardOverrideFormattingFunction());
            }

            final ProcessingContext reportContext = flowController.getReportContext();
            this.definition = definition;
            this.flowController = flowController;
            this.attributeContext = new DefaultDataAttributeContext(reportContext.getOutputProcessorMetaData(),
                    reportContext.getResourceBundleFactory().getLocale());

            /*
             * Here we process every single band
             */
            setupReportHeader();
            setupWizardRelationalGroups();
            setupWizardDetails();
            setupReportFooter();
            setupPageHeader();
            setupPageFooter();

            return definition;

        } finally {
            this.wizardSpecification = null;
            this.definition = null;
            this.flowController = null;
            this.attributeContext = null;
        }

    }

    private void setupPageFooter() {
        final Section pageFooter = definition.getPageFooter();
        if (pageFooter == null)
            return;

        iterateSection(pageFooter,
                new SaikuUpdateMessagesTask(model.getPageFooterElements(), PAGE_FOOTER_MSG, model));

    }

    private void setupPageHeader() {
        final Section pageHeader = definition.getPageHeader();
        if (pageHeader == null)
            return;

        iterateSection(pageHeader,
                new SaikuUpdateMessagesTask(model.getPageHeaderElements(), PAGE_HEADER_MSG, model));

    }

    private void setupReportFooter() {

        final ReportFooter footer = definition.getReportFooter();

        /*
         * The report footer consists of two parts that need to be processed differently
         * - The summary band
         * - The message labels outside of the summary band
         */

        final Band itemBand = AutoGeneratorUtility.findGeneratedContent(footer);
        itemBand.getStyle().setStyleProperty(BandStyleKeys.LAYOUT, "row");

        final DetailFieldDefinition[] detailFieldDefinitions = wizardSpecification.getDetailFieldDefinitions();

        final float[] computedWidth = correctFieldWidths(detailFieldDefinitions);

        for (int i = 0; i < detailFieldDefinitions.length; i++) {
            final DetailFieldDefinition field = detailFieldDefinitions[i];

            final Class aggFunctionClass = field.getAggregationFunction();
            // If an aggregation is set we assume that the user wants the
            // summary to be shown
            Element footerElement = null;

            if (aggFunctionClass != null) {
                footerElement = AutoGeneratorUtility.generateFooterElement(aggFunctionClass,
                        computeElementType(field), null, field.getField());
            }

            // otherwise we show a messagelabel where the user can enter
            // additional info
            else {
                footerElement = new Element();
                footerElement.setElementType(new MessageType());
            }

            setupDefaultGrid(footer, footerElement);

            footerElement.getStyle().setStyleProperty(ElementStyleKeys.MIN_WIDTH, new Float(computedWidth[i]));
            if (Boolean.TRUE.equals(footerElement.getAttribute(AttributeNames.Wizard.NAMESPACE,
                    AttributeNames.Wizard.ALLOW_METADATA_STYLING))) {
                footerElement.setAttribute(AttributeNames.Wizard.NAMESPACE, "CachedWizardFormatData", field);
            }
            if (Boolean.TRUE.equals(footerElement.getAttribute(AttributeNames.Wizard.NAMESPACE,
                    AttributeNames.Wizard.ALLOW_METADATA_ATTRIBUTES))) {
                footerElement.setAttribute(AttributeNames.Wizard.NAMESPACE, "CachedWizardFieldData", field);
            }

            itemBand.addElement(footerElement);

            iterateSection(itemBand,
                    new SaikuUpdateMessagesTask(model.getReportSummaryElements(), RPT_SUMMARY_MSG, model));

        }

        //This is the whole report footer. we just need to update everything except the item band
        //We need to filter out by parent-has-generated-content-marker

        iterateSection(footer,
                new SaikuUpdateReportFooterTask(model.getReportFooterElements(), RPT_FOOTER_MSG, model));

    }

    private void setupReportHeader() {
        final Section reportHeader = definition.getReportHeader();
        if (reportHeader == null)
            return;

        //In the report header we just need to tag every element
        iterateSection(reportHeader,
                new SaikuUpdateReportHeaderTask(model.getReportHeaderElements(), RPT_HEADER_MSG));
    }

    private void setupWizardRelationalGroups() throws ReportProcessingException, CloneNotSupportedException {

        final Group rootgroup = definition.getRootGroup();
        RelationalGroup group;
        if (rootgroup instanceof RelationalGroup == false) {
            group = null;
        } else {
            group = (RelationalGroup) rootgroup;
        }

        final RelationalGroup template = findInnermostRelationalGroup(definition);

        final List<SaikuGroup> saikuGroups = model.getGroups();

        final GroupDefinition[] groupDefinitions = wizardSpecification.getGroupDefinitions();
        for (int i = 0; i < groupDefinitions.length; i++) {
            final GroupDefinition groupDefinition = groupDefinitions[i];
            final GroupType type = groupDefinition.getGroupType();
            if (type != null && GroupType.RELATIONAL.equals(type) == false) {
                continue;
            }

            if (group == null) {
                // create a new group and insert it at the end
                final RelationalGroup relationalGroup;
                if (template != null) {
                    relationalGroup = (RelationalGroup) template.derive();
                } else {
                    relationalGroup = new RelationalGroup();
                }

                if (groupDefinition.getGroupName() != null) {
                    relationalGroup.setName(groupDefinition.getGroupName());
                }
                configureRelationalGroup(relationalGroup, groupDefinition, i);
                insertGroup(relationalGroup);

                SaikuGroup saikuGroup = saikuGroups.get(i);
                iterateSection(relationalGroup.getHeader(), new SaikuUpdateGroupHeaderTask(model, saikuGroup, i));

            } else {
                // modify the existing group
                configureRelationalGroup(group, groupDefinition, i);

                SaikuGroup saikuGroup = saikuGroups.get(i);
                iterateSection(group.getHeader(), new SaikuUpdateGroupHeaderTask(model, saikuGroup, i));
                iterateSection(group.getFooter(),
                        new SaikuUpdateGroupFooterTask(saikuGroup.getGroupFooterElements(), model, i));

                final GroupBody body = group.getBody();
                if (body instanceof SubGroupBody) {
                    final SubGroupBody sgBody = (SubGroupBody) body;
                    if (sgBody.getGroup() instanceof RelationalGroup) {
                        group = (RelationalGroup) sgBody.getGroup();

                    } else {
                        group = null;
                    }
                } else {
                    group = null;
                }
            }

            //do the groupfooter stuff
            //TODO:
            //iterateSection(group, new SaikuUpdateGroupFooterTask(saikuGroup.getGroupFooterMessages(), GRP_FOOTER_MSG + i
            //      + "-", model));

        }
        // Remove any group bands are not being used ie. groups with no fields
        removedUnusedTemplateGroups(groupDefinitions.length);

    }

    protected void setupWizardDetails() throws ReportProcessingException {
        final DetailFieldDefinition[] detailFieldDefinitions = wizardSpecification.getDetailFieldDefinitions();
        if (detailFieldDefinitions.length == 0) {
            if (wizardSpecification.isAutoGenerateDetails()) {
                final RelationalAutoGeneratorPreProcessor generatorPreProcessor = new RelationalAutoGeneratorPreProcessor();
                if (definition instanceof MasterReport) {
                    generatorPreProcessor.performPreProcessing((MasterReport) definition, flowController);
                } else if (definition instanceof SubReport) {
                    generatorPreProcessor.performPreProcessing((SubReport) definition, flowController);
                }
            }
            return;
        }

        definition.getDetailsHeader().setRepeat(true);
        definition.getDetailsFooter().setRepeat(true);

        final Band detailsHeader = AutoGeneratorUtility.findGeneratedContent(definition.getDetailsHeader());
        final Band detailsFooter = AutoGeneratorUtility.findGeneratedContent(definition.getDetailsFooter());
        final Band itemBand = AutoGeneratorUtility.findGeneratedContent(definition.getItemBand());

        if (itemBand == null) {
            return;
        }

        final float[] computedWidth = correctFieldWidths(detailFieldDefinitions);

        itemBand.getStyle().setStyleProperty(BandStyleKeys.LAYOUT, "row");
        if (detailsHeader != null) {
            detailsHeader.getStyle().setStyleProperty(BandStyleKeys.LAYOUT, "row");
        }
        if (detailsFooter != null) {
            detailsFooter.getStyle().setStyleProperty(BandStyleKeys.LAYOUT, "row");
        }

        /*
         * TODO: This kinda breaks the concept of the PreProcessor
         */
        List<SaikuColumn> columns = model.getColumns();

        log.debug("Column Width");

        for (int i = 0; i < detailFieldDefinitions.length; i++) {
            final DetailFieldDefinition detailFieldDefinition = detailFieldDefinitions[i];
            //
            //columns.get(i).getColumnHeaderFormat().setWidth(computedWidth[i]);
            //
            //log.debug("WIZARD: " + detailFieldDefinition.getWidth().getValue() + " Calculated " + computedWidth[i]);
            setupField(detailsHeader, detailsFooter, itemBand, detailFieldDefinition, computedWidth[i], i);
        }

        if (detailsFooter != null) {
            final Element[] elements = detailsFooter.getElementArray();
            boolean footerEmpty = true;
            for (int i = 0; i < elements.length; i++) {
                final Element element = elements[i];
                if ("label".equals(element.getElementTypeName()) == false) {
                    footerEmpty = false;
                    break;
                }
            }
            if (footerEmpty) {
                detailsFooter.clear();
            }
        }

        //Saiku Specific stuff
        iterateSection(detailsHeader, new SaikuUpdateDetailsHeaderTask(model));
        iterateSection(itemBand, new SaikuUpdateDetailsTask(model));

    }

    /**
     * We iterate through all Elements of a section. A section is a group-band
     * or a header...
     * 
     * @param s
     * @param task
     */
    private void iterateSection(final Section s, final UpdateTask task) {
        final int count = s.getElementCount();
        for (int i = 0; i < count; i++) {
            final ReportElement element = s.getElement(i);
            task.processElement(element, i);
            if (element instanceof SubReport) {
                continue;
            }
            if (element instanceof Section) {
                iterateSection((Section) element, task);
            }
        }
    }

    /*
     * UTIL STUFF
     * 
     * 
     */

    protected void setupField(final Band detailsHeader, final Band detailsFooter, final Band itemBand,
            final DetailFieldDefinition field, final float width, final int fieldIdx)
            throws ReportProcessingException {
        if (StringUtils.isEmpty(field.getField())) {
            return;
        }

        final Element detailElement = AutoGeneratorUtility.generateDetailsElement(field.getField(),
                computeElementType(field));
        setupDefaultGrid(itemBand, detailElement);

        final String id = "wizard::details-" + field.getField();
        detailElement.setName(id);
        detailElement.getStyle().setStyleProperty(ElementStyleKeys.MIN_WIDTH, new Float(width));

        //      detailElement.setAttribute(AttributeNames.Wizard.NAMESPACE, AttributeNames.Wizard.ALLOW_METADATA_STYLING,
        //            false);
        //      detailElement.setAttribute(AttributeNames.Wizard.NAMESPACE, AttributeNames.Wizard.ALLOW_METADATA_ATTRIBUTES,
        //            false);
        //
        //      

        if (Boolean.TRUE.equals(detailElement.getAttribute(AttributeNames.Wizard.NAMESPACE,
                AttributeNames.Wizard.ALLOW_METADATA_STYLING))) {
            detailElement.setAttribute("http://reporting.pentaho.org/namespaces/engine/attributes/wizard",
                    "CachedWizardFormatData", field);
            detailElement.setAttribute(AttributeNames.Wizard.NAMESPACE, "CachedWizardFormatData", field);
        }
        if (Boolean.TRUE.equals(detailElement.getAttribute(AttributeNames.Wizard.NAMESPACE,
                AttributeNames.Wizard.ALLOW_METADATA_ATTRIBUTES))) {
            detailElement.setAttribute(AttributeNames.Wizard.NAMESPACE, "CachedWizardFieldData", field);
        }
        itemBand.addElement(detailElement);

        if (Boolean.TRUE.equals(field.getOnlyShowChangingValues())) {
            detailElement.setAttribute(AttributeNames.Wizard.NAMESPACE,
                    AttributeNames.Wizard.ONLY_SHOW_CHANGING_VALUES, Boolean.TRUE);
        }

        if (detailsHeader != null) {
            final Element headerElement = AutoGeneratorUtility.generateHeaderElement(field.getField());
            setupDefaultGrid(detailsHeader, headerElement);
            headerElement.getStyle().setStyleProperty(ElementStyleKeys.MIN_WIDTH, new Float(width));

            if (Boolean.TRUE.equals(headerElement.getAttribute(AttributeNames.Wizard.NAMESPACE,
                    AttributeNames.Wizard.ALLOW_METADATA_ATTRIBUTES))) {
                headerElement.setAttribute(AttributeNames.Wizard.NAMESPACE, "CachedWizardFieldData", field);
            }

            headerElement.setAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.VALUE,
                    field.getDisplayName());

            headerElement.setAttribute(AttributeNames.Wizard.NAMESPACE,
                    AttributeNames.Wizard.ALLOW_METADATA_STYLING, false);
            headerElement.setAttribute(AttributeNames.Wizard.NAMESPACE,
                    AttributeNames.Wizard.ALLOW_METADATA_ATTRIBUTES, false);

            detailsHeader.addElement(headerElement);
        }

        if (detailsFooter != null) {
            final Class aggFunctionClass = field.getAggregationFunction();
            final Element footerElement = AutoGeneratorUtility.generateFooterElement(aggFunctionClass,
                    computeElementType(field), null, field.getField());

            setupDefaultGrid(detailsFooter, footerElement);

            footerElement.getStyle().setStyleProperty(ElementStyleKeys.MIN_WIDTH, new Float(width));
            if (Boolean.TRUE.equals(footerElement.getAttribute(AttributeNames.Wizard.NAMESPACE,
                    AttributeNames.Wizard.ALLOW_METADATA_STYLING))) {
                footerElement.setAttribute(AttributeNames.Wizard.NAMESPACE, "CachedWizardFormatData", field);
            }
            if (Boolean.TRUE.equals(footerElement.getAttribute(AttributeNames.Wizard.NAMESPACE,
                    AttributeNames.Wizard.ALLOW_METADATA_ATTRIBUTES))) {
                footerElement.setAttribute(AttributeNames.Wizard.NAMESPACE, "CachedWizardFieldData", field);
            }

            // details footer looks stupid!
            // detailsFooter.addElement(footerElement);
        }
    }

    private RelationalGroup findInnermostRelationalGroup(final AbstractReportDefinition definition) {
        RelationalGroup retval = null;
        Group existingGroup = definition.getRootGroup();
        while (existingGroup instanceof RelationalGroup) {
            retval = (RelationalGroup) existingGroup;
            final GroupBody body = existingGroup.getBody();
            if (body instanceof SubGroupBody == false) {
                return retval;
            }
            final SubGroupBody sgb = (SubGroupBody) body;
            existingGroup = sgb.getGroup();
        }

        return retval;
    }

    /**
     * Removes the unusedTemplateGroups based on the assumption that if a group
     * doesn't have any fields assigned to it that it is empty.
     */
    private void removedUnusedTemplateGroups(final int groupsDefined) {
        final RelationalGroup[] templateRelationalGroups = getTemplateRelationalGroups();
        final int templateRelationalGroupCount = templateRelationalGroups.length;
        for (int i = groupsDefined; i < templateRelationalGroupCount; i++) {
            final RelationalGroup templateRelationalGroup = templateRelationalGroups[i];
            definition.removeGroup(templateRelationalGroup);
        }
    }

    private void insertGroup(final RelationalGroup group) {
        Group lastGroup = null;
        Group insertGroup = definition.getRootGroup();
        while (true) {
            if (insertGroup instanceof RelationalGroup == false) {
                if (lastGroup == null) {
                    definition.setRootGroup(group);
                    group.setBody(new SubGroupBody(insertGroup));
                    return;
                }

                final GroupBody body = lastGroup.getBody();
                final SubGroupBody sgb = new SubGroupBody(group);
                lastGroup.setBody(sgb);
                group.setBody(body);
                return;
            }

            final GroupBody body = insertGroup.getBody();
            if (body instanceof SubGroupBody == false) {
                final SubGroupBody sgb = new SubGroupBody(group);
                insertGroup.setBody(sgb);
                group.setBody(body);
                return;
            }

            lastGroup = insertGroup;
            final SubGroupBody sgb = (SubGroupBody) body;
            insertGroup = sgb.getGroup();
        }
    }

    protected void configureRelationalGroup(final RelationalGroup group, final GroupDefinition groupDefinition,
            final int index) throws ReportProcessingException {
        final String groupField = groupDefinition.getField();
        if (groupField != null) {
            group.setFieldsArray(new String[] { groupField });
        }

        configureRelationalGroupFooter(group, groupDefinition, index);
        configureRelationalGroupHeader(group, groupDefinition, index);
    }

    protected void configureRelationalGroupHeader(final Group group, final GroupDefinition groupDefinition,
            final int index) {
        final RootBandDefinition headerDefinition = groupDefinition.getHeader();
        if (headerDefinition.isVisible()) {
            final GroupHeader header = group.getHeader();
            final Boolean repeat = headerDefinition.getRepeat();
            if (repeat != null) {
                header.setRepeat(repeat.booleanValue());
            }

            final Band content = AutoGeneratorUtility.findGeneratedContent(header);
            if (content == null) {
                return;
            }

            final Element headerLabelElement = new Element();
            headerLabelElement.setElementType(new LabelType());
            if (groupDefinition.getDisplayName() != null) {
                headerLabelElement.setAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.VALUE,
                        groupDefinition.getDisplayName());
            } else {
                headerLabelElement.setAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.VALUE,
                        groupDefinition.getField());
                headerLabelElement.setAttribute(AttributeNames.Wizard.NAMESPACE, AttributeNames.Wizard.LABEL_FOR,
                        groupDefinition.getField());
                headerLabelElement.setAttribute(AttributeNames.Wizard.NAMESPACE,
                        AttributeNames.Wizard.ALLOW_METADATA_ATTRIBUTES, Boolean.TRUE);
            }

            final Element headerValueElement = AutoGeneratorUtility
                    .generateDetailsElement(groupDefinition.getField(), computeElementType(groupDefinition));
            headerValueElement.setAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.NULL_VALUE, "-");

            final Band headerElement = new Band();
            headerElement.getStyle().setStyleProperty(BandStyleKeys.LAYOUT, BandStyleKeys.LAYOUT_INLINE);
            headerElement.getStyle().setStyleProperty(ElementStyleKeys.MIN_WIDTH, new Float(-100));
            headerElement.getStyle().setStyleProperty(ElementStyleKeys.DYNAMIC_HEIGHT, Boolean.TRUE);
            headerElement.setAttribute(AttributeNames.Wizard.NAMESPACE,
                    AttributeNames.Wizard.ALLOW_METADATA_STYLING, Boolean.TRUE);
            headerElement.setAttribute(AttributeNames.Wizard.NAMESPACE, AttributeNames.Wizard.LABEL_FOR,
                    groupDefinition.getField());
            headerElement.setAttribute(AttributeNames.Wizard.NAMESPACE, "CachedWizardFormatData", headerDefinition);
            // headerElement.addElement(headerLabelElement);
            // headerElement.addElement(headerValueElement);

            headerElement.setAttribute(AttributeNames.Wizard.NAMESPACE,
                    AttributeNames.Wizard.ALLOW_METADATA_STYLING, Boolean.FALSE);
            headerElement.setAttribute(AttributeNames.Wizard.NAMESPACE,
                    AttributeNames.Wizard.ALLOW_METADATA_ATTRIBUTES, Boolean.FALSE);

            final Element headerMessageElement = new Element();
            headerMessageElement.setElementType(new MessageType());
            headerMessageElement.setAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.FIELD,
                    groupDefinition.getField());
            headerMessageElement.setAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.NAME,
                    groupDefinition.getDisplayName());

            headerMessageElement.setAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.VALUE,
                    groupDefinition.getDisplayName() + ": $(" + groupDefinition.getField() + ")");

            // there can be only one!
            headerElement.addElement(headerMessageElement);

            content.clear();
            content.addElement(headerElement);
        }
    }

    protected void configureRelationalGroupFooter(final Group group, final GroupDefinition groupDefinition,
            final int index) throws ReportProcessingException {

        final RootBandDefinition footerDefinition = groupDefinition.getFooter();
        final DetailFieldDefinition[] detailFieldDefinitions = wizardSpecification.getDetailFieldDefinitions();

        if (footerDefinition.isVisible() == false) {
            return;
        }

        final GroupFooter footer = group.getFooter();
        final Boolean repeat = footerDefinition.getRepeat();
        if (repeat != null) {
            footer.setRepeat(repeat.booleanValue());
        }

        final Band itemBand = AutoGeneratorUtility.findGeneratedContent(footer);
        itemBand.getStyle().setStyleProperty(BandStyleKeys.LAYOUT, "row");

        final float[] computedWidth = correctFieldWidths(detailFieldDefinitions);

        for (int i = 0; i < detailFieldDefinitions.length; i++) {
            final DetailFieldDefinition detailFieldDefinition = detailFieldDefinitions[i];
            setupGroupsummaryField(itemBand, detailFieldDefinition, computedWidth[i], i);
        }

    }

    /**
     * @param detailFieldDefinitions
     * @return
     */
    private float[] correctFieldWidths(final DetailFieldDefinition[] detailFieldDefinitions) {
        final Float[] widthSpecs = new Float[detailFieldDefinitions.length];

        Float userDefinedWidths = Float.valueOf(0);
        int numberOfUnsetWidths = 0;
        for (int i = 0; i < detailFieldDefinitions.length; i++) {
            final DetailFieldDefinition fieldDefinition = detailFieldDefinitions[i];
            final Length length = fieldDefinition.getWidth();
            if (length == null) {
                widthSpecs[i] = null;
                numberOfUnsetWidths++;
                continue;
            }
            widthSpecs[i] = length.getNormalizedValue();
            userDefinedWidths += widthSpecs[i];
        }

        if (userDefinedWidths - (numberOfUnsetWidths * MIN_WIDTH) < -100) {
            Float diff = -100 - (userDefinedWidths - (numberOfUnsetWidths * MIN_WIDTH));
            for (int i = detailFieldDefinitions.length - 1; i > 0; i--) {
                if (!(widthSpecs[i] == null)) {
                    widthSpecs[i] += diff;
                }
            }
        }

        final float[] computedWidth = computeFieldWidths(widthSpecs, definition.getPageDefinition().getWidth());

        //if summ is now < 100% we need to resize the last one again

        float total = 0;

        for (int i = 0; i < computedWidth.length; i++) {
            total += computedWidth[i];
            log.debug("width: " + computedWidth[i]);

        }
        log.debug(total);

        return computedWidth;
    }

    /**
     * Computes a set of field widths. The input-width definitions can be a mix of absolute and relative values; the
     * resulting widths are always relative values. If the input width is null or zero, it is assumed that the field wants
     * to have a generic width.
     *
     * @param fieldDescriptions
     * @param pageWidth
     * @return
     */
    public static float[] computeFieldWidths(final Float[] fieldDescriptions, final float pageWidth) {
        final float[] resultWidths = new float[fieldDescriptions.length];

        float definedWidth = 0;
        int definedNumberOfFields = 0;
        for (int i = 0; i < fieldDescriptions.length; i++) {
            final Number number = fieldDescriptions[i];
            if (number != null && number.floatValue() != 0) {
                if (number.floatValue() < 0) {
                    // a fixed value ..
                    resultWidths[i] = number.floatValue();
                    definedNumberOfFields += 1;
                    definedWidth += number.floatValue();
                } else {
                    final float absValue = number.floatValue();
                    final float relativeValue = -absValue * 100 / pageWidth;
                    resultWidths[i] = relativeValue;
                    definedNumberOfFields += 1;
                    definedWidth += relativeValue;
                }
            }
        }

        if (definedNumberOfFields == fieldDescriptions.length) {
            // we are done, all fields are defined.
            return resultWidths;
        }

        if (definedNumberOfFields == 0) {
            // the worst case, no element provides a weight ..
            // therefore all fields have the same proportional width.
            Arrays.fill(resultWidths, -(100 / fieldDescriptions.length));
            return resultWidths;
        }

        final float availableSpace = -100 - definedWidth;
        if (availableSpace > 0) {
            // all predefined fields already fill the complete page. There is no space left for the
            // extra columns.
            return resultWidths;
        }

        final float avgSpace = availableSpace / (fieldDescriptions.length - definedNumberOfFields);
        for (int i = 0; i < resultWidths.length; i++) {
            final float width = resultWidths[i];
            if (width == 0) {
                resultWidths[i] = avgSpace;
            }
        }
        return resultWidths;
    }

    protected void setupGroupsummaryField(final Band groupSummaryBand, final DetailFieldDefinition field,
            final float width, final int fieldIdx) throws ReportProcessingException {
        if (StringUtils.isEmpty(field.getField())) {
            return;
        }

        final Class aggFunctionClass = field.getAggregationFunction();

        // If an aggregation is set we assume that the user wants the summary to
        // be shown
        Element footerElement = null;

        if (aggFunctionClass != null) {
            footerElement = AutoGeneratorUtility.generateFooterElement(aggFunctionClass, computeElementType(field),
                    null, field.getField());
        }
        // otherwise we show a messagelabel where the user can enter additional
        // info
        else {
            footerElement = new Element();
            footerElement.setElementType(new MessageType());
        }

        setupDefaultGrid(groupSummaryBand, footerElement);

        footerElement.getStyle().setStyleProperty(ElementStyleKeys.MIN_WIDTH, new Float(width));
        if (Boolean.TRUE.equals(footerElement.getAttribute(AttributeNames.Wizard.NAMESPACE,
                AttributeNames.Wizard.ALLOW_METADATA_STYLING))) {
            footerElement.setAttribute(AttributeNames.Wizard.NAMESPACE, "CachedWizardFormatData", field);
        }
        if (Boolean.TRUE.equals(footerElement.getAttribute(AttributeNames.Wizard.NAMESPACE,
                AttributeNames.Wizard.ALLOW_METADATA_ATTRIBUTES))) {
            footerElement.setAttribute(AttributeNames.Wizard.NAMESPACE, "CachedWizardFieldData", field);
        }

        groupSummaryBand.addElement(footerElement);

    }

    protected void setupDefaultGrid(final Band band, final Element detailElement) {
        setupDefaultPadding(band, detailElement);
        final ElementStyleSheet styleSheet = detailElement.getStyle();
        // Always make the height of the detailElement dynamic to the band
        // According to thomas negative numbers equate to percentages
        styleSheet.setStyleProperty(ElementStyleKeys.MIN_HEIGHT, new Float(-100));

        final Object maybeBorderStyle = band.getAttribute(AttributeNames.Wizard.NAMESPACE,
                AttributeNames.Wizard.GRID_STYLE);
        final Object maybeBorderWidth = band.getAttribute(AttributeNames.Wizard.NAMESPACE,
                AttributeNames.Wizard.GRID_WIDTH);
        final Object maybeBorderColor = band.getAttribute(AttributeNames.Wizard.NAMESPACE,
                AttributeNames.Wizard.GRID_COLOR);

        if (maybeBorderColor instanceof Color == false || maybeBorderStyle instanceof BorderStyle == false
                || maybeBorderWidth instanceof Number == false) {
            return;
        }

        final BorderStyle style = (BorderStyle) maybeBorderStyle;
        final Color color = (Color) maybeBorderColor;
        final Number number = (Number) maybeBorderWidth;
        final Float width = new Float(number.floatValue());

        styleSheet.setStyleProperty(ElementStyleKeys.BORDER_TOP_WIDTH, width);
        styleSheet.setStyleProperty(ElementStyleKeys.BORDER_TOP_COLOR, color);
        styleSheet.setStyleProperty(ElementStyleKeys.BORDER_TOP_STYLE, style);

        styleSheet.setStyleProperty(ElementStyleKeys.BORDER_LEFT_WIDTH, width);
        styleSheet.setStyleProperty(ElementStyleKeys.BORDER_LEFT_COLOR, color);
        styleSheet.setStyleProperty(ElementStyleKeys.BORDER_LEFT_STYLE, style);

        styleSheet.setStyleProperty(ElementStyleKeys.BORDER_BOTTOM_WIDTH, width);
        styleSheet.setStyleProperty(ElementStyleKeys.BORDER_BOTTOM_COLOR, color);
        styleSheet.setStyleProperty(ElementStyleKeys.BORDER_BOTTOM_STYLE, style);

        styleSheet.setStyleProperty(ElementStyleKeys.BORDER_RIGHT_WIDTH, width);
        styleSheet.setStyleProperty(ElementStyleKeys.BORDER_RIGHT_COLOR, color);
        styleSheet.setStyleProperty(ElementStyleKeys.BORDER_RIGHT_STYLE, style);
    }

    protected void setupDefaultPadding(final Band band, final Element detailElement) {
        final Object maybePaddingTop = band.getAttribute(AttributeNames.Wizard.NAMESPACE,
                AttributeNames.Wizard.PADDING_TOP);
        final Object maybePaddingLeft = band.getAttribute(AttributeNames.Wizard.NAMESPACE,
                AttributeNames.Wizard.PADDING_LEFT);
        final Object maybePaddingBottom = band.getAttribute(AttributeNames.Wizard.NAMESPACE,
                AttributeNames.Wizard.PADDING_BOTTOM);
        final Object maybePaddingRight = band.getAttribute(AttributeNames.Wizard.NAMESPACE,
                AttributeNames.Wizard.PADDING_RIGHT);

        if (maybePaddingTop instanceof Number == false || maybePaddingLeft instanceof Number == false
                || maybePaddingBottom instanceof Number == false || maybePaddingRight instanceof Number == false) {
            return;
        }

        final Number paddingTop = (Number) maybePaddingTop;
        final Number paddingLeft = (Number) maybePaddingLeft;
        final Number paddingBottom = (Number) maybePaddingBottom;
        final Number paddingRight = (Number) maybePaddingRight;

        final ElementStyleSheet styleSheet = detailElement.getStyle();
        styleSheet.setStyleProperty(ElementStyleKeys.PADDING_TOP, new Float(paddingTop.floatValue()));
        styleSheet.setStyleProperty(ElementStyleKeys.PADDING_LEFT, new Float(paddingLeft.floatValue()));
        styleSheet.setStyleProperty(ElementStyleKeys.PADDING_BOTTOM, new Float(paddingBottom.floatValue()));
        styleSheet.setStyleProperty(ElementStyleKeys.PADDING_RIGHT, new Float(paddingRight.floatValue()));
    }

    /**
     * @return the relational groups in the templates in a flattened array.
     */
    private RelationalGroup[] getTemplateRelationalGroups() {
        final ArrayList<RelationalGroup> relationalGroups = new ArrayList<RelationalGroup>();
        Group group = definition.getRootGroup();
        while (group != null && group instanceof RelationalGroup) {
            relationalGroups.add((RelationalGroup) group);
            final GroupBody body = group.getBody();
            if (body instanceof SubGroupBody) {
                final SubGroupBody sgBody = (SubGroupBody) body;
                if (sgBody.getGroup() instanceof RelationalGroup) {
                    group = sgBody.getGroup();
                } else {
                    group = null;
                }
            } else {
                group = null;
            }
        }

        return relationalGroups.toArray(new RelationalGroup[relationalGroups.size()]);
    }

    protected ElementType computeElementType(final FieldDefinition fieldDefinition) {
        final String field = fieldDefinition.getField();
        final DataAttributes attributes = flowController.getDataSchema().getAttributes(field);
        if (attributes == null) {
            log.warn("Field '" + field + "' is declared in the wizard-specification, "
                    + "but not present in the data. Assuming defaults.");
            return new TextFieldType();
        }
        final Class fieldType = (Class) attributes.getMetaAttribute(MetaAttributeNames.Core.NAMESPACE,
                MetaAttributeNames.Core.TYPE, Class.class, attributeContext);
        if (fieldType == null) {
            return new TextFieldType();
        }

        if (Number.class.isAssignableFrom(fieldType)) {
            return new NumberFieldType();
        }
        if (Date.class.isAssignableFrom(fieldType)) {
            return new DateFieldType();
        }
        if (byte[].class.isAssignableFrom(fieldType) || Blob.class.isAssignableFrom(fieldType)
                || Image.class.isAssignableFrom(fieldType)) {
            return new ContentFieldType();
        }
        return new TextFieldType();
    }

}