001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package org.apache.hadoop.hdfs;
020    
021    import java.io.IOException;
022    import java.io.InputStream;
023    import java.net.HttpURLConnection;
024    import java.net.URL;
025    
026    import org.apache.hadoop.fs.FSInputStream;
027    import org.apache.hadoop.hdfs.server.namenode.StreamFile;
028    
029    /**
030     * To support HTTP byte streams, a new connection to an HTTP server needs to be
031     * created each time. This class hides the complexity of those multiple 
032     * connections from the client. Whenever seek() is called, a new connection
033     * is made on the successive read(). The normal input stream functions are 
034     * connected to the currently active input stream. 
035     */
036    public abstract class ByteRangeInputStream extends FSInputStream {
037      
038      /**
039       * This class wraps a URL and provides method to open connection.
040       * It can be overridden to change how a connection is opened.
041       */
042      public static abstract class URLOpener {
043        protected URL url;
044      
045        public URLOpener(URL u) {
046          url = u;
047        }
048      
049        public void setURL(URL u) {
050          url = u;
051        }
052      
053        public URL getURL() {
054          return url;
055        }
056    
057        protected abstract HttpURLConnection openConnection() throws IOException;
058    
059        protected abstract HttpURLConnection openConnection(final long offset) throws IOException;
060      }
061    
062      enum StreamStatus {
063        NORMAL, SEEK
064      }
065      protected InputStream in;
066      protected URLOpener originalURL;
067      protected URLOpener resolvedURL;
068      protected long startPos = 0;
069      protected long currentPos = 0;
070      protected long filelength;
071    
072      StreamStatus status = StreamStatus.SEEK;
073    
074      /**
075       * Create with the specified URLOpeners. Original url is used to open the 
076       * stream for the first time. Resolved url is used in subsequent requests.
077       * @param o Original url
078       * @param r Resolved url
079       */
080      public ByteRangeInputStream(URLOpener o, URLOpener r) {
081        this.originalURL = o;
082        this.resolvedURL = r;
083      }
084      
085      protected abstract void checkResponseCode(final HttpURLConnection connection
086          ) throws IOException;
087      
088      protected abstract URL getResolvedUrl(final HttpURLConnection connection
089          ) throws IOException;
090    
091      private InputStream getInputStream() throws IOException {
092        if (status != StreamStatus.NORMAL) {
093          
094          if (in != null) {
095            in.close();
096            in = null;
097          }
098          
099          // Use the original url if no resolved url exists, eg. if
100          // it's the first time a request is made.
101          final URLOpener opener =
102            (resolvedURL.getURL() == null) ? originalURL : resolvedURL;
103    
104          final HttpURLConnection connection = opener.openConnection(startPos);
105          connection.connect();
106          checkResponseCode(connection);
107    
108          final String cl = connection.getHeaderField(StreamFile.CONTENT_LENGTH);
109          filelength = (cl == null) ? -1 : Long.parseLong(cl);
110          in = connection.getInputStream();
111    
112          resolvedURL.setURL(getResolvedUrl(connection));
113          status = StreamStatus.NORMAL;
114        }
115        
116        return in;
117      }
118      
119      private void update(final boolean isEOF, final int n)
120          throws IOException {
121        if (!isEOF) {
122          currentPos += n;
123        } else if (currentPos < filelength) {
124          throw new IOException("Got EOF but currentPos = " + currentPos
125              + " < filelength = " + filelength);
126        }
127      }
128    
129      public int read() throws IOException {
130        final int b = getInputStream().read();
131        update(b == -1, 1);
132        return b;
133      }
134      
135      /**
136       * Seek to the given offset from the start of the file.
137       * The next read() will be from that location.  Can't
138       * seek past the end of the file.
139       */
140      public void seek(long pos) throws IOException {
141        if (pos != currentPos) {
142          startPos = pos;
143          currentPos = pos;
144          status = StreamStatus.SEEK;
145        }
146      }
147    
148      /**
149       * Return the current offset from the start of the file
150       */
151      public long getPos() throws IOException {
152        return currentPos;
153      }
154    
155      /**
156       * Seeks a different copy of the data.  Returns true if
157       * found a new source, false otherwise.
158       */
159      public boolean seekToNewSource(long targetPos) throws IOException {
160        return false;
161      }
162    }