1 /* |
|
2 * Copyright (c) 2008, 2009, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. Oracle designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Oracle in the LICENSE file that accompanied this code. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 * version 2 for more details (a copy is included in the LICENSE file that |
|
15 * accompanied this code). |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License version |
|
18 * 2 along with this work; if not, write to the Free Software Foundation, |
|
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 * |
|
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 * or visit www.oracle.com if you need additional information or have any |
|
23 * questions. |
|
24 */ |
|
25 package sun.jkernel; |
|
26 |
|
27 import java.io.*; |
|
28 import java.net.HttpRetryException; |
|
29 import java.util.*; |
|
30 import java.util.concurrent.*; |
|
31 import java.util.jar.*; |
|
32 import java.util.zip.GZIPInputStream; |
|
33 |
|
34 /** |
|
35 * Represents a bundle which may or may not currently be installed. |
|
36 * |
|
37 *@author Ethan Nicholas |
|
38 */ |
|
39 public class Bundle { |
|
40 static { |
|
41 if (!DownloadManager.jkernelLibLoaded) { |
|
42 // This code can be invoked directly by the deploy build. |
|
43 System.loadLibrary("jkernel"); |
|
44 } |
|
45 } |
|
46 /** |
|
47 * Compress file sourcePath with "extra" algorithm (e.g. 7-Zip LZMA) |
|
48 * if available, put the uncompressed data into file destPath and |
|
49 * return true. If not available return false and do nothing with destPath. |
|
50 * |
|
51 * @param srcPath path to existing uncompressed file |
|
52 * @param destPath path for the compressed file to be created |
|
53 * @returns true if extra algorithm used, false if not |
|
54 * @throws IOException if the extra compression code should be available |
|
55 * but cannot be located or linked to, the destination file already |
|
56 * exists or cannot be opened for writing, or the compression fails |
|
57 */ |
|
58 public static native boolean extraCompress(String srcPath, |
|
59 String destPath) throws IOException; |
|
60 |
|
61 /** |
|
62 * Decompress file sourcePath with "extra" algorithm (e.g. 7-Zip LZMA) |
|
63 * if available, put the uncompressed data into file destPath and |
|
64 * return true. If not available return false and do nothing with |
|
65 * destPath. |
|
66 * @param srcPath path to existing compressed file |
|
67 * @param destPath path to uncompressed file to be created |
|
68 * @returns true if extra algorithm used, false if not |
|
69 * @throws IOException if the extra uncompression code should be available |
|
70 * but cannot be located or linked to, the destination file already |
|
71 * exists or cannot be opened for writing, or the uncompression fails |
|
72 */ |
|
73 public static native boolean extraUncompress(String srcPath, |
|
74 String destPath) throws IOException; |
|
75 |
|
76 private static final String BUNDLE_JAR_ENTRY_NAME = "classes.jar"; |
|
77 |
|
78 /** The bundle is not present. */ |
|
79 protected static final int NOT_DOWNLOADED = 0; |
|
80 |
|
81 /** |
|
82 * The bundle is in the download queue but has not finished downloading. |
|
83 */ |
|
84 protected static final int QUEUED = 1; |
|
85 |
|
86 /** The bundle has finished downloading but is not installed. */ |
|
87 protected static final int DOWNLOADED = 2; |
|
88 |
|
89 /** The bundle is fully installed and functional. */ |
|
90 protected static final int INSTALLED = 3; |
|
91 |
|
92 /** Thread pool used to manage dependency downloads. */ |
|
93 private static ExecutorService threadPool; |
|
94 |
|
95 /** Size of thread pool. */ |
|
96 static final int THREADS; |
|
97 |
|
98 static { |
|
99 String downloads = System.getProperty( |
|
100 DownloadManager.KERNEL_SIMULTANEOUS_DOWNLOADS_PROPERTY); |
|
101 if (downloads != null) |
|
102 THREADS = Integer.parseInt(downloads.trim()); |
|
103 else |
|
104 THREADS = 1; |
|
105 } |
|
106 |
|
107 /** Mutex used to safely access receipts file. */ |
|
108 private static Mutex receiptsMutex; |
|
109 |
|
110 /** Maps bundle names to known bundle instances. */ |
|
111 private static Map<String, Bundle> bundles = |
|
112 new HashMap<String, Bundle>(); |
|
113 |
|
114 /** Contains the names of currently-installed bundles. */ |
|
115 static Set<String> receipts = new HashSet<String>(); |
|
116 |
|
117 private static int bytesDownloaded; |
|
118 |
|
119 /** Path where bundle receipts are written. */ |
|
120 private static File receiptPath = new File(DownloadManager.getBundlePath(), |
|
121 "receipts"); |
|
122 |
|
123 /** The size of the receipts file the last time we saw it. */ |
|
124 private static int receiptsSize; |
|
125 |
|
126 /** The bundle name, e.g. "java_awt". */ |
|
127 private String name; |
|
128 |
|
129 /** The path to which we are saving the downloaded bundle file. */ |
|
130 private File localPath; |
|
131 |
|
132 /** |
|
133 * The path of the extracted JAR file containing the bundle's classes. |
|
134 */ |
|
135 private File jarPath; |
|
136 |
|
137 // for vista IE7 protected mode |
|
138 private File lowJarPath; |
|
139 private File lowJavaPath = null; |
|
140 |
|
141 /** The current state (DOWNLOADED, INSTALLED, etc.). */ |
|
142 protected int state; |
|
143 |
|
144 /** |
|
145 * True if we should delete the downloaded bundle after installing it. |
|
146 */ |
|
147 protected boolean deleteOnInstall = true; |
|
148 |
|
149 private static Mutex getReceiptsMutex() { |
|
150 if (receiptsMutex == null) |
|
151 receiptsMutex = Mutex.create(DownloadManager.MUTEX_PREFIX + |
|
152 "receipts"); |
|
153 return receiptsMutex; |
|
154 } |
|
155 |
|
156 |
|
157 /** |
|
158 * Reads the receipts file in order to seed the list of currently |
|
159 * installed bundles. |
|
160 */ |
|
161 static synchronized void loadReceipts() { |
|
162 getReceiptsMutex().acquire(); |
|
163 try { |
|
164 if (receiptPath.exists()) { |
|
165 int size = (int) receiptPath.length(); |
|
166 if (size != receiptsSize) { // ensure that it has actually |
|
167 // been modified |
|
168 DataInputStream in = null; |
|
169 try { |
|
170 receipts.clear(); |
|
171 for (String bundleName : DownloadManager.getBundleNames()) { |
|
172 if ("true".equals(DownloadManager.getBundleProperty(bundleName, |
|
173 DownloadManager.INSTALL_PROPERTY))) |
|
174 receipts.add(bundleName); |
|
175 } |
|
176 if (receiptPath.exists()) { |
|
177 in = new DataInputStream(new BufferedInputStream( |
|
178 new FileInputStream(receiptPath))); |
|
179 String line; |
|
180 while ((line = in.readLine()) != null) { |
|
181 receipts.add(line.trim()); |
|
182 } |
|
183 } |
|
184 receiptsSize = size; |
|
185 } |
|
186 catch (IOException e) { |
|
187 DownloadManager.log(e); |
|
188 // safe to continue, as the worst that happens is |
|
189 // we re-download existing bundles |
|
190 } finally { |
|
191 if (in != null) { |
|
192 try { |
|
193 in.close(); |
|
194 } catch (IOException ioe) { |
|
195 DownloadManager.log(ioe); |
|
196 } |
|
197 } |
|
198 } |
|
199 } |
|
200 } |
|
201 } |
|
202 finally { |
|
203 getReceiptsMutex().release(); |
|
204 } |
|
205 } |
|
206 |
|
207 |
|
208 /** Returns the bundle corresponding to the specified name. */ |
|
209 public static synchronized Bundle getBundle(String bundleId) |
|
210 throws IOException { |
|
211 Bundle result =(Bundle) bundles.get(bundleId); |
|
212 if (result == null && (bundleId.equals("merged") || |
|
213 Arrays.asList(DownloadManager.getBundleNames()).contains(bundleId))) { |
|
214 result = new Bundle(); |
|
215 result.name = bundleId; |
|
216 |
|
217 if (DownloadManager.isWindowsVista()) { |
|
218 result.localPath = |
|
219 new File(DownloadManager.getLocalLowTempBundlePath(), |
|
220 bundleId + ".zip"); |
|
221 result.lowJavaPath = new File( |
|
222 DownloadManager.getLocalLowKernelJava() + bundleId); |
|
223 } else { |
|
224 result.localPath = new File(DownloadManager.getBundlePath(), |
|
225 bundleId + ".zip"); |
|
226 } |
|
227 |
|
228 String jarPath = DownloadManager.getBundleProperty(bundleId, |
|
229 DownloadManager.JAR_PATH_PROPERTY); |
|
230 if (jarPath != null) { |
|
231 if (DownloadManager.isWindowsVista()) { |
|
232 result.lowJarPath = new File( |
|
233 DownloadManager.getLocalLowKernelJava() + bundleId, |
|
234 jarPath); |
|
235 } |
|
236 result.jarPath = new File(DownloadManager.JAVA_HOME, |
|
237 jarPath); |
|
238 |
|
239 } else { |
|
240 |
|
241 if (DownloadManager.isWindowsVista()) { |
|
242 result.lowJarPath = new File( |
|
243 DownloadManager.getLocalLowKernelJava() + bundleId + |
|
244 "\\lib\\bundles", |
|
245 bundleId + ".jar"); |
|
246 } |
|
247 |
|
248 result.jarPath = new File(DownloadManager.getBundlePath(), |
|
249 bundleId + ".jar"); |
|
250 |
|
251 } |
|
252 |
|
253 bundles.put(bundleId, result); |
|
254 } |
|
255 return result; |
|
256 } |
|
257 |
|
258 |
|
259 /** |
|
260 * Returns the name of this bundle. The name is typically defined by |
|
261 * the bundles.xml file. |
|
262 */ |
|
263 public String getName() { |
|
264 return name; |
|
265 } |
|
266 |
|
267 |
|
268 /** |
|
269 * Sets the name of this bundle. |
|
270 */ |
|
271 public void setName(String name) { |
|
272 this.name = name; |
|
273 } |
|
274 |
|
275 |
|
276 /** |
|
277 * Returns the path to the bundle file on the local filesystem. The file |
|
278 * will only exist if the bundle has already been downloaded; otherwise |
|
279 * it will be created when download() is called. |
|
280 */ |
|
281 public File getLocalPath() { |
|
282 return localPath; |
|
283 } |
|
284 |
|
285 |
|
286 /** |
|
287 * Sets the location of the bundle file on the local filesystem. If the |
|
288 * file already exists, the bundle will be considered downloaded; |
|
289 * otherwise the file will be created when download() is called. |
|
290 */ |
|
291 public void setLocalPath(File localPath) { |
|
292 this.localPath = localPath; |
|
293 } |
|
294 |
|
295 |
|
296 /** |
|
297 * Returns the path to the extracted JAR file containing this bundle's |
|
298 * classes. This file should only exist after the bundle has been |
|
299 * installed. |
|
300 */ |
|
301 public File getJarPath() { |
|
302 return jarPath; |
|
303 } |
|
304 |
|
305 |
|
306 /** |
|
307 * Sets the path to the extracted JAR file containing this bundle's |
|
308 * classes. This file will be created as part of installing the bundle. |
|
309 */ |
|
310 public void setJarPath(File jarPath) { |
|
311 this.jarPath = jarPath; |
|
312 } |
|
313 |
|
314 |
|
315 /** |
|
316 * Returns the size of the bundle download in bytes. |
|
317 */ |
|
318 public int getSize() { |
|
319 return Integer.valueOf(DownloadManager.getBundleProperty(getName(), |
|
320 DownloadManager.SIZE_PROPERTY)); |
|
321 } |
|
322 |
|
323 |
|
324 /** |
|
325 * Returns true if the bundle file (getLocalPath()) should be deleted |
|
326 * when the bundle is successfully installed. Defaults to true. |
|
327 */ |
|
328 public boolean getDeleteOnInstall() { |
|
329 return deleteOnInstall; |
|
330 } |
|
331 |
|
332 |
|
333 /** |
|
334 * Sets whether the bundle file (getLocalPath()) should be deleted |
|
335 * when the bundle is successfully installed. Defaults to true. |
|
336 */ |
|
337 public void setDeleteOnInstall(boolean deleteOnInstall) { |
|
338 this.deleteOnInstall = deleteOnInstall; |
|
339 } |
|
340 |
|
341 |
|
342 /** Sets the current state of this bundle to match reality. */ |
|
343 protected void updateState() { |
|
344 synchronized(Bundle.class) { |
|
345 loadReceipts(); |
|
346 if (receipts.contains(name) || |
|
347 "true".equals(DownloadManager.getBundleProperty(name, |
|
348 DownloadManager.INSTALL_PROPERTY))) |
|
349 state = Bundle.INSTALLED; |
|
350 else if (localPath.exists()) |
|
351 state = Bundle.DOWNLOADED; |
|
352 } |
|
353 } |
|
354 |
|
355 |
|
356 private String getURL(boolean showUI) throws IOException { |
|
357 Properties urls = DownloadManager.getBundleURLs(showUI); |
|
358 String result = urls.getProperty(name + ".zip"); |
|
359 if (result == null) { |
|
360 result = urls.getProperty(name); |
|
361 if (result == null) { |
|
362 DownloadManager.log("Unable to determine bundle URL for " + this); |
|
363 DownloadManager.log("Bundle URLs: " + urls); |
|
364 DownloadManager.sendErrorPing(DownloadManager.ERROR_NO_SUCH_BUNDLE); |
|
365 |
|
366 throw new NullPointerException("Unable to determine URL " + |
|
367 "for bundle: " + this); |
|
368 } |
|
369 } |
|
370 return result; |
|
371 } |
|
372 |
|
373 |
|
374 /** |
|
375 * Downloads the bundle. This method blocks until the download is |
|
376 * complete. |
|
377 * |
|
378 *@param showProgress true to display a progress dialog |
|
379 */ |
|
380 private void download(boolean showProgress) { |
|
381 if (DownloadManager.isJREComplete()) |
|
382 return; |
|
383 Mutex mutex = Mutex.create(DownloadManager.MUTEX_PREFIX + name + |
|
384 ".download"); |
|
385 mutex.acquire(); |
|
386 try { |
|
387 long start = System.currentTimeMillis(); |
|
388 |
|
389 boolean retry; |
|
390 |
|
391 do { |
|
392 retry = false; |
|
393 updateState(); |
|
394 if (state == DOWNLOADED || state == INSTALLED) { |
|
395 return; |
|
396 } |
|
397 File tmp = null; |
|
398 try { |
|
399 tmp = new File(localPath + ".tmp"); |
|
400 |
|
401 // tmp.deleteOnExit(); |
|
402 |
|
403 if (DownloadManager.getBaseDownloadURL().equals( |
|
404 DownloadManager.RESOURCE_URL)) { |
|
405 // RESOURCE_URL is used during build process, to |
|
406 // avoid actual network traffic. This is called in |
|
407 // the SplitJRE DownloadTest to determine which |
|
408 // classes are needed to support downloads, but we |
|
409 // bypass the actual HTTP download to simplify the |
|
410 // build process (it's all native code, so from |
|
411 // DownloadTest's standpoint it doesn't matter if we |
|
412 // really call it or not). |
|
413 String path = "/" + name + ".zip"; |
|
414 InputStream in = |
|
415 getClass().getResourceAsStream(path); |
|
416 if (in == null) |
|
417 throw new IOException("could not locate " + |
|
418 "resource: " + path); |
|
419 FileOutputStream out = new FileOutputStream(tmp); |
|
420 DownloadManager.send(in, out); |
|
421 in.close(); |
|
422 out.close(); |
|
423 } |
|
424 else { |
|
425 try { |
|
426 String bundleURL = getURL(showProgress); |
|
427 DownloadManager.log("Downloading from: " + |
|
428 bundleURL); |
|
429 DownloadManager.downloadFromURL(bundleURL, tmp, |
|
430 name.replace('_', '.'), showProgress); |
|
431 } |
|
432 catch (HttpRetryException e) { |
|
433 // Akamai returned a 403, get new URL |
|
434 DownloadManager.flushBundleURLs(); |
|
435 String bundleURL = getURL(showProgress); |
|
436 DownloadManager.log("Retrying at new " + |
|
437 "URL: " + bundleURL); |
|
438 DownloadManager.downloadFromURL(bundleURL, tmp, |
|
439 name.replace('_', '.'), |
|
440 showProgress); |
|
441 // we intentionally don't do a 403 retry |
|
442 // again, to avoid infinite retries |
|
443 } |
|
444 } |
|
445 if (!tmp.exists() || tmp.length() == 0) { |
|
446 if (showProgress) { |
|
447 // since showProgress = true, native code should |
|
448 // have offered to retry. Since we ended up here, |
|
449 // we conclude that download failed & user opted to |
|
450 // cancel. Set complete to true to stop bugging |
|
451 // him in the future (if one bundle fails, the |
|
452 // rest are virtually certain to). |
|
453 DownloadManager.complete = true; |
|
454 } |
|
455 DownloadManager.fatalError(DownloadManager.ERROR_UNSPECIFIED); |
|
456 } |
|
457 |
|
458 /** |
|
459 * Bundle security |
|
460 * |
|
461 * Check for corruption/spoofing |
|
462 */ |
|
463 |
|
464 |
|
465 /* Create a bundle check from the tmp file */ |
|
466 BundleCheck gottenCheck = BundleCheck.getInstance(tmp); |
|
467 |
|
468 /* Get the check expected for the Bundle */ |
|
469 BundleCheck expectedCheck = BundleCheck.getInstance(name); |
|
470 |
|
471 // Do they match? |
|
472 |
|
473 if (expectedCheck.equals(gottenCheck)) { |
|
474 |
|
475 // Security check OK, uncompress the bundle file |
|
476 // into the local path |
|
477 |
|
478 long uncompressedLength = tmp.length(); |
|
479 localPath.delete(); |
|
480 |
|
481 File uncompressedPath = new File(tmp.getPath() + |
|
482 ".jar0"); |
|
483 if (! extraUncompress(tmp.getPath(), |
|
484 uncompressedPath.getPath())) { |
|
485 // Extra uncompression not available, fall |
|
486 // back to alternative if it is enabled. |
|
487 if (DownloadManager.debug) { |
|
488 DownloadManager.log("Uncompressing with GZIP"); |
|
489 } |
|
490 GZIPInputStream in = new GZIPInputStream( new |
|
491 BufferedInputStream(new FileInputStream(tmp), |
|
492 DownloadManager.BUFFER_SIZE)); |
|
493 BufferedOutputStream out = new BufferedOutputStream( |
|
494 new FileOutputStream(uncompressedPath), |
|
495 DownloadManager.BUFFER_SIZE); |
|
496 DownloadManager.send(in,out); |
|
497 in.close(); |
|
498 out.close(); |
|
499 if (! uncompressedPath.renameTo(localPath)) { |
|
500 throw new IOException("unable to rename " + |
|
501 uncompressedPath + " to " + localPath); |
|
502 } |
|
503 } else { |
|
504 if (DownloadManager.debug) { |
|
505 DownloadManager.log("Uncompressing with LZMA"); |
|
506 } |
|
507 if (! uncompressedPath.renameTo(localPath)) { |
|
508 throw new IOException("unable to rename " + |
|
509 uncompressedPath + " to " + localPath); |
|
510 } |
|
511 } |
|
512 state = DOWNLOADED; |
|
513 bytesDownloaded += uncompressedLength; |
|
514 long time = (System.currentTimeMillis() - |
|
515 start); |
|
516 DownloadManager.log("Downloaded " + name + |
|
517 " in " + time + "ms. Downloaded " + |
|
518 bytesDownloaded + " bytes this session."); |
|
519 |
|
520 // Normal completion |
|
521 } else { |
|
522 |
|
523 // Security check not OK: remove the temp file |
|
524 // and consult the user |
|
525 |
|
526 tmp.delete(); |
|
527 |
|
528 DownloadManager.log( |
|
529 "DownloadManager: Security check failed for " + |
|
530 "bundle " + name); |
|
531 |
|
532 // only show dialog if we are not in silent mode |
|
533 if (showProgress) { |
|
534 retry = DownloadManager.askUserToRetryDownloadOrQuit( |
|
535 DownloadManager.ERROR_UNSPECIFIED); |
|
536 } |
|
537 |
|
538 if (!retry) { |
|
539 // User wants to give up |
|
540 throw new RuntimeException( |
|
541 "Failed bundle security check and user " + |
|
542 "canceled"); |
|
543 } |
|
544 } |
|
545 } |
|
546 catch (IOException e) { |
|
547 // Look for "out of space" using File.getUsableSpace() |
|
548 // here when downloadFromURL starts throwing IOException |
|
549 // (or preferably a distinct exception for this case). |
|
550 DownloadManager.log(e); |
|
551 } |
|
552 } while (retry); |
|
553 } finally { |
|
554 mutex.release(); |
|
555 } |
|
556 } |
|
557 |
|
558 |
|
559 /** |
|
560 * Calls {@link #queueDownload()} on all of this bundle's dependencies. |
|
561 */ |
|
562 void queueDependencies(boolean showProgress) { |
|
563 try { |
|
564 String dependencies = |
|
565 DownloadManager.getBundleProperty(name, |
|
566 DownloadManager.DEPENDENCIES_PROPERTY); |
|
567 if (dependencies != null) { |
|
568 StringTokenizer st = new StringTokenizer(dependencies, |
|
569 " ,"); |
|
570 while (st.hasMoreTokens()) { |
|
571 Bundle b = getBundle(st.nextToken()); |
|
572 if (b != null && !b.isInstalled()) { |
|
573 if (DownloadManager.debug) { |
|
574 DownloadManager.log("Queueing " + b.name + |
|
575 " as a dependency of " + name + "..."); |
|
576 } |
|
577 b.install(showProgress, true, false); |
|
578 } |
|
579 } |
|
580 } |
|
581 } catch (IOException e) { |
|
582 // shouldn't happen |
|
583 DownloadManager.log(e); |
|
584 } |
|
585 } |
|
586 |
|
587 |
|
588 static synchronized ExecutorService getThreadPool() { |
|
589 if (threadPool == null) { |
|
590 threadPool = Executors.newFixedThreadPool(THREADS, |
|
591 new ThreadFactory () { |
|
592 public Thread newThread(Runnable r) { |
|
593 Thread result = new Thread(r); |
|
594 result.setDaemon(true); |
|
595 return result; |
|
596 } |
|
597 } |
|
598 ); |
|
599 } |
|
600 return threadPool; |
|
601 } |
|
602 |
|
603 |
|
604 private void unpackBundle() throws IOException { |
|
605 File useJarPath = null; |
|
606 if (DownloadManager.isWindowsVista()) { |
|
607 useJarPath = lowJarPath; |
|
608 File jarDir = useJarPath.getParentFile(); |
|
609 if (jarDir != null) { |
|
610 jarDir.mkdirs(); |
|
611 } |
|
612 } else { |
|
613 useJarPath = jarPath; |
|
614 } |
|
615 |
|
616 DownloadManager.log("Unpacking " + this + " to " + useJarPath); |
|
617 |
|
618 InputStream rawStream = new FileInputStream(localPath); |
|
619 JarInputStream in = new JarInputStream(rawStream) { |
|
620 public void close() throws IOException { |
|
621 // prevent any sub-processes here from actually closing the |
|
622 // input stream; we'll use rawsStream.close() when we're |
|
623 // done with it |
|
624 } |
|
625 }; |
|
626 |
|
627 try { |
|
628 File jarTmp = null; |
|
629 JarEntry entry; |
|
630 while ((entry = in.getNextJarEntry()) != null) { |
|
631 String entryName = entry.getName(); |
|
632 if (entryName.equals("classes.pack")) { |
|
633 File packTmp = new File(useJarPath + ".pack"); |
|
634 packTmp.getParentFile().mkdirs(); |
|
635 DownloadManager.log("Writing temporary .pack file " + packTmp); |
|
636 OutputStream tmpOut = new FileOutputStream(packTmp); |
|
637 try { |
|
638 DownloadManager.send(in, tmpOut); |
|
639 } finally { |
|
640 tmpOut.close(); |
|
641 } |
|
642 // we unpack to a temporary file and then, towards the end |
|
643 // of this method, use a (hopefully atomic) rename to put it |
|
644 // into its final location; this should avoid the problem of |
|
645 // partially-completed downloads. Doing the rename last |
|
646 // allows us to check for the presence of the JAR file to |
|
647 // see whether the bundle has in fact been downloaded. |
|
648 jarTmp = new File(useJarPath + ".tmp"); |
|
649 DownloadManager.log("Writing temporary .jar file " + jarTmp); |
|
650 unpack(packTmp, jarTmp); |
|
651 packTmp.delete(); |
|
652 } else if (!entryName.startsWith("META-INF")) { |
|
653 File dest; |
|
654 if (DownloadManager.isWindowsVista()) { |
|
655 dest = new File(lowJavaPath, |
|
656 entryName.replace('/', File.separatorChar)); |
|
657 } else { |
|
658 dest = new File(DownloadManager.JAVA_HOME, |
|
659 entryName.replace('/', File.separatorChar)); |
|
660 } |
|
661 if (entryName.equals(BUNDLE_JAR_ENTRY_NAME)) |
|
662 dest = useJarPath; |
|
663 File destTmp = new File(dest + ".tmp"); |
|
664 boolean exists = dest.exists(); |
|
665 if (!exists) { |
|
666 DownloadManager.log(dest + ".mkdirs()"); |
|
667 dest.getParentFile().mkdirs(); |
|
668 } |
|
669 try { |
|
670 DownloadManager.log("Using temporary file " + destTmp); |
|
671 FileOutputStream out = |
|
672 new FileOutputStream(destTmp); |
|
673 try { |
|
674 byte[] buffer = new byte[2048]; |
|
675 int c; |
|
676 while ((c = in.read(buffer)) > 0) |
|
677 out.write(buffer, 0, c); |
|
678 } finally { |
|
679 out.close(); |
|
680 } |
|
681 if (exists) |
|
682 dest.delete(); |
|
683 DownloadManager.log("Renaming from " + destTmp + " to " + dest); |
|
684 if (!destTmp.renameTo(dest)) { |
|
685 throw new IOException("unable to rename " + |
|
686 destTmp + " to " + dest); |
|
687 } |
|
688 |
|
689 } catch (IOException e) { |
|
690 if (!exists) |
|
691 throw e; |
|
692 // otherwise the file already existed and the fact |
|
693 // that we failed to re-write it probably just |
|
694 // means that it was in use |
|
695 } |
|
696 } |
|
697 } |
|
698 |
|
699 // rename the temporary jar into its final location |
|
700 if (jarTmp != null) { |
|
701 if (useJarPath.exists()) |
|
702 jarTmp.delete(); |
|
703 else if (!jarTmp.renameTo(useJarPath)) { |
|
704 throw new IOException("unable to rename " + jarTmp + |
|
705 " to " + useJarPath); |
|
706 } |
|
707 } |
|
708 if (DownloadManager.isWindowsVista()) { |
|
709 // move bundle to real location |
|
710 DownloadManager.log("Using broker to move " + name); |
|
711 if (!DownloadManager.moveDirWithBroker( |
|
712 DownloadManager.getKernelJREDir() + name)) { |
|
713 throw new IOException("unable to create " + name); |
|
714 } |
|
715 DownloadManager.log("Broker finished " + name); |
|
716 } |
|
717 DownloadManager.log("Finished unpacking " + this); |
|
718 } finally { |
|
719 rawStream.close(); |
|
720 } |
|
721 if (deleteOnInstall) { |
|
722 localPath.delete(); |
|
723 } |
|
724 |
|
725 } |
|
726 |
|
727 |
|
728 public static void unpack(File pack, File jar) throws IOException { |
|
729 Process p = Runtime.getRuntime().exec(DownloadManager.JAVA_HOME + File.separator + |
|
730 "bin" + File.separator + "unpack200 -Hoff \"" + pack + "\" \"" + jar + "\""); |
|
731 try { |
|
732 p.waitFor(); |
|
733 } |
|
734 catch (InterruptedException e) { |
|
735 } |
|
736 } |
|
737 |
|
738 |
|
739 /** |
|
740 * Unpacks and installs the bundle. The bundle's classes are not |
|
741 * immediately added to the boot class path; this happens when the VM |
|
742 * attempts to load a class and calls getBootClassPathEntryForClass(). |
|
743 */ |
|
744 public void install() throws IOException { |
|
745 install(true, false, true); |
|
746 } |
|
747 |
|
748 |
|
749 /** |
|
750 * Unpacks and installs the bundle, optionally hiding the progress |
|
751 * indicator. The bundle's classes are not immediately added to the |
|
752 * boot class path; this happens when the VM attempts to load a class |
|
753 * and calls getBootClassPathEntryForClass(). |
|
754 * |
|
755 *@param showProgress true to display a progress dialog |
|
756 *@param downloadOnly true to download but not install |
|
757 *@param block true to wait until the operation is complete before returning |
|
758 */ |
|
759 public synchronized void install(final boolean showProgress, |
|
760 final boolean downloadOnly, boolean block) throws IOException { |
|
761 if (DownloadManager.isJREComplete()) |
|
762 return; |
|
763 if (state == NOT_DOWNLOADED || state == QUEUED) { |
|
764 // we allow an already-queued bundle to be placed into the queue |
|
765 // again, to handle the case where the bundle is queued with |
|
766 // downloadOnly true and then we try to queue it again with |
|
767 // downloadOnly false -- the second queue entry will actually |
|
768 // install it. |
|
769 if (state != QUEUED) { |
|
770 DownloadManager.addToTotalDownloadSize(getSize()); |
|
771 state = QUEUED; |
|
772 } |
|
773 if (getThreadPool().isShutdown()) { |
|
774 if (state == NOT_DOWNLOADED || state == QUEUED) |
|
775 doInstall(showProgress, downloadOnly); |
|
776 } |
|
777 else { |
|
778 Future task = getThreadPool().submit(new Runnable() { |
|
779 public void run() { |
|
780 try { |
|
781 if (state == NOT_DOWNLOADED || state == QUEUED || |
|
782 (!downloadOnly && state == DOWNLOADED)) { |
|
783 doInstall(showProgress, downloadOnly); |
|
784 } |
|
785 } |
|
786 catch (IOException e) { |
|
787 // ignore |
|
788 } |
|
789 } |
|
790 }); |
|
791 queueDependencies(showProgress); |
|
792 if (block) { |
|
793 try { |
|
794 task.get(); |
|
795 } |
|
796 catch (Exception e) { |
|
797 throw new Error(e); |
|
798 } |
|
799 } |
|
800 } |
|
801 } |
|
802 else if (state == DOWNLOADED && !downloadOnly) |
|
803 doInstall(showProgress, false); |
|
804 } |
|
805 |
|
806 |
|
807 private void doInstall(boolean showProgress, boolean downloadOnly) |
|
808 throws IOException { |
|
809 Mutex mutex = Mutex.create(DownloadManager.MUTEX_PREFIX + name + |
|
810 ".install"); |
|
811 DownloadManager.bundleInstallStart(); |
|
812 try { |
|
813 mutex.acquire(); |
|
814 updateState(); |
|
815 if (state == NOT_DOWNLOADED || state == QUEUED) { |
|
816 download(showProgress); |
|
817 } |
|
818 |
|
819 if (state == DOWNLOADED && downloadOnly) { |
|
820 return; |
|
821 } |
|
822 |
|
823 if (state == INSTALLED) { |
|
824 return; |
|
825 } |
|
826 if (state != DOWNLOADED) { |
|
827 DownloadManager.fatalError(DownloadManager.ERROR_UNSPECIFIED); |
|
828 } |
|
829 |
|
830 DownloadManager.log("Calling unpackBundle for " + this); |
|
831 unpackBundle(); |
|
832 DownloadManager.log("Writing receipt for " + this); |
|
833 writeReceipt(); |
|
834 updateState(); |
|
835 DownloadManager.log("Finished installing " + this + ", state=" + state); |
|
836 } finally { |
|
837 if (lowJavaPath != null) { |
|
838 lowJavaPath.delete(); |
|
839 } |
|
840 mutex.release(); |
|
841 DownloadManager.bundleInstallComplete(); |
|
842 } |
|
843 } |
|
844 |
|
845 |
|
846 synchronized void setState(int state) { |
|
847 this.state = state; |
|
848 } |
|
849 |
|
850 |
|
851 /** Returns <code>true</code> if this bundle has been installed. */ |
|
852 public boolean isInstalled() { |
|
853 synchronized (Bundle.class) { |
|
854 updateState(); |
|
855 return state == INSTALLED; |
|
856 } |
|
857 } |
|
858 |
|
859 |
|
860 /** |
|
861 * Adds an entry to the receipts file indicating that this bundle has |
|
862 * been successfully downloaded. |
|
863 */ |
|
864 private void writeReceipt() { |
|
865 getReceiptsMutex().acquire(); |
|
866 File useReceiptPath = null; |
|
867 try { |
|
868 |
|
869 try { |
|
870 |
|
871 receipts.add(name); |
|
872 |
|
873 if (DownloadManager.isWindowsVista()) { |
|
874 // write out receipts to locallow |
|
875 useReceiptPath = new File( |
|
876 DownloadManager.getLocalLowTempBundlePath(), |
|
877 "receipts"); |
|
878 |
|
879 if (receiptPath.exists()) { |
|
880 // copy original file to locallow location |
|
881 DownloadManager.copyReceiptFile(receiptPath, |
|
882 useReceiptPath); |
|
883 } |
|
884 |
|
885 // update receipt in locallow path |
|
886 // only append if original receipt path exists |
|
887 FileOutputStream out = new FileOutputStream(useReceiptPath, |
|
888 receiptPath.exists()); |
|
889 out.write((name + System.getProperty("line.separator")).getBytes("utf-8")); |
|
890 out.close(); |
|
891 |
|
892 // use broker to move back to real path |
|
893 if (!DownloadManager.moveFileWithBroker( |
|
894 DownloadManager.getKernelJREDir() |
|
895 + "-bundles" + File.separator + "receipts")) { |
|
896 throw new IOException("failed to write receipts"); |
|
897 } |
|
898 } else { |
|
899 useReceiptPath = receiptPath; |
|
900 FileOutputStream out = new FileOutputStream(useReceiptPath, |
|
901 true); |
|
902 out.write((name + System.getProperty("line.separator")).getBytes("utf-8")); |
|
903 out.close(); |
|
904 } |
|
905 |
|
906 |
|
907 } catch (IOException e) { |
|
908 DownloadManager.log(e); |
|
909 // safe to continue, as the worst that happens is we |
|
910 // re-download existing bundles |
|
911 } |
|
912 } |
|
913 finally { |
|
914 getReceiptsMutex().release(); |
|
915 } |
|
916 } |
|
917 |
|
918 |
|
919 public String toString() { |
|
920 return "Bundle[" + name + "]"; |
|
921 } |
|
922 } |
|