/*
* 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();
}
}