001/* Utilities for JNLP aka Web Start 002 003 Copyright (c) 2002-2016 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 027 */ 028package ptolemy.actor.gui; 029 030import java.io.File; 031import java.io.FileNotFoundException; 032import java.io.IOException; 033import java.net.URI; 034import java.net.URISyntaxException; 035import java.net.URL; 036import java.nio.file.Files; 037import java.nio.file.Path; 038import java.util.ArrayList; 039import java.util.Arrays; 040import java.util.HashMap; 041import java.util.Map; 042 043import ptolemy.util.ClassUtilities; 044import ptolemy.util.FileUtilities; 045import ptolemy.util.StringUtilities; 046 047/////////////////////////////////////////////////////////////////// 048//// JNLPUtilities 049 050/** This class contains utilities for use with JNLP, aka Web Start. 051 052 <p>For more information about Web Start, see 053 <a href="http://www.oracle.com/technetwork/java/javase/tech/index-jsp-136112.html" target="_top"><code>http://www.oracle.com/technetwork/java/javase/tech/index-jsp-136112.html</code></a> 054 or <code>$PTII/doc/webStartHelp</code> 055 056 @author Christopher Hylands 057 @version $Id$ 058 @since Ptolemy II 2.0 059 @Pt.ProposedRating Red (cxh) 060 @Pt.AcceptedRating Red (cxh) 061 @see Configuration 062 */ 063public class JNLPUtilities { 064 /** Instances of this class cannot be created. 065 */ 066 private JNLPUtilities() { 067 } 068 069 /////////////////////////////////////////////////////////////////// 070 //// public methods //// 071 072 /** Canonicalize a jar URL. If the possibleJarURL argument is a 073 * jar URL (that is, it starts with 'jar:'), then convert any 074 * space characters to %20. If the possibleJarURL argument is 075 * not a jar URL, then return the possibleJarURL argument. 076 * @param possibleJarURL A URL that may or may not be a jar URL 077 * @return either the original possibleJarURL or a canonicalized 078 * jar URL 079 * @exception java.net.MalformedURLException If new URL() throws it. 080 */ 081 public static URL canonicalizeJarURL(URL possibleJarURL) 082 throws java.net.MalformedURLException { 083 // This method is needed so that under Web Start we are always 084 // referring to files like intro.htm with the same URL. 085 // The reason is that the Web Start under Windows is likely 086 // to be in c:/Documents and Settings/username 087 // so we want to always refer to the files with the same URL 088 // so as to avoid duplicate windows 089 if (possibleJarURL.toExternalForm().startsWith("jar:")) { 090 String possibleJarURLPath = StringUtilities 091 .substitute(possibleJarURL.toExternalForm(), " ", "%20"); 092 if (possibleJarURLPath.contains("..")) { 093 // A jar URL with a relative path. about:checkCompleteDemos will generate these. 094 String[] path = possibleJarURLPath.split("/"); 095 ArrayList<String> paths = new ArrayList(Arrays.asList(path)); 096 097 for (int j = 0; j < paths.size(); j++) { 098 // System.out.println(paths.size() + " paths.get(" + j + "): "+ paths.get(j) + " paths: " + paths); 099 if (paths.get(j).equals("..")) { 100 if (j > 0) { 101 //System.out.println(j-1 + " Removing: " + paths.get(j-1)); 102 paths.remove(j - 1); 103 } 104 //System.out.println(j-1 + "Removing: " + paths.get(j-1)); 105 paths.remove(j - 1); 106 j = j - 2; 107 } 108 } 109 StringBuffer newPath = new StringBuffer(); 110 for (String pathElement : paths) { 111 newPath.append(pathElement + "/"); 112 } 113 possibleJarURLPath = newPath.toString().substring(0, 114 newPath.length() - 1); 115 //System.out.println("JNLPUtilities: possibleJarURLPath: " + possibleJarURLPath); 116 try { 117 URL jarURL = ClassUtilities 118 .jarURLEntryResource(possibleJarURLPath); 119 //System.out.println("JNLPUtilities: jarURL: " + jarURL); 120 return jarURL; 121 } catch (IOException ex) { 122 throw new java.net.MalformedURLException(ex.toString()); 123 } 124 } 125 126 // FIXME: Could it be that we only want to convert spaces before 127 // the '!/' string? 128 URL jarURL = new URL(possibleJarURLPath); 129 //System.out.println("JNLPUtilities: 2 jarURL: " + jarURL); 130 // FIXME: should we check to see if the jarURL exists here? 131 // if (jarURL == null) { 132 // try { 133 // return ClassUtilities 134 // .jarURLEntryResource(possibleJarURLPath); 135 // } catch (IOException ex) { 136 // throw new java.net.MalformedURLException(ex.toString()); 137 // } 138 // } 139 return jarURL; 140 } 141 142 return possibleJarURL; 143 } 144 145 /** Get the resource, if it is in a jar URL, then 146 * copy the resource to a temporary file first. 147 * 148 * If the file is copied to a temporary location, then 149 * it is deleted when the process exits. 150 * 151 * This method is used when jar URLs are not 152 * able to be read in by a function call. 153 * 154 * If the spec refers to a URL that is a directory, 155 * then the possibly shortened spec is returned 156 * with a trailing /. No temporary directory 157 * is created. 158 * 159 * @param spec The string to be found as a resource. 160 * @return The File. 161 * @exception IOException If the jar URL cannot be saved as a temporary file. 162 */ 163 public static File getResourceSaveJarURLAsTempFile(String spec) 164 throws IOException { 165 // System.out.println("JNLPUtilities.g.r.s.j.u.a.t.f(): start spec: " + spec); 166 // If the spec is not a jar URL, then check in file system. 167 // This method is used by CapeCode to find .js file resources with require(). 168 int jarSeparatorIndex = spec.indexOf("!" + File.separator); 169 File results = null; 170 if (jarSeparatorIndex == -1) { 171 results = new File(spec); 172 if (results.exists()) { 173 // System.out.println("JNLPUtilities.g.r.s.j.u.a.t.f(): start spec: " + spec + " 0 return: " + results); 174 return results; 175 } 176 } else { 177 // Strip off the text leading up to !/ or !\ 178 spec = spec.substring(jarSeparatorIndex + 2); 179 } 180 181 // If the resources is not found at all, return null. 182 URL url = ClassUtilities.getResource(spec); 183 if (url == null) { 184 // System.out.println("JNLPUtilities.g.r.s.j.u.a.t.f(): start spec: " + spec + " 0.5"); 185 // Windows, getResource() on spec with backslashes causes problems. 186 String spec2 = spec.replace("\\", "/"); 187 url = ClassUtilities.getResource(spec2); 188 if (url == null) { 189 // If we are trying to read something with a path like ./decode.js, then check the _lastSpec 190 if (spec.startsWith("." + File.separator) 191 && _lastSpec != null) { 192 String parentLastSpec = _lastSpec.substring(0, 193 _lastSpec.lastIndexOf(File.separator) + 1); 194 return getResourceSaveJarURLAsTempFile( 195 parentLastSpec + spec.substring(2)); 196 } 197 return null; 198 } 199 } 200 201 results = null; 202 203 // For jar urls, copy the file to a temporary 204 // location that is removed when the process exits. 205 if (url.toExternalForm().startsWith("jar:")) { 206 // If we have already seen the url, then return 207 // what was returned last time 208 try { 209 // We use a map of URIs because FindBugs reports: 210 // "Dm: Maps and sets of URLs can be performance hogs (DMI_COLLECTION_OF_URLS)" 211 // See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html 212 if (_jarURITemporaryFiles != null 213 && _jarURITemporaryFiles.containsKey(url.toURI())) { 214 results = _jarURITemporaryFiles.get(url.toURI()); 215 _lastSpec = spec; 216 // System.out.println("JNLPUtilities.g.r.s.j.u.a.t.f(): start spec: " + spec + " 1 return: " + results); 217 return results; 218 } 219 } catch (URISyntaxException ex) { 220 IOException ioException = new IOException( 221 "Failed to look up " + url + " in the cache."); 222 ioException.initCause(ex); 223 throw ioException; 224 } 225 String prefix = ""; 226 String suffix = ""; 227 int lastIndexOfSlash = spec.lastIndexOf(File.separator); 228 int lastIndexOfDot = spec.lastIndexOf("."); 229 if (lastIndexOfSlash == -1) { 230 if (lastIndexOfDot == -1) { 231 prefix = spec; 232 } else { 233 prefix = spec.substring(0, lastIndexOfDot); 234 suffix = "." + spec.substring(lastIndexOfDot + 1); 235 } 236 } else { 237 if (lastIndexOfDot == -1) { 238 prefix = spec.substring(lastIndexOfSlash + 1); 239 } else { 240 prefix = spec.substring(lastIndexOfSlash + 1, 241 lastIndexOfDot); 242 suffix = "." + spec.substring(lastIndexOfDot + 1); 243 } 244 } 245 try { 246 String temporaryFileName = saveJarURLAsTempFile(url.toString(), 247 prefix, suffix, null /*directory*/); 248 results = new File(temporaryFileName); 249 // System.out.println("JNLPUtilities.g.r.s.j.u.a.t.f(): start spec: " + spec + " 1.5 reslts: " + results + " exists: " + results.exists()); 250 } catch (IOException ex) { 251 // If the spec exists with a trailing / or \ , then just 252 // return that so that we can detect that it is a 253 // directory. FIXME: the directory is not actually 254 // created here, which could be confusing. 255 if (spec.length() > 0 && spec 256 .charAt(spec.length() - 1) != File.separatorChar) { 257 URL urlDirectory = ClassUtilities 258 .getResource(spec + File.separator); 259 if (urlDirectory != null) { 260 results = new File(spec + File.separator); 261 } 262 } else { 263 results = null; 264 } 265 } 266 if (_jarURITemporaryFiles == null) { 267 _jarURITemporaryFiles = new HashMap<URI, File>(); 268 } 269 try { 270 _jarURITemporaryFiles.put(url.toURI(), results); 271 } catch (URISyntaxException ex) { 272 IOException ioException = new IOException( 273 "Failed to add " + url + " in the cache."); 274 ioException.initCause(ex); 275 throw ioException; 276 } 277 _lastSpec = spec; 278 // System.out.println("JNLPUtilities.g.r.s.j.u.a.t.f(): start spec: " + spec + " 2 return: " + results); 279 return results; 280 } else { 281 // If the resource is not a jar URL, try 282 // creating a file. 283 try { 284 results = new File(url.toURI()); 285 } catch (URISyntaxException e) { 286 results = new File(url.getPath()); 287 } catch (IllegalArgumentException e) { 288 results = new File(url.getPath()); 289 } 290 // System.out.println("JNLPUtilities.g.r.s.j.u.a.t.f(): start spec: " + spec + " 3 return: " + results); 291 return results; 292 } 293 } 294 295 /** Return true if we are running under WebStart. 296 * @return True if we are running under WebStart. 297 */ 298 public static boolean isRunningUnderWebStart() { 299 try { 300 // NOTE: getProperty() will probably fail in applets, which 301 // is why this is in a try block. 302 String javaWebStart = System.getProperty("javawebstart.version"); 303 304 if (javaWebStart != null) { 305 return true; 306 } 307 } catch (SecurityException security) { 308 // Ignored 309 } 310 311 return false; 312 } 313 314 /** Given a jar url of the format jar:{url}!/{entry}, return 315 * the resource, if any of the {entry}. 316 * If the string does not contain <code>!/</code>, then return 317 * null. Web Start uses jar URL, and there are some cases where 318 * if we have a jar URL, then we may need to strip off the 319 * <code>jar:<i>url</i>!/</code> part so that we can search for 320 * the {entry} as a resource. 321 * 322 * @param spec The string containing the jar url. 323 * @exception IOException If it cannot convert the specification to 324 * a URL. 325 * @return the resource if any. 326 * @deprecated Use ptolemy.util.ClassUtilities#jarURLEntryResource(String) 327 * @see ptolemy.util.ClassUtilities#jarURLEntryResource(String) 328 * @see java.net.JarURLConnection 329 */ 330 @Deprecated 331 public static URL jarURLEntryResource(String spec) throws IOException { 332 return ClassUtilities.jarURLEntryResource(spec); 333 } 334 335 /** Given a jar URL, read in the resource and save it as a file. 336 * The file is created using the prefix and suffix in the 337 * directory referred to by the directory argument. If the 338 * directory argument is null, then it is saved in the platform 339 * dependent temporary directory. 340 * The file is deleted upon exit. 341 * @see java.io.File#createTempFile(java.lang.String, java.lang.String, java.io.File) 342 * @param jarURLName The name of the jar URL to read. jar URLS start 343 * with "jar:" and have a "!/" in them. 344 * @param prefix The prefix used to generate the name, it must be 345 * at least three characters long. 346 * @param suffix The suffix to use to generate the name. If the 347 * suffix is null, then the suffix of the jarURLName is used. If 348 * the jarURLName does not contain a ".", then ".tmp" will be used 349 * @param directory The directory where the temporary file is 350 * created. If directory is null then the platform dependent 351 * temporary directory is used. 352 * @return the name of the temporary file that was created 353 * @exception IOException If there is a problem saving the jar URL. 354 */ 355 public static String saveJarURLAsTempFile(String jarURLName, String prefix, 356 String suffix, File directory) throws IOException { 357 URL jarURL = _lookupJarURL(jarURLName); 358 jarURLName = jarURL.toString(); 359 360 // File.createTempFile() does the bulk of the work for us, 361 // we just check to see if suffix is null, and if it is, 362 // get the suffix from the jarURLName. 363 if (suffix == null) { 364 // If the jarURLName does not contain a ".", then we pass 365 // suffix = null to File.createTempFile(), which defaults 366 // to ".tmp" 367 if (jarURLName.lastIndexOf('.') != -1) { 368 suffix = jarURLName.substring(jarURLName.lastIndexOf('.')); 369 } 370 } 371 372 File temporaryFile = null; 373 try { 374 375 temporaryFile = File.createTempFile(prefix, suffix, directory); 376 } catch (Exception ex) { 377 throw new IOException("While trying to save a jar URL \"" 378 + jarURLName 379 + "\", failed to create temporary file with prefix: \"" 380 + prefix + "\", suffix: \"" + suffix + "\", directory: \"" 381 + directory); 382 } 383 temporaryFile.deleteOnExit(); 384 385 try { 386 // The resource pointed to might be a pdf file, which 387 // is binary, so we are careful to read it byte by 388 // byte and not do any conversions of the bytes. 389 FileUtilities.binaryCopyURLToFile(jarURL, temporaryFile); 390 } catch (Throwable throwable) { 391 // Hmm, jarURL could be referring to a directory. 392 if (temporaryFile.delete()) { 393 throw new IOException("Copying \"" + jarURL + "\" to \"" 394 + temporaryFile + "\" failed: " + throwable 395 + " Then deleting \"" + temporaryFile + "\" failed?"); 396 } 397 Path directoryPath = directory.toPath(); 398 Path temporaryDirectory = Files.createTempDirectory(directoryPath, 399 prefix); 400 temporaryFile = temporaryDirectory.toFile(); 401 temporaryFile.deleteOnExit(); 402 FileUtilities.binaryCopyURLToFile(jarURL, temporaryFile); 403 } 404 return temporaryFile.toString(); 405 } 406 407 /** Given a jar URL, read in the resource and save it as a file in 408 * a similar directory in the classpath if possible. In this 409 * context, by similar directory, we mean the directory where 410 * the file would found if it was not in the jar url. 411 * For example, if the jar url is 412 * jar:file:/ptII/doc/design.jar!/doc/design/design.pdf 413 * then this method will read design.pdf from design.jar 414 * and save it as /ptII/doc/design.pdf. 415 * 416 * @param jarURLName The name of the jar URL to read. jar URLS start 417 * with "jar:" and have a "!/" in them. 418 * @return the name of the file that was created or 419 * null if the file cannot be created 420 * @exception IOException If there is a problem saving the jar URL. 421 */ 422 public static String saveJarURLInClassPath(String jarURLName) 423 throws IOException { 424 URL jarURL = _lookupJarURL(jarURLName); 425 jarURLName = jarURL.toString(); 426 427 int jarSeparatorIndex = jarURLName.indexOf("!/"); 428 429 if (jarSeparatorIndex == -1) { 430 // Could be that we found a copy of the file in the classpath. 431 return jarURLName; 432 } 433 434 // If the entry directory matches the jarURL directory, then 435 // write out the file in the proper location. 436 String jarURLFileName = jarURLName.substring(0, jarSeparatorIndex); 437 String entryFileName = jarURLName.substring(jarSeparatorIndex + 2); 438 439 // We assume / is the file separator here because URLs 440 // _BY_DEFINITION_ have / as a separator and not the Microsoft 441 // non-conforming hack of using a backslash. 442 String jarURLParentFileName = jarURLFileName.substring(0, 443 jarURLFileName.lastIndexOf("/")); 444 445 String parentEntryFileName = entryFileName.substring(0, 446 entryFileName.lastIndexOf("/")); 447 448 if (jarURLParentFileName.endsWith(parentEntryFileName) 449 && jarURLParentFileName.startsWith("jar:file:/")) { 450 // The top level directory, probably $PTII 451 String jarURLTop = jarURLParentFileName.substring(9, 452 jarURLParentFileName.length() 453 - parentEntryFileName.length()); 454 455 File temporaryFile = new File(jarURLTop, entryFileName); 456 457 // If the file exists, we assume that it is the right one. 458 // FIXME: we could do more here, like check for file sizes. 459 if (!temporaryFile.exists()) { 460 FileUtilities.binaryCopyURLToFile(jarURL, temporaryFile); 461 } 462 463 return temporaryFile.toString(); 464 } 465 466 return null; 467 } 468 469 /////////////////////////////////////////////////////////////////// 470 //// private methods //// 471 // Lookup a jarURLName as a resource. 472 private static URL _lookupJarURL(String jarURLName) throws IOException { 473 // We call jarURLEntryResource here so that we get a URL 474 // that has the right jar file associated with the right entry. 475 URL jarURL = jarURLEntryResource(jarURLName); 476 477 if (jarURL == null) { 478 jarURL = ClassUtilities.getResource(jarURLName); 479 } 480 481 if (jarURL == null) { 482 throw new FileNotFoundException( 483 "Could not find '" + jarURLName + "'"); 484 } 485 486 return jarURL; 487 } 488 489 /////////////////////////////////////////////////////////////////// 490 //// private variables //// 491 492 /** The map of URIs to Files used by 493 * getResourceSaveJarURIAsTempFile(). 494 */ 495 private static Map<URI, File> _jarURITemporaryFiles; 496 497 private static String _lastSpec = null; 498}