001/*
002 * Copyright (c) 2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: barseghian $'
006 * '$Date: 2013-01-16 01:59:40 +0000 (Wed, 16 Jan 2013) $' 
007 * '$Revision: 31337 $'
008 * 
009 * Permission is hereby granted, without written agreement and without
010 * license or royalty fees, to use, copy, modify, and distribute this
011 * software and its documentation for any purpose, provided that the above
012 * copyright notice and the following two paragraphs appear in all copies
013 * of this software.
014 *
015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
019 * SUCH DAMAGE.
020 *
021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
026 * ENHANCEMENTS, OR MODIFICATIONS.
027 *
028 */
029
030package org.kepler.reporting.gui;
031
032import java.awt.BorderLayout;
033import java.awt.Color;
034import java.awt.Component;
035import java.awt.event.ActionEvent;
036import java.awt.event.WindowAdapter;
037import java.awt.event.WindowEvent;
038import java.io.ByteArrayInputStream;
039import java.io.File;
040import java.io.IOException;
041import java.io.InputStream;
042import java.util.HashMap;
043import java.util.List;
044import java.util.Map;
045import java.util.concurrent.Executors;
046
047import javax.swing.AbstractAction;
048import javax.swing.JButton;
049import javax.swing.JFileChooser;
050import javax.swing.JOptionPane;
051import javax.swing.JPanel;
052import javax.swing.JScrollPane;
053import javax.xml.transform.TransformerException;
054
055import org.apache.commons.logging.Log;
056import org.apache.commons.logging.LogFactory;
057import org.apache.fop.apps.FOPException;
058import org.kepler.configuration.ConfigurationManager;
059import org.kepler.configuration.ConfigurationProperty;
060import org.kepler.gui.KeplerGraphFrame;
061import org.kepler.gui.KeplerGraphFrame.Components;
062import org.kepler.gui.KeplerGraphFrameUpdater;
063import org.kepler.gui.TabPane;
064import org.kepler.gui.TabPaneFactory;
065import org.kepler.gui.state.ReportingStateChangeEvent;
066import org.kepler.gui.state.StateChangeEvent;
067import org.kepler.gui.state.StateChangeListener;
068import org.kepler.gui.state.StateChangeMonitor;
069import org.kepler.gui.state.ViewStateChangeEvent;
070import org.kepler.moml.NamedObjId;
071import org.kepler.objectmanager.cache.LocalRepositoryManager;
072import org.kepler.objectmanager.lsid.KeplerLSID;
073import org.kepler.provenance.ProvenanceEnabledListener;
074import org.kepler.provenance.ProvenanceRecorder;
075import org.kepler.provenance.Queryable;
076import org.kepler.reporting.rio.ReportAssembler;
077import org.kepler.reporting.rio.ReportInstance;
078import org.kepler.reporting.rio.fop.ReportRenderer;
079import org.kepler.reporting.rio.util.ProvenanceUtil;
080import org.kepler.reporting.roml.ReportLayout;
081import org.kepler.util.WorkflowRun;
082import org.kepler.workflow.WorkflowManager;
083
084import ptolemy.actor.gui.PtolemyFrame;
085import ptolemy.actor.gui.TableauFrame;
086import ptolemy.gui.JFileChooserBugFix;
087import ptolemy.gui.PtFileChooser;
088import ptolemy.gui.PtGUIUtilities;
089import ptolemy.kernel.util.IllegalActionException;
090import ptolemy.kernel.util.NameDuplicationException;
091import ptolemy.kernel.util.NamedObj;
092import ptolemy.util.MessageHandler;
093
094
095public class ReportViewerPanel extends JPanel implements TabPane, StateChangeListener, 
096        ProvenanceEnabledListener, KeplerGraphFrameUpdater {
097
098        private TableauFrame _frame;
099        
100        private String _title;
101        
102        private JScrollPane scrollPane;
103
104        private ReportInstance report;
105
106        private String xslt;
107                
108        private JButton _refreshButton;
109        
110        private JButton _saveButton;
111        
112        private String _keplerdatadir;
113        
114        public static Log log = LogFactory.getLog(ReportViewerPanel.class);
115        
116        public ReportViewerPanel() {
117                super(new BorderLayout());
118        }
119
120        /**
121         * Implementation of TabPane getName()
122         */
123        public String getTabName() {
124                return _title;
125        }
126        
127        public void setTabName(String name) {
128                _title = name;
129        }
130
131        /**
132         * Implementation of TabPane setParentFrame(TableauFrame)
133         */
134        public void setParentFrame(TableauFrame parent) {
135                _frame = parent;
136        }
137
138        /**
139         * Implementation of getParentFrame getName()
140         */
141        public TableauFrame getParentFrame() {
142                return _frame;
143        }
144
145        public void initializeTab() throws Exception {
146                        
147                StateChangeMonitor.getInstance().addStateChangeListener(
148                                ReportingStateChangeEvent.WORKFLOW_EXECUTION_COMPLETE,
149                                this);
150                
151                StateChangeMonitor.getInstance().addStateChangeListener(
152                                WorkflowRun.WORKFLOWRUN_SELECTED,
153                                this);
154                
155                // clean up the listener when the frame closes
156           final StateChangeListener listenerReference = this;
157           final KeplerGraphFrameUpdater keplerGraphFrameUpdater = this;
158            _frame.addWindowListener(new WindowAdapter() {
159                        public void windowClosed(WindowEvent e) {
160                                StateChangeMonitor.getInstance().removeStateChangeListener(ReportingStateChangeEvent.WORKFLOW_EXECUTION_COMPLETE, listenerReference);
161                                StateChangeMonitor.getInstance().removeStateChangeListener(WorkflowRun.WORKFLOWRUN_SELECTED, listenerReference);
162                                KeplerGraphFrame.removeUpdater(keplerGraphFrameUpdater);
163                        }
164                });
165                
166                //make the dummy test button
167                try {
168                        _refreshButton = 
169                                new JButton(
170                                        new AbstractAction("Refresh (using current layout)") {
171
172                                                public void actionPerformed(ActionEvent e) {
173                                                        try {
174                                                                renderReportInstanceFromCurrentLayout(null);
175                                                                renderReportTab();
176                                                        } catch (Exception e1) {
177                                                                // TODO Auto-generated catch block
178                                                                e1.printStackTrace();
179                                                        }
180                                                }
181                        });
182                } catch (Exception e) {
183                        e.printStackTrace();
184                }
185
186        
187                // make the Save pdf button
188                try {
189                        _saveButton = new JButton(new AbstractAction("Save pdf") {
190
191                                public void actionPerformed(ActionEvent e) {
192                                        
193                                        if (report == null){
194                                                return;
195                                        }
196
197                                        LocalRepositoryManager lrm = LocalRepositoryManager
198                                                        .getInstance();
199                                        // Avoid white boxes in file chooser, see
200                                        // http://bugzilla.ecoinformatics.org/show_bug.cgi?id=3801
201                                        JFileChooserBugFix jFileChooserBugFix = new JFileChooserBugFix();
202                                        Color background = jFileChooserBugFix.saveBackground();
203
204                                        PtFileChooser chooser = new PtFileChooser(_frame, "Save PDF", JFileChooser.SAVE_DIALOG);
205                                        chooser.setCurrentDirectory(lrm.getSaveRepository());
206                                        File pdfFile = new File(lrm.getSaveRepository(),
207                                                        "report.pdf");
208                                        chooser.setSelectedFile(pdfFile);
209
210                                        int returnVal = chooser.showDialog(_frame,
211                                                        "Save");
212
213                                        if (returnVal == JFileChooser.APPROVE_OPTION) {
214                                                pdfFile = chooser.getSelectedFile();
215                                                
216                                                if (pdfFile.exists() && !PtGUIUtilities.useFileDialog()) {
217                                                        int choice = JOptionPane.showConfirmDialog(_frame,
218                                                                        "The file exists, would you like to overwrite it?", "",
219                                                                        JOptionPane.YES_NO_OPTION);
220                                                        if (choice == JOptionPane.YES_OPTION) {                                         
221                                                                try {
222                                                                        ReportRenderer.convertReport2PDF(report, pdfFile,
223                                                                        null);
224                                                                } catch (FOPException e1) {
225                                                                        e1.printStackTrace();
226                                                                } catch (IOException e1) {
227                                                                        e1.printStackTrace();
228                                                                } catch (TransformerException e1) {
229                                                                        e1.printStackTrace();
230                                                                }
231                                                        }
232                                                }
233                                                else{
234                                                        try {
235                                                                ReportRenderer.convertReport2PDF(report, pdfFile,
236                                                                null);
237                                                        } catch (FOPException e1) {
238                                                                e1.printStackTrace();
239                                                        } catch (IOException e1) {
240                                                                e1.printStackTrace();
241                                                        } catch (TransformerException e1) {
242                                                                e1.printStackTrace();
243                                                        }
244                                                }
245                                        }
246                                        jFileChooserBugFix.restoreBackground(background);
247
248                                }
249
250                        });
251
252                        JPanel buttonPanel = new JPanel();
253                        buttonPanel.add(_refreshButton);
254
255                        this.add(buttonPanel, BorderLayout.NORTH);
256
257                        scrollPane = new JScrollPane();
258                        this.add(scrollPane, BorderLayout.CENTER);
259
260                        //KeplerGraphFrame.addUpdater(this);
261
262                        buttonPanel.add(_saveButton);
263
264                        scrollPane = new JScrollPane();
265                        this.add(scrollPane, BorderLayout.CENTER);
266
267                        KeplerGraphFrame.addUpdater(this);
268
269                } catch (Exception e) {
270                        e.printStackTrace();
271                }
272
273}
274        
275        public void renderReportInstanceFromCurrentLayout(Integer execId) throws Exception {
276
277                // look up the the workflow LSID
278                NamedObj reference = ((PtolemyFrame)_frame).getModel();
279                KeplerLSID lsid = NamedObjId.getIdFor(reference);
280                Queryable queryable = null;
281                
282                // get the latest execution
283                if (execId == null) {
284                        queryable = ProvenanceUtil.getQueryable(lsid, _frame);
285                        execId = queryable.getLastExecutionForWorkflow(lsid);
286                        //execId = ProvenanceUtil.getQueryable(lsid, _frame).getLastExecutionForWorkflow(lsid);
287                }
288                if (execId == null) {
289                        if (_frame != null){
290                                String warnMessage = "No execution history found for this workflow. Please run the workflow before viewing report.";
291                                JOptionPane.showMessageDialog(_frame, warnMessage, "Error",
292                        JOptionPane.ERROR_MESSAGE);
293                }
294                        return;
295                }
296                
297                // look up the ROML for the workflow from the manager
298                ReportLayout rl = WorkflowManager.getInstance().getWorkflow(_frame, lsid).getReportLayout(_frame);
299                
300                // assemble the instance from provenance data + current layout
301                if (rl != null){
302                        report = ReportAssembler.populateReport(queryable, rl, lsid, execId, _frame);
303                }
304                else{
305                        report = null;
306                }
307                
308        }
309        
310        public void setReport(ReportInstance report) {
311                this.report = report;
312        }
313        
314        public void setXslt(String path) {
315                this.xslt = path;
316        }
317
318        public static class Factory extends TabPaneFactory {
319                /**
320                 * Create a factory with the given name and container.
321                 * 
322                 *@param container
323                 *            The container.
324                 *@param name
325                 *            The name of the entity.
326                 *@exception IllegalActionException
327                 *                If the container is incompatible with this attribute.
328                 *@exception NameDuplicationException
329                 *                If the name coincides with an attribute already in the
330                 *                container.
331                 */
332                public Factory(NamedObj container, String name)
333                                throws IllegalActionException, NameDuplicationException {
334                        super(container, name);
335                }
336
337                /**
338                 * Create a pane that displays the given report viewer.
339                 * 
340                 * @return A new TabPane that displays the viewer
341                 */
342
343                public TabPane createTabPane(TableauFrame parent) {
344                        ReportViewerPanel rvp = new ReportViewerPanel();
345                        rvp.setTabName(this.getName());
346                        // StringAttribute xslt = (StringAttribute)
347                        // this.getAttribute("_xslt");
348                        List<ConfigurationProperty> l = ConfigurationManager.getInstance()
349                                        .getProperties(ConfigurationManager.getModule("reporting"),
350                                                        "config.pair");
351                        for (int i = 0; i < l.size(); i++) {
352                                ConfigurationProperty cp = l.get(i);
353                                if (cp.getProperty("name").getValue().equals(
354                                                "Report Viewer XSLT")) {
355                                        rvp.setXslt(cp.getProperty("value").getValue());
356                                        return rvp;
357                                }
358                        }
359                        return null;
360                }
361                
362        }
363
364        public void handleStateChange(StateChangeEvent event) { 
365                try {
366                        NamedObj reference = ((PtolemyFrame)_frame).getModel();
367                        
368                        if (event.getChangedState().equals(ReportingStateChangeEvent.REPORT_INSTANCE_CHANGED)) {
369                                if (event.getReference().equals(reference)) {
370                                        // render the tab
371                                        renderReportInstanceFromCurrentLayout(null);
372                                        renderReportTab();
373                                }
374                        }
375                        if (event.getChangedState().equals(ViewStateChangeEvent.WORKFLOW_EXECUTION_COMPLETE)) {
376                                if (event.getReference().equals(reference)) {
377                                        //render the tab in a thread
378                                        Executors.newSingleThreadExecutor().execute(new Runnable() {
379                                                public void run() {
380                                                        try {
381                                                                lookupReportInstance(null);
382                                                                renderReportTab();
383                                                        } catch (Exception e) {
384                                                                // TODO Auto-generated catch block
385                                                                e.printStackTrace();
386                                                        }
387                                                }
388                                        });
389                                }
390                        }
391                        // render selected 
392                        if (event.getChangedState().equals(WorkflowRun.WORKFLOWRUN_SELECTED)) {
393                                NamedObj namedObj = event.getReference();
394                                if (namedObj instanceof WorkflowRun) {
395                                        WorkflowRun wfRun = (WorkflowRun) namedObj;
396                                        if (event.getSource() == (KeplerGraphFrame) this._frame){
397                                                final Integer execId = wfRun.getExecId();
398                                                //render the tab in a thread
399                                                Executors.newSingleThreadExecutor().execute(new Runnable() {
400                                                        public void run() {
401                                                                try {
402                                                                        lookupReportInstance(execId);
403                                                                        renderReportTab();
404                                                                } catch (Exception e) {
405                                                                        // TODO Auto-generated catch block
406                                                                        e.printStackTrace();
407                                                                }
408                                                        }
409                                                });
410                                        }
411                                }
412                        }
413                }
414                catch (Exception e) {
415                        e.printStackTrace();
416                }
417        }
418
419        /**
420         * 
421         * @param execId - if null, default queryable is used to look up last execution for the model
422         * associated with _frame
423         * @throws Exception
424         */
425        public void lookupReportInstance(Integer execId) throws Exception {
426        
427                // look up the the workflow LSID
428                NamedObj reference = ((PtolemyFrame)_frame).getModel();
429                KeplerLSID lsid = NamedObjId.getIdFor(reference);
430                Queryable queryable = null;
431                
432                // get the latest execution from the default queryable if there was none given
433                if (execId == null) {
434                        queryable = ProvenanceRecorder.getDefaultQueryable(reference);
435                        execId = queryable.getLastExecutionForWorkflow(lsid);
436                        //execId = ProvenanceUtil.getQueryable(lsid, _frame).getLastExecutionForWorkflow(lsid);
437                }
438                else{
439                        queryable = ProvenanceUtil.getQueryable(lsid, _frame);
440                }
441                if (execId == null) {
442                        MessageHandler.message("ReportViewerPanel lookupReportInstance - No execution history found for this workflow. Please run the workflow before viewing report.");
443                        return;
444                }
445                
446                // look up the RIO instance XML in provenance
447                Map<String, String> metadataMap = new HashMap<String, String>();
448                metadataMap.put("type", ReportInstance.class.getName());
449                //List<byte[]> dataList = ProvenanceRecorder.getDefaultQueryable(reference).getAssociatedDataForExecution(execId, metadataMap, false);
450                List<byte[]> dataList = queryable.getAssociatedDataForExecution(execId, metadataMap, false);
451                if (dataList != null && !dataList.isEmpty()) {
452                        //don't use a ReportLayout, or you'll get a serialization error later
453                        //ReportLayout rl = ReportRenderer.convertXML2Report(new ByteArrayInputStream(dataList.get(0)));
454                        //report = new ReportInstance(rl);
455                        report = (ReportInstance) ReportRenderer.convertXML2Report(new ByteArrayInputStream(dataList.get(0)));
456                }
457                else{
458                        report = null;
459                }
460                
461        }
462        
463        public void renderReportTab() {
464                if (this.report != null) {
465                        try {
466        
467                                // use the XSLT to render the AWT version
468                                // NOTE: this is NOT the PDF
469                                InputStream xsltStream = this.getClass().getResourceAsStream(xslt);
470                                Component reportView = ReportRenderer.convertReport2AWT(report, xsltStream);
471                                                        
472                                // add to the scrollpane
473                                scrollPane.setViewportView(reportView);
474                                
475                        } catch (Exception e) {
476                                log.error("error generating report view");
477                                e.printStackTrace();
478                        }
479                }
480                else{
481                        scrollPane.setViewport(null);
482                }
483        }
484
485        /*
486         * (non-Javadoc)
487         * @see org.kepler.provenance.ProvenanceEnabledListener#toggle(boolean)
488         */
489        public void toggle(boolean enabled) {
490                _refreshButton.setEnabled(enabled);
491        }
492
493        
494        public void updateFrameComponents(Components components) {
495
496                NamedObj model = ((PtolemyFrame) _frame).getModel();
497                ProvenanceRecorder pr = ProvenanceRecorder.getDefaultProvenanceRecorder(model);
498                
499                if (pr != null){
500                        pr.removeEnabledListener(this);
501                        pr.addEnabledListener(this);
502                }
503        }
504
505        public int compareTo(KeplerGraphFrameUpdater o) {
506                if (this == o)
507                        return 0;
508                else
509                        //must be greater than provenance
510                        return 1;
511        }
512
513        public void dispose(KeplerGraphFrame frame) {
514                
515        }
516
517}