An OutputStream
wrapper class for JNLP muffins (which are to JNLP as cookies are to HTTP):
1 public class MuffinOutputStream 2 extends DataOutputStream { 3 final String name; 4 5 /** 6 * Constructs a new {@code MuffinOutputStream} for 7 * the given <var>name</var>. Nothing 8 * happens—<em>even on writes</em>— 9 * until {@link #close()} when everything is written 10 * at once after all the writes are collected 11 * together first before committing. 12 * 13 * @param name the muffin name relative to the JNLP 14 * code base 15 * 16 * @see PersistenceService#get(URL) 17 * @see PersistenceService#create(URL, long) 18 */ 19 public MuffinOutputStream(final String name) { 20 super(new ByteArrayOutputStream()); 21 22 this.name = name; 23 } 24 25 /** 26 * Really writes the queued data to the muffin. 27 * <p/> 28 * {@inheritDoc} 29 * 30 * @throws IOException if the muffin cannot be created, 31 * opened or written 32 */ 33 @Override 34 public void close() 35 throws IOException { 36 super.close(); 37 38 final ByteArrayOutputStream baos 39 = (ByteArrayOutputStream) out; 40 final OutputStream muffinStream = open(name, 41 baos.size()); 42 43 copyStreams(new ByteArrayInputStream( 44 baos.toByteArray()), muffinStream); 45 46 muffinStream.flush(); 47 muffinStream.close(); 48 } 49 50 private static OutputStream open(final String name, 51 final int maxsize) 52 throws IOException { 53 try { 54 final PersistenceService ps = lookup( 55 PersistenceService.class); 56 final URL codeBase = lookup( 57 BasicService.class).getCodeBase(); 58 // Escape bad URL characters 59 final URL muffin = createMuffin(codeBase, 60 encode(name, "UTF-8")); 61 62 // Parent must exist, but do not clobber if so 63 boolean parentAlreadyExists = false; 64 65 try { 66 try { 67 ps.create(codeBase, 0); 68 69 } catch (final IOException e) { 70 parentAlreadyExists = true; 71 } 72 73 try { 74 // Remove exising in case size is wrong 75 ps.delete(muffin); 76 } catch (final IOException e) { 77 // Truly ignore: ok to fail on deletion 78 } 79 80 ps.create(muffin, maxsize); 81 // Saved on client, not on server 82 ps.setTag(muffin, DIRTY); 83 84 return ps.get(muffin).getOutputStream(true); 85 86 } finally { 87 if (!parentAlreadyExists) 88 ps.delete(codeBase); 89 } 90 91 } catch (final IOException wrapped) { 92 final IOException e = new IOException( 93 "Cannot save data to " + name); 94 95 e.initCause(wrapped); 96 97 throw e; 98 } 99 } 100 }
(Note several helper methods used in the code, but not presented here: lookup
, createMuffin
and copyStreams
.)
Writing MuffinInputStream
is much simpler since one need not worry about parent muffins, nor bunch up the output to work out the size of the muffin before persisting. And neither does the "delay until close" pattern come into play. This is one of those times I wish I had C++ destructors for my Java.
No comments:
Post a Comment