/* * 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.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.metadata.model.Domain; import org.pentaho.metadata.model.LogicalModel; import org.pentaho.reporting.engine.classic.core.AbstractReportDefinition; 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.ReportPreProcessor; import org.pentaho.reporting.engine.classic.core.ReportProcessingException; import org.pentaho.reporting.engine.classic.core.cache.CachingDataFactory; 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.layout.output.DefaultProcessingContext; import org.pentaho.reporting.engine.classic.core.modules.output.pageable.pdf.PdfPageableModule; import org.pentaho.reporting.engine.classic.core.modules.output.table.html.HtmlTableModule; import org.pentaho.reporting.engine.classic.core.modules.parser.bundle.writer.BundleWriter; import org.pentaho.reporting.engine.classic.core.modules.parser.bundle.writer.BundleWriterException; import org.pentaho.reporting.engine.classic.core.parameters.DefaultListParameter; import org.pentaho.reporting.engine.classic.core.parameters.DefaultParameterDefinition; import org.pentaho.reporting.engine.classic.core.parameters.ParameterAttributeNames; import org.pentaho.reporting.engine.classic.core.parameters.ParameterDefinitionEntry; 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.states.StateUtilities; import org.pentaho.reporting.engine.classic.core.states.datarow.DefaultFlowController; import org.pentaho.reporting.engine.classic.core.util.ReportParameterValues; import org.pentaho.reporting.engine.classic.core.wizard.DataSchemaDefinition; import org.pentaho.reporting.engine.classic.extensions.datasources.cda.CdaDataFactory; import org.pentaho.reporting.engine.classic.wizard.WizardOverrideFormattingFunction; import org.pentaho.reporting.engine.classic.wizard.WizardProcessor; import org.pentaho.reporting.engine.classic.wizard.model.WizardSpecification; import org.pentaho.reporting.libraries.repository.ContentIOException; import org.pentaho.reporting.libraries.resourceloader.ResourceException; import org.pentaho.reporting.platform.plugin.SimpleReportingComponent; import org.saiku.adhoc.exceptions.ReportException; import org.saiku.adhoc.exceptions.SaikuAdhocException; import org.saiku.adhoc.messages.Messages; import org.saiku.adhoc.model.WorkspaceSessionHolder; import org.saiku.adhoc.model.builder.CdaBuilder; import org.saiku.adhoc.model.builder.WizardBuilder; import org.saiku.adhoc.model.dto.HtmlReport; import org.saiku.adhoc.model.master.ReportTemplate; import org.saiku.adhoc.model.master.SaikuMasterModel; import org.saiku.adhoc.model.master.SaikuParameter; import org.saiku.adhoc.providers.ICdaProvider; import org.saiku.adhoc.providers.IMetadataProvider; import org.saiku.adhoc.providers.IPrptProvider; import org.saiku.adhoc.service.repository.IRepositoryHelper; import org.saiku.adhoc.utils.ParamUtils; import org.saiku.adhoc.utils.TemplateUtils; import pt.webdetails.cda.settings.CdaSettings; public class ReportGeneratorService { public void setCdaProvider(ICdaProvider cdaProvider) { this.cdaProvider = cdaProvider; } protected WorkspaceSessionHolder sessionHolder; protected IRepositoryHelper repository; private ICdaProvider cdaProvider; private IPrptProvider prptProvider; private IMetadataProvider metadataProvider; public void setMetadataProvider(IMetadataProvider metadataProvider) { this.metadataProvider = metadataProvider; } public void setPrptProvider(IPrptProvider prptProvider) { this.prptProvider = prptProvider; } protected static final Log log = LogFactory .getLog(ReportGeneratorService.class); public void setSessionHolder(WorkspaceSessionHolder sessionHolder) { this.sessionHolder = sessionHolder; } public void setRepositoryHelper(IRepositoryHelper repository) { this.repository = repository; } /** * Renders the report for a given query to html * * @param sessionId * @param report * @param acceptedPage * @param template * @return * @throws Exception * @throws IOException * @throws ResourceException */ public void renderReportHtml(String sessionId, String templateName, HtmlReport report, Integer acceptedPage) throws Exception { SaikuMasterModel model = sessionHolder.getModel(sessionId); if(model==null){ throw new SaikuAdhocException( Messages.getErrorString("ReportGeneratorService.ERROR_0001_MASTERMODEL_NOT_FOUND") ); } sessionHolder.storeCda(sessionId); String path = prptProvider.getTemplatePath(); String solution = prptProvider.getSolution(); if(templateName != null && !templateName.equals("default")){ ReportTemplate template = prptProvider.getTemplate(path, solution, templateName); model.setReportTemplate(template); } MasterReport output = processReport(model); ByteArrayOutputStream stream = new ByteArrayOutputStream(); generateHtmlReport(output, stream, ParamUtils.getReportParameters("", model), report, acceptedPage); String string = stream.toString(); report.setData(string); } /** * Renders the report for a given query to pdf * * @param sessionId * @param report * @param acceptedPage * @param template * @return * @throws Exception * @throws IOException */ public void renderReportPdf(String sessionId, OutputStream stream) throws Exception { //html SaikuMasterModel model = sessionHolder.getModel(sessionId); sessionHolder.storeCda(sessionId); MasterReport output = processReport(model); generatePdfReport(output, stream, ParamUtils.getReportParameters("", model)); } /** * @param model * @param dataFactory * @param reportTemplate * @param output * @return * @throws ReportException * @throws ReportProcessingException * @throws SaikuAdhocException * @throws IOException * @throws ResourceException */ protected MasterReport processReport(SaikuMasterModel model) throws SaikuAdhocException { CachingDataFactory dataFactory = null; try { final MasterReport reportTemplate = prptProvider.getPrptTemplate(model.getReportTemplate()); final WizardBuilder wizardBuilder = new WizardBuilder(); final WizardSpecification wizardSpecification = wizardBuilder.build(model); ArrayList<String> queryIds = new ArrayList<String>(); //add the masterquery queryIds.add(model.getSessionId()); //add the param-queries List<SaikuParameter> parameters = model.getParameters(); for (SaikuParameter saikuParameter : parameters) { //TODO: there might be other types that dont need a query if(!saikuParameter.getType().equals("Date")){ queryIds.add(saikuParameter.getCategory() + "." + saikuParameter.getId()); } } reportTemplate.setDataFactory(cdaProvider.getDataFactory(queryIds)); reportTemplate.setQuery(model.getSessionId()); reportTemplate.setParameterDefinition(this.buildParamDefs(model)); reportTemplate.setAttribute(AttributeNames.Wizard.NAMESPACE, "wizard-spec", wizardSpecification); final ProcessingContext processingContext = new DefaultProcessingContext(); final DataSchemaDefinition definition = reportTemplate .getDataSchemaDefinition(); ReportParameterValues parameterValues = StateUtilities.computeParameterValueSet(reportTemplate, getReportParameterValues(model)); final ParameterDefinitionEntry[] parameterDefinitions = reportTemplate.getParameterDefinition() .getParameterDefinitions(); DefaultFlowController flowController = new DefaultFlowController( processingContext, definition, parameterValues, parameterDefinitions, false); ensureSaikuPreProcessorIsAdded(reportTemplate, model); ensureHasOverrideWizardFormatting(reportTemplate, flowController); dataFactory = new CachingDataFactory( reportTemplate.getDataFactory(), false); dataFactory.initialize(processingContext.getConfiguration(), processingContext.getResourceManager(), processingContext.getContentBase(), processingContext.getResourceBundleFactory()); DefaultFlowController postQueryFlowController = flowController .performQuery(dataFactory, reportTemplate.getQuery(), reportTemplate.getQueryLimit(), reportTemplate .getQueryTimeout(), flowController .getMasterRow().getResourceBundleFactory()); reportTemplate.setAttribute(AttributeNames.Wizard.NAMESPACE, AttributeNames.Wizard.ENABLE, Boolean.TRUE); //ist das einmal zu viel? ReportPreProcessor processor = new SaikuAdhocPreProcessor(); ((SaikuAdhocPreProcessor) processor).setSaikuMasterModel(model); MasterReport output = processor.performPreProcessing( reportTemplate, postQueryFlowController); output.setAttribute(AttributeNames.Wizard.NAMESPACE, AttributeNames.Wizard.ENABLE, Boolean.FALSE); output.setAttribute(AttributeNames.Wizard.NAMESPACE, AttributeNames.Wizard.ENABLE, Boolean.FALSE); TemplateUtils.mergePageSetup(model, output); return output; }catch(ReportDataFactoryException e){ log.error(e); throw new SaikuAdhocException(Messages.getErrorString("ReportGenerator.ERROR_0001_DATAFACTORY")); } catch (ReportProcessingException e) { log.error(e); throw new SaikuAdhocException(Messages.getErrorString("ReportGenerator.ERROR_0002_REPORT_PROCESSING")); } catch (ReportException e) { log.error(e); throw new SaikuAdhocException(Messages.getErrorString("ReportGenerator.ERROR_0003_WIZARD_BUILDER")); } finally { if(dataFactory!=null){ dataFactory.close(); } } } private ReportParameterDefinition buildParamDefs(SaikuMasterModel model) { DefaultParameterDefinition paramDef = new DefaultParameterDefinition(); List<SaikuParameter> parameters = model.getParameters(); //Generate Filter Parameters for (SaikuParameter saikuParameter : parameters) { final String categoryId = saikuParameter.getCategory(); final String columnId = saikuParameter.getId(); final String parameterName = "F_" + categoryId + "_" + columnId; if(saikuParameter.getType().equals("String")){ final DefaultListParameter listParam = new DefaultListParameter(categoryId + "." + columnId, columnId, columnId, parameterName, true, false, String[].class); listParam.setParameterAttribute( ParameterAttributeNames.Core.NAMESPACE, ParameterAttributeNames.Core.TYPE, ParameterAttributeNames.Core.TYPE_LIST); listParam.setParameterAttribute( ParameterAttributeNames.Core.NAMESPACE, ParameterAttributeNames.Core.LABEL, saikuParameter.getName()); paramDef.addParameterDefinition(listParam); } if(saikuParameter.getType().equals("Date")){ String nameFrom = parameterName + "_FROM"; String nameTo = parameterName + "_TO"; final PlainParameter plainParameterFrom = new PlainParameter(nameFrom, java.util.Date.class); plainParameterFrom.setParameterAttribute( ParameterAttributeNames.Core.NAMESPACE, ParameterAttributeNames.Core.LABEL, saikuParameter.getName() + " from"); plainParameterFrom.setParameterAttribute( ParameterAttributeNames.Core.NAMESPACE, ParameterAttributeNames.Core.TYPE, ParameterAttributeNames.Core.TYPE_DATEPICKER); plainParameterFrom.setParameterAttribute( ParameterAttributeNames.Core.NAMESPACE, ParameterAttributeNames.Core.DEFAULT_VALUE_FORMULA, "TODAY()"); paramDef.addParameterDefinition(plainParameterFrom); final PlainParameter plainParameterTo = new PlainParameter(nameTo, java.util.Date.class); plainParameterTo.setParameterAttribute( ParameterAttributeNames.Core.NAMESPACE, ParameterAttributeNames.Core.LABEL, saikuParameter.getName() + " until"); plainParameterTo.setParameterAttribute( ParameterAttributeNames.Core.NAMESPACE, ParameterAttributeNames.Core.TYPE, ParameterAttributeNames.Core.TYPE_DATEPICKER); plainParameterTo.setParameterAttribute( ParameterAttributeNames.Core.NAMESPACE, ParameterAttributeNames.Core.DEFAULT_VALUE_FORMULA, "TODAY()"); paramDef.addParameterDefinition(plainParameterTo); } if(saikuParameter.getType().equals("Numeric")){ final DefaultListParameter listParam = new DefaultListParameter(categoryId + "." + columnId, columnId, columnId, parameterName, true, false, Object[].class); listParam.setParameterAttribute( ParameterAttributeNames.Core.NAMESPACE, ParameterAttributeNames.Core.TYPE, ParameterAttributeNames.Core.TYPE_LIST); listParam.setParameterAttribute( ParameterAttributeNames.Core.NAMESPACE, ParameterAttributeNames.Core.LABEL, saikuParameter.getName()); paramDef.addParameterDefinition(listParam); } } //set layout horizontal paramDef.setAttribute( ParameterAttributeNames.Core.NAMESPACE, ParameterAttributeNames.Core.LAYOUT, ParameterAttributeNames.Core.LAYOUT_HORIZONTAL ); return paramDef; } /** * @param model * @param output * @return * @throws IOException * @throws BundleWriterException * @throws ContentIOException */ private ByteArrayOutputStream generatePrptOutput(SaikuMasterModel model, final MasterReport output) throws IOException, BundleWriterException, ContentIOException { final ByteArrayOutputStream prptContent = new ByteArrayOutputStream(); ensureSaikuPreProcessorIsRemoved(output); BundleWriter.writeReportToZipStream(output, prptContent); return prptContent; } protected ReportParameterValues getReportParameterValues( SaikuMasterModel model) { ReportParameterValues vals = new ReportParameterValues(); Map<String, Object> reportParameters = ParamUtils.getReportParameters("", model); if (null != model) { Collection<String> keyset = reportParameters.keySet(); for (Iterator<String> iterator = keyset.iterator(); iterator .hasNext();) { String key = (String) iterator.next(); vals.put(key, reportParameters.get(key)); } } return vals; } /** * Generate the report * * @param output * @param stream * @param report * @param acceptedPage * @param query2 * @throws Exception */ private void generateHtmlReport(MasterReport output, OutputStream stream, Map<String, Object> reportParameters, HtmlReport report, Integer acceptedPage) throws Exception{ final SimpleReportingComponent reportComponent = prptProvider.getReportingComponent(); reportComponent.setReport(output); reportComponent.setPaginateOutput(true); reportComponent.setInputs(reportParameters); reportComponent.setDefaultOutputTarget(HtmlTableModule.TABLE_HTML_PAGE_EXPORT_TYPE); reportComponent.setOutputTarget(HtmlTableModule.TABLE_HTML_PAGE_EXPORT_TYPE); reportComponent.setDashboardMode(true); reportComponent.setOutputStream(stream); reportComponent.setAcceptedPage(acceptedPage); //reportComponent.setUseContentRepository(false); reportComponent.validate(); reportComponent.execute(); report.setCurrentPage(reportComponent.getAcceptedPage()); report.setPageCount(reportComponent.getPageCount()); } /** * Generate the report * * @param output * @param stream * @param report * @param acceptedPage * @param query2 * @throws Exception */ private void generatePdfReport(MasterReport output, OutputStream stream, Map<String, Object> reportParameters) throws Exception{ final SimpleReportingComponent reportComponent = new SimpleReportingComponent(); reportComponent.setReport(output); reportComponent.setPaginateOutput(true); reportComponent.setInputs(reportParameters); reportComponent.setDefaultOutputTarget(PdfPageableModule.PDF_EXPORT_TYPE); reportComponent.setOutputTarget(PdfPageableModule.PDF_EXPORT_TYPE); reportComponent.setOutputStream(stream); reportComponent.validate(); reportComponent.execute(); } protected void ensureSaikuPreProcessorIsRemoved(final AbstractReportDefinition element) { final ReportPreProcessor[] oldProcessors = element.getPreProcessors(); ArrayList<ReportPreProcessor> newProcessors = new ArrayList<ReportPreProcessor>(); for (int i = 0; i < oldProcessors.length; i++) { ReportPreProcessor processor = oldProcessors[i]; if (!(processor instanceof SaikuAdhocPreProcessor || processor instanceof WizardProcessor)) { newProcessors.add(processor); } } final ReportPreProcessor[] array = newProcessors.toArray(new ReportPreProcessor[newProcessors.size()]); element.setAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.PREPROCESSORS, array); } protected static void ensureSaikuPreProcessorIsAdded(final AbstractReportDefinition element, SaikuMasterModel model) { final ReportPreProcessor[] processors = element.getPreProcessors(); boolean hasSaikuProcessor = false; for (int i = 0; i < processors.length; i++) { final ReportPreProcessor processor = processors[i]; if (processor instanceof SaikuAdhocPreProcessor) { hasSaikuProcessor = true; //Set the model on the processor ((SaikuAdhocPreProcessor) processor).setSaikuMasterModel(model); } } if (!hasSaikuProcessor) { //Add a new processor with the current model final SaikuAdhocPreProcessor processor = new SaikuAdhocPreProcessor(); processor.setSaikuMasterModel(model); element.addPreProcessor(processor); } } protected static void ensureHasOverrideWizardFormatting( AbstractReportDefinition reportTemplate, DefaultFlowController flowController) { final StructureFunction[] structureFunctions = reportTemplate.getStructureFunctions(); boolean hasOverrideWizardFormatting = false; for (int i = 0; i < structureFunctions.length; i++) { final StructureFunction structureFunction = structureFunctions[i]; if(structureFunction instanceof WizardOverrideFormattingFunction){ hasOverrideWizardFormatting = false; break; } } if(!hasOverrideWizardFormatting){ reportTemplate.addStructureFunction(new WizardOverrideFormattingFunction()); } } public void savePrpt(String sessionId, String path, String file, String username, String password) throws ContentIOException, IOException, ReportProcessingException, SaikuAdhocException, ResourceException { //Bundlewriter Exception abbilden try { SaikuMasterModel model = sessionHolder.getModel(sessionId); if (!file.endsWith(".prpt")) { file += ".prpt"; } String[] splits = ParamUtils.splitFirst(path.substring(1),"/"); ByteArrayOutputStream prptContent = null; MasterReport output = processReport(model); ((CdaDataFactory) output.getDataFactory()).setUsername(username); ((CdaDataFactory) output.getDataFactory()).setPassword(password); prptContent = generatePrptOutput(model, output); String solPath = splits.length > 1 ? splits[1] : ""; repository.writeFile(splits[0], solPath, file, prptContent); } catch (BundleWriterException e) { log.error(e); throw new SaikuAdhocException(Messages.getErrorString("ReportGenerator.ERROR_0004_BUNDLEWRITER")); } } public void saveCda(String sessionId, String fullPath, String file) throws SaikuAdhocException { SaikuMasterModel model = sessionHolder.getModel(sessionId); if (!file.endsWith(".cda")) { file += ".cda"; } String[] splits = ParamUtils.splitFirst(fullPath.substring(1),"/"); try{ final Domain domain = metadataProvider.getDomain(model.getDomainId()); final LogicalModel logicalModel = metadataProvider.getLogicalModel(model.getDomainId(),model.getLogicalModelId()); final CdaBuilder cdaBuilder = new CdaBuilder(); CdaSettings cdaSettings = cdaBuilder.build(model, domain, logicalModel); String solPath = splits.length > 1 ? splits[1] : ""; cdaProvider.addDatasource(splits[0], solPath, file, cdaSettings.asXML()); } catch (Exception e) { e.printStackTrace(); throw new SaikuAdhocException( Messages.getErrorString("Repository.ERROR_0001_COULD_NOT_PUBLISH_FILE") ); } } }