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 won't 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