1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 23 package com.sun.star.report.pentaho; 24 25 import com.sun.star.report.DataSourceFactory; 26 import com.sun.star.report.ImageService; 27 import com.sun.star.report.InputRepository; 28 import com.sun.star.report.JobDefinitionException; 29 import com.sun.star.report.JobProgressIndicator; 30 import com.sun.star.report.JobProperties; 31 import com.sun.star.report.OutputRepository; 32 import com.sun.star.report.ParameterMap; 33 import com.sun.star.report.ReportEngineParameterNames; 34 import com.sun.star.report.ReportExecutionException; 35 import com.sun.star.report.ReportJob; 36 import com.sun.star.report.ReportJobDefinition; 37 import com.sun.star.report.SDBCReportDataFactory; 38 import com.sun.star.report.pentaho.loader.InputRepositoryLoader; 39 import com.sun.star.report.pentaho.model.OfficeDetailSection; 40 import com.sun.star.report.pentaho.model.OfficeDocument; 41 import com.sun.star.report.pentaho.model.OfficeGroup; 42 import com.sun.star.report.pentaho.model.OfficeReport; 43 import com.sun.star.report.pentaho.output.chart.ChartRawReportProcessor; 44 import com.sun.star.report.pentaho.output.spreadsheet.SpreadsheetRawReportProcessor; 45 import com.sun.star.report.pentaho.output.text.TextRawReportProcessor; 46 47 import java.io.IOException; 48 49 import java.lang.Integer; 50 51 import java.util.ArrayList; 52 import java.util.List; 53 54 import org.apache.commons.logging.Log; 55 import org.apache.commons.logging.LogFactory; 56 57 import org.jfree.report.expressions.Expression; 58 import org.jfree.report.expressions.FormulaExpression; 59 import org.jfree.report.flow.DefaultReportJob; 60 import org.jfree.report.flow.ReportProcessor; 61 import org.jfree.report.flow.raw.XmlPrintReportProcessor; 62 import org.jfree.report.structure.Node; 63 import org.jfree.report.structure.Section; 64 import org.jfree.report.util.ReportParameters; 65 66 import org.pentaho.reporting.libraries.formula.lvalues.ContextLookup; 67 import org.pentaho.reporting.libraries.formula.lvalues.FormulaFunction; 68 import org.pentaho.reporting.libraries.formula.lvalues.LValue; 69 import org.pentaho.reporting.libraries.formula.lvalues.Term; 70 import org.pentaho.reporting.libraries.formula.parser.FormulaParser; 71 import org.pentaho.reporting.libraries.formula.parser.ParseException; 72 import org.pentaho.reporting.libraries.resourceloader.Resource; 73 import org.pentaho.reporting.libraries.resourceloader.ResourceException; 74 import org.pentaho.reporting.libraries.resourceloader.ResourceManager; 75 76 77 /** 78 * ToDo: Allow interrupting of jobs and report the report progress 79 */ 80 public class PentahoReportJob implements ReportJob 81 { 82 83 private static final Log LOGGER = LogFactory.getLog(PentahoReportJob.class); 84 private boolean finished; 85 private final List listeners; 86 private final DataSourceFactory dataSourceFactory; 87 private final OutputRepository outputRepository; 88 private final JobProperties jobProperties; 89 private OfficeDocument report; 90 private final ResourceManager resourceManager; 91 private final String outputName; 92 private final ImageService imageService; 93 private final InputRepository inputRepository; 94 private final ReportJobDefinition definition; 95 private final List masterValues; 96 private final List detailColumns; 97 getDefinition()98 public ReportJobDefinition getDefinition() 99 { 100 return definition; 101 } 102 PentahoReportJob(final ReportJobDefinition definition)103 public PentahoReportJob(final ReportJobDefinition definition) 104 throws JobDefinitionException 105 { 106 if (definition == null) 107 { 108 throw new NullPointerException(); 109 } 110 111 this.definition = definition; 112 this.listeners = new ArrayList(); 113 this.jobProperties = definition.getProcessingParameters().copy(); 114 115 this.dataSourceFactory = (DataSourceFactory) jobProperties.getProperty(ReportEngineParameterNames.INPUT_DATASOURCE_FACTORY); 116 if (this.dataSourceFactory == null) 117 { 118 throw new JobDefinitionException("DataSourceFactory must not be null."); 119 } 120 121 this.outputRepository = (OutputRepository) jobProperties.getProperty(ReportEngineParameterNames.OUTPUT_REPOSITORY); 122 if (this.outputRepository == null) 123 { 124 throw new JobDefinitionException("OutputRepository must not be null."); 125 } 126 127 this.inputRepository = 128 (InputRepository) jobProperties.getProperty(ReportEngineParameterNames.INPUT_REPOSITORY); 129 if (inputRepository == null) 130 { 131 throw new JobDefinitionException("InputRepository must not be null."); 132 } 133 134 this.outputName = (String) jobProperties.getProperty(ReportEngineParameterNames.OUTPUT_NAME); 135 if (outputName == null) 136 { 137 throw new JobDefinitionException("OutputName must not be null"); 138 } 139 140 this.imageService = (ImageService) jobProperties.getProperty(ReportEngineParameterNames.IMAGE_SERVICE); 141 if (imageService == null) 142 { 143 throw new JobDefinitionException("A valid image-service implementation must be given."); 144 } 145 146 this.masterValues = (ArrayList) jobProperties.getProperty(ReportEngineParameterNames.INPUT_MASTER_VALUES); 147 this.detailColumns = (ArrayList) jobProperties.getProperty(ReportEngineParameterNames.INPUT_DETAIL_COLUMNS); 148 Integer maxRows=(Integer) jobProperties.getProperty(ReportEngineParameterNames.MAXROWS); 149 150 this.resourceManager = new ResourceManager(); 151 this.resourceManager.registerDefaults(); 152 this.resourceManager.registerLoader(new InputRepositoryLoader(inputRepository)); 153 154 try 155 { 156 this.report = parseReport(definition); 157 } 158 catch (ResourceException e) 159 { 160 throw new JobDefinitionException("Failed to parse the report.", e); 161 } 162 } 163 parseReport(final ReportJobDefinition definition)164 private OfficeDocument parseReport(final ReportJobDefinition definition) 165 throws ResourceException, JobDefinitionException 166 { 167 final String reportResource = (String) this.jobProperties.getProperty(ReportEngineParameterNames.INPUT_NAME); 168 if (reportResource == null) 169 { 170 throw new JobDefinitionException("Report definition name must be given"); 171 } 172 173 final Resource res = resourceManager.createDirectly("sun:oo://" + reportResource, OfficeDocument.class); 174 final OfficeDocument tempReport = (OfficeDocument) res.getResource(); 175 tempReport.setDataFactory(new StarReportDataFactory(dataSourceFactory)); 176 tempReport.setJobProperties(definition.getProcessingParameters().copy()); 177 final ReportParameters inputParameters = tempReport.getInputParameters(); 178 179 final ParameterMap queryParameters = definition.getQueryParameters(); 180 final String[] paramKeys = queryParameters.keys(); 181 for (int i = 0; i < paramKeys.length; i++) 182 { 183 final String key = paramKeys[i]; 184 inputParameters.put(key, queryParameters.get(key)); 185 } 186 187 return tempReport; 188 } 189 addProgressIndicator(final JobProgressIndicator indicator)190 public void addProgressIndicator(final JobProgressIndicator indicator) 191 { 192 listeners.add(indicator); 193 } 194 195 /** 196 * Interrupt the job. 197 */ interrupt()198 public void interrupt() 199 { 200 // hey, not yet .. 201 } 202 203 /** 204 * Queries the jobs result status. 205 * 206 * @return true, if the job is finished (or has been interrupted), false if the job 207 * waits for activation. 208 */ isFinished()209 public boolean isFinished() 210 { 211 return finished; 212 } 213 finish()214 public void finish() 215 { 216 finished = true; 217 } 218 219 /** 220 * Queries the jobs execution status. 221 * 222 * @return true, if the job is currently running, false otherwise. 223 */ isRunning()224 public boolean isRunning() 225 { 226 return !finished; 227 } 228 removeProgressIndicator(final JobProgressIndicator indicator)229 public void removeProgressIndicator(final JobProgressIndicator indicator) 230 { 231 listeners.remove(indicator); 232 } 233 collectGroupExpressions(final Node[] nodes, final List expressions, final FormulaParser parser, final Expression reportFunctions[])234 private void collectGroupExpressions(final Node[] nodes, final List expressions, final FormulaParser parser, final Expression reportFunctions[]) 235 { 236 for (int i = 0; i < nodes.length; i++) 237 { 238 final Node node = nodes[i]; 239 if (node instanceof OfficeGroup) 240 { 241 final OfficeGroup group = (OfficeGroup) node; 242 final FormulaExpression exp = (FormulaExpression) group.getGroupingExpression(); 243 if (exp == null) 244 { 245 continue; 246 } 247 248 try 249 { 250 final String expression = exp.getFormulaExpression(); 251 if (expression == null) 252 { 253 continue; 254 } 255 final FormulaFunction function = (FormulaFunction) parser.parse(expression); 256 final LValue[] parameters = function.getChildValues(); 257 if (parameters.length > 0) 258 { 259 String name = parameters[0].toString(); 260 if (parameters[0] instanceof ContextLookup) 261 { 262 final ContextLookup context = (ContextLookup) parameters[0]; 263 name = context.getName(); 264 } 265 for (int j = 0; j < reportFunctions.length; j++) 266 { 267 if (reportFunctions[j] instanceof FormulaExpression) 268 { 269 final FormulaExpression reportExp = (FormulaExpression) reportFunctions[j]; 270 271 if (reportExp.getName().equals(name)) 272 { 273 LValue val = parser.parse(reportExp.getFormulaExpression()); 274 while( !(val instanceof ContextLookup)) 275 { 276 if (val instanceof Term) 277 { 278 val = ((Term)val).getHeadValue(); 279 } 280 else if (val instanceof FormulaFunction) 281 { 282 final FormulaFunction reportFunction = (FormulaFunction) val; 283 val = reportFunction.getChildValues()[0]; 284 } 285 } 286 if (val instanceof ContextLookup) 287 { 288 final ContextLookup context = (ContextLookup) val; 289 name = context.getName(); 290 } 291 break; 292 } 293 } 294 } 295 296 final Object[] pair = new Object[2]; 297 pair[0] = name; 298 pair[1] = group.getAttribute(OfficeNamespaces.OOREPORT_NS, "sort-ascending"); 299 expressions.add(pair); 300 } 301 } 302 catch (ParseException ex) 303 { 304 LOGGER.error("ReportProcessing failed", ex); 305 } 306 } 307 else if (node instanceof OfficeDetailSection) 308 { 309 return; 310 } 311 if (node instanceof Section) 312 { 313 final Section section = (Section) node; 314 collectGroupExpressions(section.getNodeArray(), expressions, parser, reportFunctions); 315 } 316 } 317 } 318 setMetaDataProperties(DefaultReportJob job)319 private void setMetaDataProperties(DefaultReportJob job) 320 { 321 job.getConfiguration().setConfigProperty(ReportEngineParameterNames.AUTHOR, (String) jobProperties.getProperty(ReportEngineParameterNames.AUTHOR)); 322 job.getConfiguration().setConfigProperty(ReportEngineParameterNames.TITLE, (String) jobProperties.getProperty(ReportEngineParameterNames.TITLE)); 323 } 324 325 /** 326 * Although we might want to run the job as soon as it has been created, sometimes it is 327 * wiser to let the user add some listeners first. If we execute at once, the user 328 * either has to deal with threading code or wont receive any progress information in 329 * single threaded environments. 330 */ execute()331 public void execute() 332 throws ReportExecutionException, IOException 333 { 334 final DefaultReportJob job = new DefaultReportJob(report); 335 setMetaDataProperties(job); 336 final String contentType = (String) jobProperties.getProperty(ReportEngineParameterNames.CONTENT_TYPE); 337 //noinspection OverlyBroadCatchBlock 338 try 339 { 340 final ReportParameters parameters = job.getParameters(); 341 342 if (masterValues != null && detailColumns != null) 343 { 344 parameters.put(SDBCReportDataFactory.MASTER_VALUES, masterValues); 345 parameters.put(SDBCReportDataFactory.DETAIL_COLUMNS, detailColumns); 346 } 347 348 final Node[] nodes = report.getNodeArray(); 349 350 final FormulaParser parser = new FormulaParser(); 351 final ArrayList expressions = new ArrayList(); 352 final OfficeReport officeReport = (OfficeReport) ((Section) nodes[0]).getNode(0); 353 final Section reportBody = (Section) officeReport.getBodySection(); 354 collectGroupExpressions(reportBody.getNodeArray(), expressions, parser, officeReport.getExpressions()); 355 parameters.put(SDBCReportDataFactory.GROUP_EXPRESSIONS, expressions); 356 final String command = (String) officeReport.getAttribute(OfficeNamespaces.OOREPORT_NS, "command"); 357 final String commandType = (String) officeReport.getAttribute(OfficeNamespaces.OOREPORT_NS, SDBCReportDataFactory.COMMAND_TYPE); 358 final String escapeProcessing = (String) officeReport.getAttribute(OfficeNamespaces.OOREPORT_NS, SDBCReportDataFactory.ESCAPE_PROCESSING); 359 report.setQuery(command); 360 parameters.put(SDBCReportDataFactory.COMMAND_TYPE, commandType); 361 parameters.put(SDBCReportDataFactory.ESCAPE_PROCESSING, !("false".equals(escapeProcessing))); 362 363 final String filter = (String) officeReport.getAttribute(OfficeNamespaces.OOREPORT_NS, "filter"); 364 parameters.put(SDBCReportDataFactory.UNO_FILTER, filter); 365 366 parameters.put(ReportEngineParameterNames.MAXROWS, report.getJobProperties().getProperty(ReportEngineParameterNames.MAXROWS)); 367 368 final long startTime = System.currentTimeMillis(); 369 final ReportProcessor rp = getProcessorForContentType(contentType); 370 rp.processReport(job); 371 job.close(); 372 final long endTime = System.currentTimeMillis(); 373 LOGGER.debug("Report processing time: " + (endTime - startTime)); 374 } 375 catch (final Exception e) 376 { 377 String message = e.getMessage(); 378 if (message == null || message.length() == 0) 379 { 380 message = "Failed to process the report"; 381 } 382 throw new ReportExecutionException(message, e); 383 } 384 385 } 386 getProcessorForContentType(final String mimeType)387 protected ReportProcessor getProcessorForContentType(final String mimeType) 388 throws ReportExecutionException 389 { 390 final ReportProcessor ret; 391 392 if (PentahoReportEngineMetaData.OPENDOCUMENT_SPREADSHEET.equals(mimeType)) 393 { 394 ret = new SpreadsheetRawReportProcessor(inputRepository, outputRepository, outputName, imageService, dataSourceFactory); 395 } 396 else if (PentahoReportEngineMetaData.OPENDOCUMENT_TEXT.equals(mimeType)) 397 { 398 ret = new TextRawReportProcessor(inputRepository, outputRepository, outputName, imageService, dataSourceFactory); 399 } 400 else if (PentahoReportEngineMetaData.OPENDOCUMENT_CHART.equals(mimeType)) 401 { 402 ret = new ChartRawReportProcessor(inputRepository, outputRepository, outputName, imageService, dataSourceFactory); 403 } 404 else if (PentahoReportEngineMetaData.DEBUG.equals(mimeType)) 405 { 406 ret = new XmlPrintReportProcessor(System.out, "ISO-8859-1"); 407 } 408 else 409 { 410 throw new ReportExecutionException("Invalid mime-type"); 411 } 412 413 return ret; 414 } 415 } 416