1717 */
1818package org .apache .hadoop .hdfs .server .namenode .web .resources ;
1919
20+ import java .io .FileNotFoundException ;
2021import java .io .IOException ;
2122import java .io .InputStream ;
23+ import java .io .OutputStream ;
24+ import java .io .PrintStream ;
2225import java .net .URI ;
2326import java .net .URISyntaxException ;
2427import java .util .EnumSet ;
3740import javax .ws .rs .core .Context ;
3841import javax .ws .rs .core .MediaType ;
3942import javax .ws .rs .core .Response ;
43+ import javax .ws .rs .core .StreamingOutput ;
4044
4145import org .apache .commons .logging .Log ;
4246import org .apache .commons .logging .LogFactory ;
4347import org .apache .hadoop .fs .Options ;
4448import org .apache .hadoop .hdfs .protocol .DatanodeInfo ;
49+ import org .apache .hadoop .hdfs .protocol .DirectoryListing ;
4550import org .apache .hadoop .hdfs .protocol .HdfsFileStatus ;
4651import org .apache .hadoop .hdfs .protocol .LocatedBlocks ;
4752import org .apache .hadoop .hdfs .server .blockmanagement .DatanodeDescriptor ;
5863import org .apache .hadoop .hdfs .web .resources .GetOpParam ;
5964import org .apache .hadoop .hdfs .web .resources .GroupParam ;
6065import org .apache .hadoop .hdfs .web .resources .HttpOpParam ;
66+ import org .apache .hadoop .hdfs .web .resources .LengthParam ;
6167import org .apache .hadoop .hdfs .web .resources .ModificationTimeParam ;
68+ import org .apache .hadoop .hdfs .web .resources .OffsetParam ;
6269import org .apache .hadoop .hdfs .web .resources .OverwriteParam ;
6370import org .apache .hadoop .hdfs .web .resources .OwnerParam ;
6471import org .apache .hadoop .hdfs .web .resources .Param ;
@@ -79,15 +86,23 @@ public class NamenodeWebHdfsMethods {
7986 private @ Context ServletContext context ;
8087
8188 private static DatanodeInfo chooseDatanode (final NameNode namenode ,
82- final String path , final HttpOpParam .Op op ) throws IOException {
83- if (op == PostOpParam .Op .APPEND ) {
84- final HdfsFileStatus status = namenode .getRpcServer ().getFileInfo (path );
89+ final String path , final HttpOpParam .Op op , final long openOffset
90+ ) throws IOException {
91+ if (op == GetOpParam .Op .OPEN || op == PostOpParam .Op .APPEND ) {
92+ final NamenodeProtocols np = namenode .getRpcServer ();
93+ final HdfsFileStatus status = np .getFileInfo (path );
8594 final long len = status .getLen ();
95+ if (op == GetOpParam .Op .OPEN && (openOffset < 0L || openOffset >= len )) {
96+ throw new IOException ("Offset=" + openOffset + " out of the range [0, "
97+ + len + "); " + op + ", path=" + path );
98+ }
99+
86100 if (len > 0 ) {
87- final LocatedBlocks locations = namenode .getRpcServer ().getBlockLocations (path , len -1 , 1 );
101+ final long offset = op == GetOpParam .Op .OPEN ? openOffset : len - 1 ;
102+ final LocatedBlocks locations = np .getBlockLocations (path , offset , 1 );
88103 final int count = locations .locatedBlockCount ();
89104 if (count > 0 ) {
90- return JspHelper .bestNode (locations .get (count - 1 ));
105+ return JspHelper .bestNode (locations .get (0 ));
91106 }
92107 }
93108 }
@@ -98,9 +113,9 @@ private static DatanodeInfo chooseDatanode(final NameNode namenode,
98113 }
99114
100115 private static URI redirectURI (final NameNode namenode ,
101- final String path , final HttpOpParam .Op op ,
116+ final String path , final HttpOpParam .Op op , final long openOffset ,
102117 final Param <?, ?>... parameters ) throws URISyntaxException , IOException {
103- final DatanodeInfo dn = chooseDatanode (namenode , path , op );
118+ final DatanodeInfo dn = chooseDatanode (namenode , path , op , openOffset );
104119 final String query = op .toQueryString () + Param .toSortedString ("&" , parameters );
105120 final String uripath = "/" + WebHdfsFileSystem .PATH_PREFIX + path ;
106121
@@ -148,8 +163,9 @@ public Response put(
148163
149164 if (LOG .isTraceEnabled ()) {
150165 LOG .trace (op + ": " + path
151- + Param .toSortedString (", " , dstPath , owner , group , permission ,
152- overwrite , bufferSize , replication , blockSize ));
166+ + Param .toSortedString (", " , dstPath , owner , group , permission ,
167+ overwrite , bufferSize , replication , blockSize ,
168+ modificationTime , accessTime , renameOptions ));
153169 }
154170
155171 final String fullpath = path .getAbsolutePath ();
@@ -159,7 +175,7 @@ public Response put(
159175 switch (op .getValue ()) {
160176 case CREATE :
161177 {
162- final URI uri = redirectURI (namenode , fullpath , op .getValue (),
178+ final URI uri = redirectURI (namenode , fullpath , op .getValue (), - 1L ,
163179 permission , overwrite , bufferSize , replication , blockSize );
164180 return Response .temporaryRedirect (uri ).build ();
165181 }
@@ -234,7 +250,8 @@ public Response post(
234250 switch (op .getValue ()) {
235251 case APPEND :
236252 {
237- final URI uri = redirectURI (namenode , fullpath , op .getValue (), bufferSize );
253+ final URI uri = redirectURI (namenode , fullpath , op .getValue (), -1L ,
254+ bufferSize );
238255 return Response .temporaryRedirect (uri ).build ();
239256 }
240257 default :
@@ -250,9 +267,15 @@ public Response post(
250267 @ Produces ({MediaType .APPLICATION_OCTET_STREAM , MediaType .APPLICATION_JSON })
251268 public Response root (
252269 @ QueryParam (GetOpParam .NAME ) @ DefaultValue (GetOpParam .DEFAULT )
253- final GetOpParam op
254- ) throws IOException {
255- return get (ROOT , op );
270+ final GetOpParam op ,
271+ @ QueryParam (OffsetParam .NAME ) @ DefaultValue (OffsetParam .DEFAULT )
272+ final OffsetParam offset ,
273+ @ QueryParam (LengthParam .NAME ) @ DefaultValue (LengthParam .DEFAULT )
274+ final LengthParam length ,
275+ @ QueryParam (BufferSizeParam .NAME ) @ DefaultValue (BufferSizeParam .DEFAULT )
276+ final BufferSizeParam bufferSize
277+ ) throws IOException , URISyntaxException {
278+ return get (ROOT , op , offset , length , bufferSize );
256279 }
257280
258281 /** Handle HTTP GET request. */
@@ -262,27 +285,89 @@ public Response root(
262285 public Response get (
263286 @ PathParam (UriFsPathParam .NAME ) final UriFsPathParam path ,
264287 @ QueryParam (GetOpParam .NAME ) @ DefaultValue (GetOpParam .DEFAULT )
265- final GetOpParam op
266- ) throws IOException {
288+ final GetOpParam op ,
289+ @ QueryParam (OffsetParam .NAME ) @ DefaultValue (OffsetParam .DEFAULT )
290+ final OffsetParam offset ,
291+ @ QueryParam (LengthParam .NAME ) @ DefaultValue (LengthParam .DEFAULT )
292+ final LengthParam length ,
293+ @ QueryParam (BufferSizeParam .NAME ) @ DefaultValue (BufferSizeParam .DEFAULT )
294+ final BufferSizeParam bufferSize
295+ ) throws IOException , URISyntaxException {
267296
268297 if (LOG .isTraceEnabled ()) {
269298 LOG .trace (op + ", " + path
270- + Param .toSortedString (", " ));
299+ + Param .toSortedString (", " , offset , length , bufferSize ));
271300 }
272301
302+ final NameNode namenode = (NameNode )context .getAttribute ("name.node" );
303+ final String fullpath = path .getAbsolutePath ();
304+ final NamenodeProtocols np = namenode .getRpcServer ();
305+
273306 switch (op .getValue ()) {
307+ case OPEN :
308+ {
309+ final URI uri = redirectURI (namenode , fullpath , op .getValue (),
310+ offset .getValue (), offset , length , bufferSize );
311+ return Response .temporaryRedirect (uri ).build ();
312+ }
274313 case GETFILESTATUS :
275- final NameNode namenode = (NameNode )context .getAttribute ("name.node" );
276- final String fullpath = path .getAbsolutePath ();
277- final HdfsFileStatus status = namenode .getRpcServer ().getFileInfo (fullpath );
314+ {
315+ final HdfsFileStatus status = np .getFileInfo (fullpath );
278316 final String js = JsonUtil .toJsonString (status );
279317 return Response .ok (js ).type (MediaType .APPLICATION_JSON ).build ();
280-
318+ }
319+ case LISTSTATUS :
320+ {
321+ final StreamingOutput streaming = getListingStream (np , fullpath );
322+ return Response .ok (streaming ).type (MediaType .APPLICATION_JSON ).build ();
323+ }
281324 default :
282325 throw new UnsupportedOperationException (op + " is not supported" );
283326 }
284327 }
285328
329+ private static DirectoryListing getDirectoryListing (final NamenodeProtocols np ,
330+ final String p , byte [] startAfter ) throws IOException {
331+ final DirectoryListing listing = np .getListing (p , startAfter , false );
332+ if (listing == null ) { // the directory does not exist
333+ throw new FileNotFoundException ("File " + p + " does not exist." );
334+ }
335+ return listing ;
336+ }
337+
338+ private static StreamingOutput getListingStream (final NamenodeProtocols np ,
339+ final String p ) throws IOException {
340+ final DirectoryListing first = getDirectoryListing (np , p ,
341+ HdfsFileStatus .EMPTY_NAME );
342+
343+ return new StreamingOutput () {
344+ @ Override
345+ public void write (final OutputStream outstream ) throws IOException {
346+ final PrintStream out = new PrintStream (outstream );
347+ out .print ('[' );
348+
349+ final HdfsFileStatus [] partial = first .getPartialListing ();
350+ if (partial .length > 0 ) {
351+ out .print (JsonUtil .toJsonString (partial [0 ]));
352+ }
353+ for (int i = 1 ; i < partial .length ; i ++) {
354+ out .println (',' );
355+ out .print (JsonUtil .toJsonString (partial [i ]));
356+ }
357+
358+ for (DirectoryListing curr = first ; curr .hasMore (); ) {
359+ curr = getDirectoryListing (np , p , curr .getLastName ());
360+ for (HdfsFileStatus s : curr .getPartialListing ()) {
361+ out .println (',' );
362+ out .print (JsonUtil .toJsonString (s ));
363+ }
364+ }
365+
366+ out .println (']' );
367+ }
368+ };
369+ }
370+
286371 /** Handle HTTP DELETE request. */
287372 @ DELETE
288373 @ Path ("{path:.*}" )
0 commit comments