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}