001/*
002 * Copyright (c) 2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: barseghian $'
006 * '$Date: 2013-01-15 21:38:31 +0000 (Tue, 15 Jan 2013) $' 
007 * '$Revision: 31329 $'
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
030/**
031 * 
032 */
033package org.kepler.kar.handlers;
034
035import java.io.ByteArrayInputStream;
036import java.io.File;
037import java.io.FileInputStream;
038import java.io.InputStream;
039import java.util.HashMap;
040import java.util.Hashtable;
041import java.util.List;
042import java.util.Map;
043import java.util.Vector;
044
045import javax.swing.JLabel;
046
047import org.apache.commons.logging.Log;
048import org.apache.commons.logging.LogFactory;
049import org.kepler.gui.state.ReportingStateChangeEvent;
050import org.kepler.gui.state.StateChangeMonitor;
051import org.kepler.kar.KAREntry;
052import org.kepler.kar.KAREntryHandler;
053import org.kepler.kar.KAREntryHandlerFactory;
054import org.kepler.kar.KARFile;
055import org.kepler.moml.NamedObjId;
056import org.kepler.moml.NamedObjIdReferralList;
057import org.kepler.objectmanager.ObjectManager;
058import org.kepler.objectmanager.cache.CacheManager;
059import org.kepler.objectmanager.cache.CacheObject;
060import org.kepler.objectmanager.cache.ReportLayoutCacheObject;
061import org.kepler.objectmanager.lsid.KeplerLSID;
062import org.kepler.provenance.Queryable;
063import org.kepler.reporting.rio.ReportInstance;
064import org.kepler.reporting.rio.fop.ReportRenderer;
065import org.kepler.reporting.rio.util.ProvenanceUtil;
066import org.kepler.reporting.roml.ReportLayout;
067import org.kepler.util.WorkflowRun;
068import org.kepler.workflow.WorkflowManager;
069import org.kepler.workflowrunmanager.WorkflowRunManager;
070import org.kepler.workflowrunmanager.WorkflowRunManagerManager;
071
072import ptolemy.actor.gui.TableauFrame;
073import ptolemy.kernel.CompositeEntity;
074import ptolemy.kernel.util.IllegalActionException;
075import ptolemy.kernel.util.NameDuplicationException;
076import ptolemy.kernel.util.NamedObj;
077
078/**
079 * @author Ben Leinfelder
080 * 
081 */
082public class ReportLayoutKAREntryHandler implements KAREntryHandler {
083
084        private static final Log log = LogFactory.getLog(ReportLayoutKAREntryHandler.class.getName());
085        private static final boolean isDebugging = log.isDebugEnabled();
086
087
088        public ReportLayoutKAREntryHandler() {
089        }
090
091        /**
092         * Method for backwards compatibility with KAR version 1.0 KAR version 2.0
093         * uses the binary class name as the type.
094         * 
095         * @see org.kepler.kar.KAREntryHandler#getTypeName()
096         */
097        public String getTypeName() {
098                return ReportLayout.class.getName();
099        }
100
101        public boolean handlesType(String typeName) {
102
103                //try string based comparison
104                if (typeName.equals(ReportLayout.class.getName())) {
105                        return true;
106                }
107                if (typeName.equals(ReportInstance.class.getName())) {
108                        return true;
109                }
110//              if (typeName.equals(WorkflowRun.class.getName())) {
111//                      return true;
112//              }
113                return false;
114        }
115
116        /*
117         * (non-Javadoc)
118         * 
119         * @see org.kepler.kar.KAREntryHandler#initialize()
120         */
121        public void initialize() {
122                if (isDebugging) {
123                        log.debug("initialize()");
124                }
125        }
126        
127        public boolean open(KARFile karFile, KAREntry entry, TableauFrame tableauFrame) throws Exception {
128                // look up the LSID for the entry, do we need it?
129                KeplerLSID reportLsid = entry.getLSID();
130                CacheManager cm = CacheManager.getInstance();
131                ReportLayoutCacheObject rlco = (ReportLayoutCacheObject) cm.getObject(reportLsid);
132                ReportLayout rl = null;
133                rlco = null; // force the read from the karfile BRL, 2009/12/14
134                if (rlco == null){ // try to get it out of the karFile
135                        rl = ReportRenderer.convertXML2Report(karFile.getInputStream(entry));
136                }
137                else{
138                        rl = (ReportLayout) rlco.getObject();
139                }
140                
141                KeplerLSID workflowLsid = rl.getWorkflowLSID();
142                
143                // the tableauFrame passed in is the old window - the one from
144                // which the open action was invoked. We want the newest
145                TableauFrame tf = WorkflowManager.getRankingTableauFrame();
146                WorkflowManager.getInstance().getWorkflow(tf, workflowLsid).addReportLayout(tf, rl);
147                
148                //try to refresh the UI
149                NamedObj namedObj = ObjectManager.getInstance().getObjectRevision(workflowLsid);
150                JLabel dummyComponent = new JLabel();
151                ReportingStateChangeEvent event = new ReportingStateChangeEvent(dummyComponent, ReportingStateChangeEvent.REPORT_LAYOUT_CHANGED, namedObj);
152                StateChangeMonitor.getInstance().notifyStateChange(event);
153                dummyComponent = null;
154                
155                return true;
156        }
157
158        /*
159         * (non-Javadoc)
160         * 
161         * @see org.kepler.kar.KAREntryHandler#open(java.util.jar.JarEntry)
162         */
163        public CacheObject cache(KARFile karFile, KAREntry entry) throws Exception {
164                if (isDebugging) {
165                        log.debug("cache(" + karFile.toString() + "," + entry.toString() + ")");
166                }
167                
168                // look up the LSID for the entry, do we need it?
169                KeplerLSID reportLsid = entry.getLSID();
170                
171                // reportLsid.getRevision()
172                
173                //create the reportLayout object
174                InputStream is = karFile.getInputStream(entry);
175                ReportLayout rl = ReportRenderer.convertXML2Report(is);
176
177                // giving both e.g. RIO.1.xml and Unnamed1_ROML.xml name "roml" like below 
178                // seems to cause bug#4969. instead use entry.getName() so they're different.
179                //ReportLayoutCacheObject rlco = new ReportLayoutCacheObject("roml", reportLsid);
180                ReportLayoutCacheObject rlco = new ReportLayoutCacheObject(entry.getName(), reportLsid);
181                rlco.setReportLayout(rl);
182                //rlco.write();
183                if (rlco == null) {
184                        log.error("ReportLayoutCacheObject is null");
185                }
186                
187                //set in the manager
188                ///KeplerLSID workflowLsid = rl.getWorkflowLSID();
189                
190//              WorkflowManager wm = WorkflowManager.getInstance();
191//              Workflow wf = wm.getWorkflow(workflowLsid);
192//              List<ReportLayout> layouts = wf.getReportLayouts();
193//              layouts.clear();
194//              layouts.add(rl);
195                
196                // cache object for reporting
197                return rlco;
198
199        }
200
201        /*
202         * (non-Javadoc)
203         * 
204         * @see
205         * org.kepler.kar.KAREntryHandler#save(org.kepler.objectmanager.lsid.KeplerLSID
206         * )
207         */
208        public Hashtable<KAREntry, InputStream> save(Vector<KeplerLSID> lsids, KeplerLSID karLsid,
209                        TableauFrame tableauFrame)
210                throws Exception {
211                if (isDebugging) log.debug("save("+lsids.toString()+", karLSID:" +karLsid.toString()+")");
212                
213                WorkflowRunManagerManager wrmm = WorkflowRunManagerManager.getInstance();
214                WorkflowRunManager wrm = wrmm.getWRM(tableauFrame);
215                
216                Hashtable<KAREntry, InputStream> items = new Hashtable<KAREntry, InputStream>();
217
218                for ( KeplerLSID lsid : lsids ) {
219                        // determine what we are saving, and call the appropriate method
220                        // no longer using OM to get the run here, Nov 03, 2010.
221                        NamedObj nObj = wrm.getRun(lsid);
222                        if (nObj == null){
223                                //log.debug("current status of ObjectManager");
224                                //ObjectManager.getInstance().printDebugInfo();
225                                log.debug("WorkflowRunManager.getRun("+lsid+") returned null, try to get from ObjectManager.getInstance().getObjectRevision("+lsid+")");
226                                nObj = ObjectManager.getInstance().getObjectRevision(lsid);
227                        }
228                        if (nObj == null){
229                                log.debug("nObj == null");
230                        }
231                        if (nObj instanceof WorkflowRun) {
232                                log.debug("nObj instanceof WorkflowRun");
233                                WorkflowRun run = (WorkflowRun) nObj;
234                                Hashtable<KAREntry,InputStream> wfRunLayouts = saveWorkflowRun(run, karLsid, tableauFrame);
235                                items.putAll(wfRunLayouts);
236                        } else if (nObj instanceof CompositeEntity) {
237                                log.debug("nObj instanceof CompositeEntity");
238                                // Must avoid putting both ROMLs into the KAR when it's a run-KAR since they 
239                                // (atm - r24489) both have the same LSID, and can therefore otherwise end 
240                                // up with the ROML that does not dependsOn the run in the KAR.
241                                if (wrm.getSelectedRunsForSave() == null || wrm.getSelectedRunsForSave().isEmpty()){
242                                        log.debug("calling saveReportLayout("+lsid+")");
243                                        Hashtable<KAREntry,InputStream> workflowLayouts = saveReportLayout(lsid);
244                                        items.putAll(workflowLayouts);
245                                }
246                        }
247                }
248                return items;
249                
250        }
251        
252        /**
253         * This method is intended for serializing Layouts that have been saved 
254         * with a particular workflow execution. It DOES use provenance for looking it up
255         * @param run
256         * @return
257         * @throws Exception
258         */
259        private Hashtable<KAREntry, InputStream> saveWorkflowRun(WorkflowRun run, KeplerLSID karLsid, 
260                        TableauFrame tableauFrame) throws Exception {
261                
262                Hashtable<KAREntry, InputStream> items = new Hashtable<KAREntry, InputStream>();
263                
264                if (run == null) {
265                        log.debug("WorkflowRun is null");
266                        return null;
267                }
268                
269                // Get the layout item[s] for the run
270                Hashtable<KAREntry, InputStream> layoutItems = this.getReportLayoutForWorkflowRun(run, tableauFrame);
271                items.putAll(layoutItems);
272                
273                return items;
274        }
275        
276
277        /**
278         * This is intended to be used when serializing a ROML along with its MOML
279         * This method does NOT use provenance to retrieve any information
280         * @param lsid for the Workflow (not the report layout)
281         * @return
282         * @throws Exception
283         */
284        private Hashtable<KAREntry, InputStream> saveReportLayout(KeplerLSID lsid)
285                        throws Exception {
286                
287                log.debug("saveReportLayout(" + lsid + ")");
288
289                Hashtable<KAREntry, InputStream> items = new Hashtable<KAREntry, InputStream>();
290
291                // Get the workflow from the object manager
292                NamedObj nObj = ObjectManager.getInstance().getObjectRevision(lsid);
293                if (nObj == null) {
294                        // there is no guarantee that ObjectManager will return an object for an lsid
295                        throw new Exception("Object not found.");
296                }
297                
298                String workflowName = nObj.getName();
299                
300                ReportLayout reportLayout = null;
301                KeplerLSID reportLayoutLsid = null;
302
303                try {
304                        // if we don't have a layout for this current LSID,
305                        // then we need to look up the last LSID used to link them up
306                        if (!WorkflowManager.getInstance().workflowExists(lsid)) {
307                                log.debug("workflow/layout does not exist for LSID=" + lsid);
308                                // use the last entry in the derived from list to find the most recent one
309                                NamedObjIdReferralList nObjIdList = NamedObjId.getIDListAttributeFor(nObj);
310                                List<KeplerLSID> referrals = nObjIdList.getReferrals();
311                                if (referrals != null && referrals.size() > 0) {
312                                        KeplerLSID oldLsid = referrals.get(0);
313                                        log.debug("looking up layout using older revision, oldLSID=" + oldLsid);
314
315                                        // look up the report layout - using old LSID
316                                        //TODO change to just passing in tableauFrame?
317                                        TableauFrame tf = WorkflowManager.getRankingTableauFrame();
318                                        reportLayout = WorkflowManager.getInstance().getWorkflow(tf, oldLsid).getReportLayout(tf);
319                                }
320                        } else {
321                                // look up the report layout - lsid should be golden now
322                                TableauFrame tf = WorkflowManager.getRankingTableauFrame();
323                                reportLayout = WorkflowManager.getInstance().getWorkflow(tf, lsid).getReportLayout(tf); 
324                        }
325                        
326                        // if reportLayout is null just return
327                        if (reportLayout == null || reportLayout.isBlank()){
328                                log.warn("reportLayout is null or blank, not saving an entry to KAR");
329                                return items;
330                        }
331                        
332                        // synch the LSID on the layout before saving
333                        reportLayout.setWorkflowLSID(lsid);
334                }
335                
336                
337                catch (Exception e) {
338                        log.error("Could not set LSID for ReportLayout: " + e.getMessage());
339                }
340
341                // Get the ROML as xml 
342                File xml = File.createTempFile("roml", ".xml");
343                xml.deleteOnExit();
344                ReportRenderer.convertReport2XML(reportLayout, xml);
345                
346                // make an entry for the roml
347                String entryName = workflowName + "_ROML.xml";
348                log.debug(entryName);
349                
350                // Make an entry for the layout
351                KAREntry entry = new KAREntry(entryName);
352                
353                // Set the LSID/type for the reportLayout entry
354                entry.setLSID(reportLayout.getLsid());
355                // reportLayout.getClass().getName() can return a ReportInstance on KAR open, refer to class
356                // instead of object
357                //entry.setType(reportLayout.getClass().getName());
358                entry.setType(ReportLayout.class.getName());
359                entry.addLsidDependency(reportLayout.getWorkflowLSID());
360
361                log.debug("adding entry to KAR: " + entryName);
362
363                items.put(
364                                entry, 
365                                new FileInputStream(xml));
366                
367                return items;
368
369        }
370
371        private Hashtable<KAREntry, InputStream> getReportLayoutForWorkflowRun(WorkflowRun run,
372                        TableauFrame tableauFrame) {
373                
374                Hashtable<KAREntry, InputStream> items = new Hashtable<KAREntry, InputStream>();
375                
376                // Get a queryable for the workflow that ran
377                Queryable queryable = null;
378                try {
379                        queryable = ProvenanceUtil.getQueryable(run.getWorkflowLSID(), tableauFrame);
380                } catch (Exception e3) {
381                        log.error("could not find queryable for the workflow: " + run.getWorkflowLSID());
382                        e3.printStackTrace();
383                }
384                
385                // Get the report layout from provenance
386                ReportLayout reportLayout = null;
387                byte[] romlXML = null;
388                Map<String, String> metadataMap = new HashMap<String, String>();
389                metadataMap.put("type", ReportLayout.class.getName());
390                List<byte[]> dataList = null;
391                try {
392                        dataList = queryable.getAssociatedDataForExecution(
393                                                        run.getExecId(), 
394                                                        metadataMap, 
395                                                        false);
396                        if (dataList != null && !dataList.isEmpty()) {
397                                romlXML = dataList.get(0);
398                                // only constructing this to get the layout LSID
399                                reportLayout = ReportRenderer.convertXML2Report(new ByteArrayInputStream(romlXML));
400                        }
401                        else {
402                                log.warn("no data file found for reportLayout, run= " + run.getExecId());
403                        }
404                } catch (Exception e2) {
405                        log.error("error looking up report layout XML: " + e2.getMessage());
406                        e2.printStackTrace();
407                }
408        
409                if (reportLayout == null) {
410                        return items;
411                }
412                
413                // Add the ROML XML
414                log.debug("Adding ReportLayout: " + reportLayout);
415                KAREntry entryROML= new KAREntry("ROML." + run.getExecId() + ".xml");
416                try {
417                        entryROML.setLSID( reportLayout.getLsid() );
418                        entryROML.setType( ReportLayout.class.getName() );
419                        entryROML.addLsidDependency( reportLayout.getWorkflowLSID() );
420                        entryROML.addLsidDependency( run.getExecLSID() );
421                        items.put(entryROML, new ByteArrayInputStream(romlXML));
422                } catch (Exception e1) {
423                        log.error("error adding ROML XML entry: " + e1.getMessage());
424                        e1.printStackTrace();
425                }
426                
427                return items;
428        }
429
430        /**
431         * A factory that creates a KAREntryHandler object.
432         * 
433         *@author aaron
434         */
435        public static class Factory extends KAREntryHandlerFactory {
436                /**
437                 * Create a factory with the given name and container.
438                 * 
439                 *@param container
440                 *            The container.
441                 *@param name
442                 *            The name of the entity.
443                 *@exception IllegalActionException
444                 *                If the container is incompatible with this attribute.
445                 *@exception NameDuplicationException
446                 *                If the name coincides with an attribute already in the
447                 *                container.
448                 */
449                public Factory(NamedObj container, String name)
450                                throws IllegalActionException, NameDuplicationException {
451                        super(container, name);
452                }
453
454                /**
455                 * Create a library pane that displays the given library of actors.
456                 * 
457                 * @return A new LibraryPaneTab that displays the library
458                 */
459                public KAREntryHandler createKAREntryHandler() {
460                        if (isDebugging)
461                                log.debug("createKAREntryHandler()");
462                        ReportLayoutKAREntryHandler rlkeh = new ReportLayoutKAREntryHandler();
463                        return rlkeh;
464                }
465        }
466
467}