001/**
002 *  '$RCSfile$'
003 *  '$Author: crawl $'
004 *  '$Date: 2015-07-02 19:17:40 +0000 (Thu, 02 Jul 2015) $'
005 *  '$Revision: 33521 $'
006 *
007 *  For Details:
008 *  http://www.kepler-project.org
009 *
010 *  Copyright (c) 2010 The Regents of the
011 *  University of California. All rights reserved. Permission is hereby granted,
012 *  without written agreement and without license or royalty fees, to use, copy,
013 *  modify, and distribute this software and its documentation for any purpose,
014 *  provided that the above copyright notice and the following two paragraphs
015 *  appear in all copies of this software. IN NO EVENT SHALL THE UNIVERSITY OF
016 *  CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL,
017 *  OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
018 *  DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE
019 *  POSSIBILITY OF SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY
020 *  DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 *  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
022 *  SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
023 *  CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
024 *  ENHANCEMENTS, OR MODIFICATIONS.
025 */
026
027package org.kepler.kar;
028
029import java.io.ByteArrayInputStream;
030import java.io.File;
031import java.io.FileOutputStream;
032import java.io.IOException;
033import java.io.InputStream;
034import java.util.Collection;
035import java.util.Hashtable;
036import java.util.Iterator;
037import java.util.LinkedHashMap;
038import java.util.Vector;
039import java.util.jar.Attributes;
040import java.util.jar.Attributes.Name;
041import java.util.jar.JarOutputStream;
042
043import org.apache.commons.logging.Log;
044import org.apache.commons.logging.LogFactory;
045import org.kepler.kar.handlers.ActorMetadataKAREntryHandler;
046import org.kepler.moml.NamedObjId;
047import org.kepler.objectmanager.ActorMetadata;
048import org.kepler.objectmanager.ObjectManager;
049import org.kepler.objectmanager.cache.CacheManager;
050import org.kepler.objectmanager.lsid.KeplerLSID;
051import org.kepler.objectmanager.lsid.LSIDGenerator;
052
053import ptolemy.actor.gui.TableauFrame;
054import ptolemy.kernel.util.IllegalActionException;
055import ptolemy.kernel.util.NamedObj;
056
057/**
058 * Class to create KAR files from within kepler
059 */
060/**
061 * @author Aaron Schultz
062 * 
063 */
064public class KARBuilder {
065
066        public static final String KAR_LSID_ATTRIBUTE_NAME = "karLSID";
067
068        private static final Log log = LogFactory
069                        .getLog(KARBuilder.class.getName());
070        private static final boolean isDebugging = log.isDebugEnabled();
071
072
073        /**
074         * The save initiator list is the set of ComponentEntity objects to be saved
075         * into the KAR.
076         */
077        private Vector<NamedObj> _saveInitiatorList;
078
079        /*
080         * true if you want new lsids registered with the cache. If you are going to
081         * eventually cache the created kar file, set registerLSID to false.
082         */
083        private boolean _registerLSID;
084
085        private boolean _revision;
086
087        private KeplerLSID _karLSID;
088        private File _karFile;
089        
090        private LinkedHashMap<KAREntry, InputStream> _karItems;
091        private Vector<KeplerLSID> _karItemLSIDs;
092        private Vector<String> _karItemNames;
093        private KARManifest _manifest;
094
095        /**
096         * Empty Constructor for building a KAR file.
097         */
098        public KARBuilder() {
099
100                // initialize the defaults for private variables
101                _saveInitiatorList = new Vector<NamedObj>();
102                
103                _karItems = new LinkedHashMap<KAREntry, InputStream>();
104                _karItemLSIDs = new Vector<KeplerLSID>();
105                _karItemNames = new Vector<String>();
106                _manifest = new KARManifest();
107
108                _karLSID = null;
109                _karFile = null;
110
111                _revision = false;
112                _registerLSID = false;
113        }
114
115        public Vector<NamedObj> getSaveInitiatorList() {
116                return _saveInitiatorList;
117        }
118
119        /**
120         * Add the namedObj to the _saveInitiatorList as well as into the ObjectManager.
121         * @param namedObj
122         */
123        public void addSaveInitiator(NamedObj namedObj) {
124                _saveInitiatorList.add(namedObj);
125                
126                // XXX KAREntryHandlers may need to access these from the ObjectManager
127                // e.g. ReportLayoutKAREntryHandler.save does.
128                try {
129                        ObjectManager.getInstance().addNamedObj(namedObj);
130                } catch (Exception e) {
131                        e.printStackTrace();
132                }
133        }
134
135        public File getKarFile() {
136                return _karFile;
137        }
138
139        public void setKarFile(File karFile) {
140                _karFile = karFile;
141        }
142
143        public boolean isRevision() {
144                return _revision;
145        }
146
147        public void setRevision(boolean revision) {
148                _revision = revision;
149        }
150
151        public boolean isRegisterLSID() {
152                return _registerLSID;
153        }
154
155        public void setRegisterLSID(boolean registerLSID) {
156                _registerLSID = registerLSID;
157        }
158
159        public KeplerLSID getKarLSID() {
160                return _karLSID;
161        }
162
163        public void setKarLSID(KeplerLSID karLSID) {
164                _karLSID = karLSID;
165        }
166
167        public KARManifest getManifest() {
168                return _manifest;
169        }
170
171        public void setManifest(KARManifest manifest) {
172                _manifest = manifest;
173        }
174
175        /**
176         * Handle the creation of the KAREntry objects for the Save Initiator List.
177         * 
178         * @return Hashtable<KAREntry, InputStream>
179         */
180        public Hashtable<KAREntry, InputStream> handleInitiatorList() {
181
182                Hashtable<KAREntry, InputStream> items = new Hashtable<KAREntry, InputStream>();
183
184                for (NamedObj namedObj : _saveInitiatorList) {
185
186                        try {
187
188                                String objType = namedObj.getClass().getName();
189                                KeplerLSID lsid = NamedObjId.getIdFor(namedObj);
190
191                                ActorMetadata aMet = new ActorMetadata(namedObj);
192
193                                aMet.setName(namedObj.getName());
194                                aMet.setId(lsid.toString());
195
196                                String actorFilename = namedObj.getName() + "."
197                                                + lsid.createFilename() + ".xml";
198                                if (isDebugging)
199                                        log.debug(actorFilename);
200
201                                KAREntry entry = new KAREntry(actorFilename);
202                                entry.setLSID(lsid);
203                                entry.setType(objType);
204                                entry.setHandler(ActorMetadataKAREntryHandler.class.getName());
205
206                                String actorMetadataString = aMet.toString();
207                                byte[] actorMetadataBytes = actorMetadataString.getBytes();
208                                ByteArrayInputStream byteArrayIS = new ByteArrayInputStream(
209                                                actorMetadataBytes);
210
211                                items.put(entry, byteArrayIS);
212
213                        } catch (Exception e) {
214                                e.printStackTrace();
215                        }
216                }
217
218                return items;
219        }
220
221        /**
222         * 
223         * @param entries
224         * @return
225         */
226        public Vector<KeplerLSID> getKAREntryLSIDs(
227                        Hashtable<KAREntry, InputStream> entries) {
228
229                Vector<KeplerLSID> lsids = new Vector<KeplerLSID>();
230                for (KAREntry entry : entries.keySet()) {
231                        KeplerLSID lsid = entry.getLSID();
232                        lsids.add(lsid);
233                }
234                return lsids;
235        }
236
237        private Hashtable<KAREntry, InputStream> queryKAREntryHandlers(
238                        Vector<KeplerLSID> lsidsOfEntriesReturnedFromPreviousIteration, TableauFrame tableauFrame) 
239                        throws Exception {
240
241                Hashtable<KAREntry, InputStream> entriesForThisIteration = new Hashtable<KAREntry, InputStream>();
242
243                Collection<KAREntryHandler> allHandlers = CacheManager.getInstance()
244                                .getKAREntryHandlers();
245
246                // Loop through the KAREntryHandlers
247                for (KAREntryHandler keh : allHandlers) {
248                        if (isDebugging)
249                                log.debug(keh.getClass().getName());
250
251                        // Get the KAREntries from each handler
252                        Hashtable<KAREntry, InputStream> entries = keh.save(
253                                        lsidsOfEntriesReturnedFromPreviousIteration, _karLSID, tableauFrame);
254                        if (entries != null) {
255                                for (KAREntry entry : entries.keySet()) {
256                                        entry.setHandler(keh.getClass().getName());
257                                        entriesForThisIteration.put(entry, entries.get(entry));
258                                }
259                        }
260                }
261
262                return entriesForThisIteration;
263
264        }
265        
266        private void removeDuplicateKAREntries() throws Exception {
267
268                // now remove any "duplicate" karentries where duplicate
269                // defined as same name and lsid.
270                // see bug#4555. We may remove this in the future, where
271                // such dupes might be allowed
272                // (e.g. same name + lsid but in different subdirs in kar).
273                Hashtable<String, String> nameMap = new Hashtable<String, String>();
274                for (KAREntry ke : _karItems.keySet()) {
275                        String name = ke.getName();
276                        String itemLsid = ke.getAttributes().getValue(
277                                        KAREntry.LSID);
278                        if (nameMap.containsKey(name)) {
279                                if (nameMap.get(name).equals(itemLsid)) {
280                                        _karItemLSIDs.remove(ke.getLSID());
281                                        _karItemNames.remove(ke.getName());
282                                        _karItems.remove(ke);
283                                }
284                        }
285                        nameMap.put(name, itemLsid);
286                }
287        }
288        
289        private void addEntriesToPrivateItems(Hashtable<KAREntry, InputStream> entries) {
290                if (isDebugging) log.debug("addEntriesToPrivateItems("+entries.size()+")");
291                
292                for (KAREntry karEntryKey : entries.keySet()) {
293                        _karItems.put(karEntryKey, entries
294                                        .get(karEntryKey));
295                        _karItemLSIDs.add(karEntryKey.getLSID());
296                        _karItemNames.add(karEntryKey.getName());
297                }
298                
299        }
300
301        public void generateKAR(TableauFrame tableauFrame, String overrideModDeps) throws IllegalActionException {
302                if (isDebugging) log.debug("generateKAR()");
303
304                if (_karLSID == null) {
305                        try {
306                                _karLSID = LSIDGenerator.getInstance().getNewLSID();
307                        } catch (Exception e) {
308                                log.error("could not generate new LSID for KAR: "
309                                                + e.getMessage());
310                                e.printStackTrace();
311                        }
312                }
313
314                try {
315
316                        // Get KAREntries for the Save Initiator List
317                        Hashtable<KAREntry, InputStream> initiatorEntries = handleInitiatorList();
318                        addEntriesToPrivateItems(initiatorEntries);
319                        
320                        int pass = 1;
321
322                        // Loop through KAR Entry handlers until no more KAREntry objects
323                        // are returned
324                        Vector<KeplerLSID> previousPassEntryLSIDs = getKAREntryLSIDs(initiatorEntries);
325                        if (isDebugging) 
326                                log.debug("Pass " + pass + " entries: " + previousPassEntryLSIDs.toString());
327                        while (previousPassEntryLSIDs.size() > 0) {
328                                pass++;
329                                
330                                // Get the KAREntries from all of the handlers
331                                Hashtable<KAREntry, InputStream> entries = 
332                                        queryKAREntryHandlers(previousPassEntryLSIDs, tableauFrame);
333                                if (entries != null) {
334
335                                        previousPassEntryLSIDs.removeAllElements();
336                                        if (isDebugging) 
337                                                log.debug("Pass " + pass + " entries: ");
338                                        Vector<KeplerLSID> repeats = new Vector<KeplerLSID>();
339                                        for (KAREntry karEntryKey : entries.keySet()) {
340                                                String entryName = karEntryKey.getName();
341                                                String entryType = karEntryKey.getType();
342                                                KeplerLSID entryLSID = karEntryKey.getLSID();
343                                                if (isDebugging) 
344                                                        log.debug( entryName + "  " + entryLSID + "  " + entryType );
345                                                if (_karItemLSIDs.contains(entryLSID)) {
346                                                        // TODO make sure existing Entry Handlers do not produce repeated LSIDs.
347                                                        // This should never happen.
348                                                        System.out.println("KARBuilder generateKAR() Trying to add "+ entryName + " with " +
349                                                                        "type:"+entryType+" but an entry with lsid:" + entryLSID + " has already " +
350                                                                        "been added to KAR. Will NOT add this entry.");
351                                                        repeats.add(entryLSID);
352                                                } else if (_karItemNames.contains(entryName)) {
353                                                        // TODO make sure existing Entry Handlers do not produce repeated LSIDs.
354                                                        // This should never happen.
355                                                        System.out.println("KARBuilder generateKAR() An entry with entryName"+ entryName + 
356                                                                        " has already been added to KAR. Will NOT add this entry with lsid:" 
357                                                                        + entryLSID);
358                                                        repeats.add(entryLSID);
359                                                } else {
360                                                        previousPassEntryLSIDs.add(entryLSID);
361                                                }
362                                        }
363                                        // A kludge to protect against entry handlers returning entries that have already been added
364                                        for (KeplerLSID repeatedLSID : repeats) {
365                                                for (KAREntry ke : entries.keySet()) {
366                                                        if (ke.getLSID().equals(repeatedLSID)) {
367                                                                entries.remove(ke);
368                                                                if (isDebugging) log.debug("Removed " + repeatedLSID + " from pass " + pass + " entries" );
369                                                                break;
370                                                        }
371                                                }
372                                        }
373                                        
374                                        addEntriesToPrivateItems(entries);
375                                }
376                        }
377
378                        prepareManifest(overrideModDeps);
379
380                        writeKARFile();
381                        
382                } catch (Exception e) {
383                    e.printStackTrace();
384                        throw new IllegalActionException("Error building the KAR file.: "
385                                        + e.getMessage());
386                }
387
388        }
389
390        /**
391         * Prepare the KAR Manifest based on the kar items.
392         *
393         * @param overrideModDeps - Optional override of kar's module dependencies, 
394         * set null for normal use.
395         * @throws Exception
396         */
397        private void prepareManifest(String overrideModDeps) throws Exception {
398
399                _manifest
400                                .addMainAttribute(KARFile.LSID.toString(), _karLSID.toString());
401                if (overrideModDeps == null){
402                        _manifest.addMainAttribute(KARFile.MOD_DEPEND.toString(),
403                                ModuleDependencyUtil.buildModuleDependenciesString());
404                }
405                else{
406                        //System.out.println("KARBuilder prepareManifest using overrideModDeps:"+overrideModDeps);
407                        _manifest.addMainAttribute(KARFile.MOD_DEPEND.toString(),
408                                        overrideModDeps);
409                }
410
411                // add all the KAREntry attributes to the KARManifest
412                Vector<KAREntry> toRemove = new Vector<KAREntry>(1);
413                for (KAREntry ke : _karItems.keySet()) {
414
415                        Attributes atts = ke.getAttributes();
416                        
417                        // Check required attributes
418                        String entryLSID = atts.getValue(KAREntry.LSID);
419                        String entryType = atts.getValue(KAREntry.TYPE);
420                        String entryHandler = atts.getValue(KAREntry.HANDLER);
421
422                        if (entryLSID == null || entryType == null || entryHandler == null) {
423                                log
424                                                .warn(ke.getName()
425                                                                + " KAREntry did not have an LSID, Type, or Handler attribute.  KAREntry removed.");
426                                toRemove.add(ke);
427
428                        } else {
429                                
430                                // add all attributes of the karentry to the manifest
431                                for (Object att : atts.keySet()) {
432                                        if (att instanceof Name) {
433                                                Name attName = (Name) att;
434                                                String attValue = atts.getValue(attName);
435                                                _manifest.addEntryAttribute(ke, attName.toString(),
436                                                                attValue);
437                                        }
438                                }
439                        }
440                }
441                if (toRemove.size() > 0) {
442
443                        for (KAREntry ke : toRemove) {
444                                _karItemLSIDs.remove(ke.getLSID());
445                                _karItems.remove(ke);
446                        }
447
448                }
449
450        }
451
452        /**
453         *
454         */
455        private void writeKARFile() throws IOException {
456
457                JarOutputStream jos = new JarOutputStream(
458                                new FileOutputStream(_karFile), _manifest);
459                Iterator<KAREntry> li = _karItems.keySet().iterator();
460                while (li.hasNext()) {
461                        KAREntry entry = (KAREntry) li.next();
462                        if (isDebugging) log.debug("Writing " + entry.getName());
463                        try {
464                                jos.putNextEntry(entry);
465        
466                                if (_karItems.get(entry) instanceof InputStream) {
467                                        // inputstream from a bin file
468                                        byte[] b = new byte[1024];
469                                        InputStream is = (InputStream) _karItems.get(entry);
470                                        int numread = is.read(b, 0, 1024);
471                                        while (numread != -1) {
472                                                jos.write(b, 0, numread);
473                                                numread = is.read(b, 0, 1024);
474                                        }
475                                        is.close();
476                                        // jos.flush();
477                                        jos.closeEntry();
478                                }
479                        } catch (IOException ioe) {
480                                log.error(" Tried to write Duplicate Entry to kar " + entry.getName() + " " + entry.getLSID());
481                                ioe.printStackTrace();
482                        }
483                }
484                jos.flush();
485                jos.close();
486
487                log.info("done writing KAR file to "
488                                + _karFile.getAbsolutePath());
489        }
490
491}