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}