1 /*
   2  * Copyright (c) 1996, 2008, 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 
  26 package sun.net.www.http;
  27 
  28 import java.io.*;
  29 import sun.net.ProgressSource;
  30 import sun.net.www.MeteredStream;
  31 
  32 /**
  33  * A stream that has the property of being able to be kept alive for
  34  * multiple downloads from the same server.
  35  *
  36  * @author Stephen R. Pietrowicz (NCSA)
  37  * @author Dave Brown
  38  */
  39 public
  40 class KeepAliveStream extends MeteredStream implements Hurryable {
  41 
  42     // instance variables
  43     HttpClient hc;
  44 
  45     boolean hurried;
  46 
  47     // has this KeepAliveStream been put on the queue for asynchronous cleanup.
  48     protected boolean queuedForCleanup = false;
  49 
  50     private static final KeepAliveStreamCleaner queue = new KeepAliveStreamCleaner();
  51     private static Thread cleanerThread; // null
  52 
  53     /**
  54      * Constructor
  55      */
  56     public KeepAliveStream(InputStream is, ProgressSource pi, long expected, HttpClient hc)  {
  57         super(is, pi, expected);
  58         this.hc = hc;
  59     }
  60 
  61     /**
  62      * Attempt to cache this connection
  63      */
  64     public void close() throws IOException  {
  65         // If the inputstream is closed already, just return.
  66         if (closed) {
  67             return;
  68         }
  69 
  70         // If this stream has already been queued for cleanup.
  71         if (queuedForCleanup) {
  72             return;
  73         }
  74 
  75         // Skip past the data that's left in the Inputstream because
  76         // some sort of error may have occurred.
  77         // Do this ONLY if the skip won't block. The stream may have
  78         // been closed at the beginning of a big file and we don't want
  79         // to hang around for nothing. So if we can't skip without blocking
  80         // we just close the socket and, therefore, terminate the keepAlive
  81         // NOTE: Don't close super class
  82         try {
  83             if (expected > count) {
  84                 long nskip = (long) (expected - count);
  85                 if (nskip <= available()) {
  86                     long n = 0;
  87                     while (n < nskip) {
  88                         nskip = nskip - n;
  89                         n = skip(nskip);
  90                     }
  91                 } else if (expected <= KeepAliveStreamCleaner.MAX_DATA_REMAINING && !hurried) {
  92                     //put this KeepAliveStream on the queue so that the data remaining
  93                     //on the socket can be cleanup asyncronously.
  94                     queueForCleanup(new KeepAliveCleanerEntry(this, hc));
  95                 } else {
  96                     hc.closeServer();
  97                 }
  98             }
  99             if (!closed && !hurried && !queuedForCleanup) {
 100                 hc.finished();
 101             }
 102         } finally {
 103             if (pi != null)
 104                 pi.finishTracking();
 105 
 106             if (!queuedForCleanup) {
 107                 // nulling out the underlying inputstream as well as
 108                 // httpClient to let gc collect the memories faster
 109                 in = null;
 110                 hc = null;
 111                 closed = true;
 112             }
 113         }
 114     }
 115 
 116     /* we explicitly do not support mark/reset */
 117 
 118     public boolean markSupported()  {
 119         return false;
 120     }
 121 
 122     public void mark(int limit) {}
 123 
 124     public void reset() throws IOException {
 125         throw new IOException("mark/reset not supported");
 126     }
 127 
 128     public synchronized boolean hurry() {
 129         try {
 130             /* CASE 0: we're actually already done */
 131             if (closed || count >= expected) {
 132                 return false;
 133             } else if (in.available() < (expected - count)) {
 134                 /* CASE I: can't meet the demand */
 135                 return false;
 136             } else {
 137                 /* CASE II: fill our internal buffer
 138                  * Remind: possibly check memory here
 139                  */
 140                 int size = (int) (expected - count);
 141                 byte[] buf = new byte[size];
 142                 DataInputStream dis = new DataInputStream(in);
 143                 dis.readFully(buf);
 144                 in = new ByteArrayInputStream(buf);
 145                 hurried = true;
 146                 return true;
 147             }
 148         } catch (IOException e) {
 149             // e.printStackTrace();
 150             return false;
 151         }
 152     }
 153 
 154     private static void queueForCleanup(KeepAliveCleanerEntry kace) {
 155         synchronized(queue) {
 156             if(!kace.getQueuedForCleanup()) {
 157                 if (!queue.offer(kace)) {
 158                     kace.getHttpClient().closeServer();
 159                     return;
 160                 }
 161 
 162                 kace.setQueuedForCleanup();
 163                 queue.notifyAll();
 164             }
 165 
 166             boolean startCleanupThread = (cleanerThread == null);
 167             if (!startCleanupThread) {
 168                 if (!cleanerThread.isAlive()) {
 169                     startCleanupThread = true;
 170                 }
 171             }
 172 
 173             if (startCleanupThread) {
 174                 java.security.AccessController.doPrivileged(
 175                     new java.security.PrivilegedAction<Void>() {
 176                     public Void run() {
 177                         // We want to create the Keep-Alive-SocketCleaner in the
 178                         // system threadgroup
 179                         ThreadGroup grp = Thread.currentThread().getThreadGroup();
 180                         ThreadGroup parent = null;
 181                         while ((parent = grp.getParent()) != null) {
 182                             grp = parent;
 183                         }
 184 
 185                         cleanerThread = new Thread(grp, queue, "Keep-Alive-SocketCleaner");
 186                         cleanerThread.setDaemon(true);
 187                         cleanerThread.setPriority(Thread.MAX_PRIORITY - 2);
 188                         // Set the context class loader to null in order to avoid
 189                         // keeping a strong reference to an application classloader.
 190                         cleanerThread.setContextClassLoader(null);
 191                         cleanerThread.start();
 192                         return null;
 193                     }
 194                 });
 195             }
 196         } // queue
 197     }
 198 
 199     protected long remainingToRead() {
 200         return expected - count;
 201     }
 202 
 203     protected void setClosed() {
 204         in = null;
 205         hc = null;
 206         closed = true;
 207     }
 208 }
 209 
 210 
 211 class KeepAliveCleanerEntry
 212 {
 213     KeepAliveStream kas;
 214     HttpClient hc;
 215 
 216     public KeepAliveCleanerEntry(KeepAliveStream kas, HttpClient hc) {
 217         this.kas = kas;
 218         this.hc = hc;
 219     }
 220 
 221     protected KeepAliveStream getKeepAliveStream() {
 222         return kas;
 223     }
 224 
 225     protected HttpClient getHttpClient() {
 226         return hc;
 227     }
 228 
 229     protected void setQueuedForCleanup() {
 230         kas.queuedForCleanup = true;
 231     }
 232 
 233     protected boolean getQueuedForCleanup() {
 234         return kas.queuedForCleanup;
 235     }
 236 
 237 }