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 */
018package org.apache.hadoop.hdfs.server.namenode.web.resources;
019
020import java.io.FileNotFoundException;
021import java.io.IOException;
022import java.io.OutputStream;
023import java.io.PrintStream;
024import java.net.URI;
025import java.net.URISyntaxException;
026import java.security.PrivilegedExceptionAction;
027import java.util.EnumSet;
028
029import javax.servlet.ServletContext;
030import javax.servlet.http.HttpServletRequest;
031import javax.servlet.http.HttpServletResponse;
032import javax.ws.rs.Consumes;
033import javax.ws.rs.DELETE;
034import javax.ws.rs.DefaultValue;
035import javax.ws.rs.GET;
036import javax.ws.rs.POST;
037import javax.ws.rs.PUT;
038import javax.ws.rs.Path;
039import javax.ws.rs.PathParam;
040import javax.ws.rs.Produces;
041import javax.ws.rs.QueryParam;
042import javax.ws.rs.core.Context;
043import javax.ws.rs.core.MediaType;
044import javax.ws.rs.core.Response;
045import javax.ws.rs.core.StreamingOutput;
046
047import org.apache.commons.logging.Log;
048import org.apache.commons.logging.LogFactory;
049import org.apache.hadoop.conf.Configuration;
050import org.apache.hadoop.fs.ContentSummary;
051import org.apache.hadoop.fs.FileStatus;
052import org.apache.hadoop.fs.Options;
053import org.apache.hadoop.hdfs.protocol.DatanodeInfo;
054import org.apache.hadoop.hdfs.protocol.DirectoryListing;
055import org.apache.hadoop.hdfs.protocol.HdfsFileStatus;
056import org.apache.hadoop.hdfs.protocol.LocatedBlocks;
057import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier;
058import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenSecretManager;
059import org.apache.hadoop.hdfs.server.blockmanagement.DatanodeDescriptor;
060import org.apache.hadoop.hdfs.server.common.JspHelper;
061import org.apache.hadoop.hdfs.server.namenode.NameNode;
062import org.apache.hadoop.hdfs.server.protocol.NamenodeProtocols;
063import org.apache.hadoop.hdfs.web.JsonUtil;
064import org.apache.hadoop.hdfs.web.ParamFilter;
065import org.apache.hadoop.hdfs.web.WebHdfsFileSystem;
066import org.apache.hadoop.hdfs.web.resources.AccessTimeParam;
067import org.apache.hadoop.hdfs.web.resources.BlockSizeParam;
068import org.apache.hadoop.hdfs.web.resources.BufferSizeParam;
069import org.apache.hadoop.hdfs.web.resources.CreateParentParam;
070import org.apache.hadoop.hdfs.web.resources.DelegationParam;
071import org.apache.hadoop.hdfs.web.resources.DeleteOpParam;
072import org.apache.hadoop.hdfs.web.resources.DestinationParam;
073import org.apache.hadoop.hdfs.web.resources.DoAsParam;
074import org.apache.hadoop.hdfs.web.resources.GetOpParam;
075import org.apache.hadoop.hdfs.web.resources.GroupParam;
076import org.apache.hadoop.hdfs.web.resources.HttpOpParam;
077import org.apache.hadoop.hdfs.web.resources.LengthParam;
078import org.apache.hadoop.hdfs.web.resources.ModificationTimeParam;
079import org.apache.hadoop.hdfs.web.resources.NamenodeRpcAddressParam;
080import org.apache.hadoop.hdfs.web.resources.OffsetParam;
081import org.apache.hadoop.hdfs.web.resources.OverwriteParam;
082import org.apache.hadoop.hdfs.web.resources.OwnerParam;
083import org.apache.hadoop.hdfs.web.resources.Param;
084import org.apache.hadoop.hdfs.web.resources.PermissionParam;
085import org.apache.hadoop.hdfs.web.resources.PostOpParam;
086import org.apache.hadoop.hdfs.web.resources.PutOpParam;
087import org.apache.hadoop.hdfs.web.resources.RecursiveParam;
088import org.apache.hadoop.hdfs.web.resources.RenameOptionSetParam;
089import org.apache.hadoop.hdfs.web.resources.RenewerParam;
090import org.apache.hadoop.hdfs.web.resources.ReplicationParam;
091import org.apache.hadoop.hdfs.web.resources.TokenArgumentParam;
092import org.apache.hadoop.hdfs.web.resources.UriFsPathParam;
093import org.apache.hadoop.hdfs.web.resources.UserParam;
094import org.apache.hadoop.net.NodeBase;
095import org.apache.hadoop.security.Credentials;
096import org.apache.hadoop.security.SecurityUtil;
097import org.apache.hadoop.security.UserGroupInformation;
098import org.apache.hadoop.security.token.Token;
099import org.apache.hadoop.security.token.TokenIdentifier;
100
101import com.sun.jersey.spi.container.ResourceFilters;
102
103/** Web-hdfs NameNode implementation. */
104@Path("")
105@ResourceFilters(ParamFilter.class)
106public class NamenodeWebHdfsMethods {
107  public static final Log LOG = LogFactory.getLog(NamenodeWebHdfsMethods.class);
108
109  private static final UriFsPathParam ROOT = new UriFsPathParam("");
110  
111  private static final ThreadLocal<String> REMOTE_ADDRESS = new ThreadLocal<String>(); 
112
113  /** @return the remote client address. */
114  public static String getRemoteAddress() {
115    return REMOTE_ADDRESS.get();
116  }
117
118  private @Context ServletContext context;
119  private @Context HttpServletRequest request;
120  private @Context HttpServletResponse response;
121
122  private void init(final UserGroupInformation ugi,
123      final DelegationParam delegation,
124      final UserParam username, final DoAsParam doAsUser,
125      final UriFsPathParam path, final HttpOpParam<?> op,
126      final Param<?, ?>... parameters) throws IOException {
127    if (LOG.isTraceEnabled()) {
128      LOG.trace("HTTP " + op.getValue().getType() + ": " + op + ", " + path
129          + ", ugi=" + ugi + ", " + username + ", " + doAsUser
130          + Param.toSortedString(", ", parameters));
131    }
132
133    //clear content type
134    response.setContentType(null);
135  }
136
137  private static DatanodeInfo chooseDatanode(final NameNode namenode,
138      final String path, final HttpOpParam.Op op, final long openOffset,
139      Configuration conf) throws IOException {
140    if (op == GetOpParam.Op.OPEN
141        || op == GetOpParam.Op.GETFILECHECKSUM
142        || op == PostOpParam.Op.APPEND) {
143      final NamenodeProtocols np = namenode.getRpcServer();
144      final HdfsFileStatus status = np.getFileInfo(path);
145      if (status == null) {
146        throw new FileNotFoundException("File " + path + " not found.");
147      }
148      final long len = status.getLen();
149      if (op == GetOpParam.Op.OPEN) {
150        if (openOffset < 0L || (openOffset >= len && len > 0)) {
151          throw new IOException("Offset=" + openOffset
152              + " out of the range [0, " + len + "); " + op + ", path=" + path);
153        }
154      }
155
156      if (len > 0) {
157        final long offset = op == GetOpParam.Op.OPEN? openOffset: len - 1;
158        final LocatedBlocks locations = np.getBlockLocations(path, offset, 1);
159        final int count = locations.locatedBlockCount();
160        if (count > 0) {
161          return JspHelper.bestNode(locations.get(0), conf);
162        }
163      }
164    } 
165
166    return (DatanodeDescriptor)namenode.getNamesystem().getBlockManager(
167        ).getDatanodeManager().getNetworkTopology().chooseRandom(
168        NodeBase.ROOT);
169  }
170
171  private Token<? extends TokenIdentifier> generateDelegationToken(
172      final NameNode namenode, final UserGroupInformation ugi,
173      final String renewer) throws IOException {
174    final Credentials c = DelegationTokenSecretManager.createCredentials(
175        namenode, ugi, renewer != null? renewer: ugi.getShortUserName());
176    final Token<? extends TokenIdentifier> t = c.getAllTokens().iterator().next();
177    t.setKind(WebHdfsFileSystem.TOKEN_KIND);
178    SecurityUtil.setTokenService(t, namenode.getHttpAddress());
179    return t;
180  }
181
182  private URI redirectURI(final NameNode namenode,
183      final UserGroupInformation ugi, final DelegationParam delegation,
184      final UserParam username, final DoAsParam doAsUser,
185      final String path, final HttpOpParam.Op op, final long openOffset,
186      final Param<?, ?>... parameters) throws URISyntaxException, IOException {
187    final Configuration conf = (Configuration)context.getAttribute(JspHelper.CURRENT_CONF);
188    final DatanodeInfo dn = chooseDatanode(namenode, path, op, openOffset, conf);
189
190    final String delegationQuery;
191    if (!UserGroupInformation.isSecurityEnabled()) {
192      //security disabled
193      delegationQuery = Param.toSortedString("&", doAsUser, username);
194    } else if (delegation.getValue() != null) {
195      //client has provided a token
196      delegationQuery = "&" + delegation;
197    } else {
198      //generate a token
199      final Token<? extends TokenIdentifier> t = generateDelegationToken(
200          namenode, ugi, request.getUserPrincipal().getName());
201      delegationQuery = "&" + new DelegationParam(t.encodeToUrlString());
202    }
203    final String query = op.toQueryString() + delegationQuery
204        + "&" + new NamenodeRpcAddressParam(namenode)
205        + Param.toSortedString("&", parameters);
206    final String uripath = WebHdfsFileSystem.PATH_PREFIX + path;
207
208    final URI uri = new URI("http", null, dn.getHostName(), dn.getInfoPort(),
209        uripath, query, null);
210    if (LOG.isTraceEnabled()) {
211      LOG.trace("redirectURI=" + uri);
212    }
213    return uri;
214  }
215
216  /** Handle HTTP PUT request for the root. */
217  @PUT
218  @Path("/")
219  @Consumes({"*/*"})
220  @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON})
221  public Response putRoot(
222      @Context final UserGroupInformation ugi,
223      @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT)
224          final DelegationParam delegation,
225      @QueryParam(UserParam.NAME) @DefaultValue(UserParam.DEFAULT)
226          final UserParam username,
227      @QueryParam(DoAsParam.NAME) @DefaultValue(DoAsParam.DEFAULT)
228          final DoAsParam doAsUser,
229      @QueryParam(PutOpParam.NAME) @DefaultValue(PutOpParam.DEFAULT)
230          final PutOpParam op,
231      @QueryParam(DestinationParam.NAME) @DefaultValue(DestinationParam.DEFAULT)
232          final DestinationParam destination,
233      @QueryParam(OwnerParam.NAME) @DefaultValue(OwnerParam.DEFAULT)
234          final OwnerParam owner,
235      @QueryParam(GroupParam.NAME) @DefaultValue(GroupParam.DEFAULT)
236          final GroupParam group,
237      @QueryParam(PermissionParam.NAME) @DefaultValue(PermissionParam.DEFAULT)
238          final PermissionParam permission,
239      @QueryParam(OverwriteParam.NAME) @DefaultValue(OverwriteParam.DEFAULT)
240          final OverwriteParam overwrite,
241      @QueryParam(BufferSizeParam.NAME) @DefaultValue(BufferSizeParam.DEFAULT)
242          final BufferSizeParam bufferSize,
243      @QueryParam(ReplicationParam.NAME) @DefaultValue(ReplicationParam.DEFAULT)
244          final ReplicationParam replication,
245      @QueryParam(BlockSizeParam.NAME) @DefaultValue(BlockSizeParam.DEFAULT)
246          final BlockSizeParam blockSize,
247      @QueryParam(ModificationTimeParam.NAME) @DefaultValue(ModificationTimeParam.DEFAULT)
248          final ModificationTimeParam modificationTime,
249      @QueryParam(AccessTimeParam.NAME) @DefaultValue(AccessTimeParam.DEFAULT)
250          final AccessTimeParam accessTime,
251      @QueryParam(RenameOptionSetParam.NAME) @DefaultValue(RenameOptionSetParam.DEFAULT)
252          final RenameOptionSetParam renameOptions,
253      @QueryParam(CreateParentParam.NAME) @DefaultValue(CreateParentParam.DEFAULT)
254          final CreateParentParam createParent,
255      @QueryParam(TokenArgumentParam.NAME) @DefaultValue(TokenArgumentParam.DEFAULT)
256          final TokenArgumentParam delegationTokenArgument
257      ) throws IOException, InterruptedException {
258    return put(ugi, delegation, username, doAsUser, ROOT, op, destination,
259        owner, group, permission, overwrite, bufferSize, replication,
260        blockSize, modificationTime, accessTime, renameOptions, createParent,
261        delegationTokenArgument);
262  }
263
264  /** Handle HTTP PUT request. */
265  @PUT
266  @Path("{" + UriFsPathParam.NAME + ":.*}")
267  @Consumes({"*/*"})
268  @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON})
269  public Response put(
270      @Context final UserGroupInformation ugi,
271      @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT)
272          final DelegationParam delegation,
273      @QueryParam(UserParam.NAME) @DefaultValue(UserParam.DEFAULT)
274          final UserParam username,
275      @QueryParam(DoAsParam.NAME) @DefaultValue(DoAsParam.DEFAULT)
276          final DoAsParam doAsUser,
277      @PathParam(UriFsPathParam.NAME) final UriFsPathParam path,
278      @QueryParam(PutOpParam.NAME) @DefaultValue(PutOpParam.DEFAULT)
279          final PutOpParam op,
280      @QueryParam(DestinationParam.NAME) @DefaultValue(DestinationParam.DEFAULT)
281          final DestinationParam destination,
282      @QueryParam(OwnerParam.NAME) @DefaultValue(OwnerParam.DEFAULT)
283          final OwnerParam owner,
284      @QueryParam(GroupParam.NAME) @DefaultValue(GroupParam.DEFAULT)
285          final GroupParam group,
286      @QueryParam(PermissionParam.NAME) @DefaultValue(PermissionParam.DEFAULT)
287          final PermissionParam permission,
288      @QueryParam(OverwriteParam.NAME) @DefaultValue(OverwriteParam.DEFAULT)
289          final OverwriteParam overwrite,
290      @QueryParam(BufferSizeParam.NAME) @DefaultValue(BufferSizeParam.DEFAULT)
291          final BufferSizeParam bufferSize,
292      @QueryParam(ReplicationParam.NAME) @DefaultValue(ReplicationParam.DEFAULT)
293          final ReplicationParam replication,
294      @QueryParam(BlockSizeParam.NAME) @DefaultValue(BlockSizeParam.DEFAULT)
295          final BlockSizeParam blockSize,
296      @QueryParam(ModificationTimeParam.NAME) @DefaultValue(ModificationTimeParam.DEFAULT)
297          final ModificationTimeParam modificationTime,
298      @QueryParam(AccessTimeParam.NAME) @DefaultValue(AccessTimeParam.DEFAULT)
299          final AccessTimeParam accessTime,
300      @QueryParam(RenameOptionSetParam.NAME) @DefaultValue(RenameOptionSetParam.DEFAULT)
301          final RenameOptionSetParam renameOptions,
302      @QueryParam(CreateParentParam.NAME) @DefaultValue(CreateParentParam.DEFAULT)
303          final CreateParentParam createParent,
304      @QueryParam(TokenArgumentParam.NAME) @DefaultValue(TokenArgumentParam.DEFAULT)
305          final TokenArgumentParam delegationTokenArgument
306      ) throws IOException, InterruptedException {
307
308    init(ugi, delegation, username, doAsUser, path, op, destination, owner,
309        group, permission, overwrite, bufferSize, replication, blockSize,
310        modificationTime, accessTime, renameOptions, delegationTokenArgument);
311
312    return ugi.doAs(new PrivilegedExceptionAction<Response>() {
313      @Override
314      public Response run() throws IOException, URISyntaxException {
315        REMOTE_ADDRESS.set(request.getRemoteAddr());
316        try {
317
318    final String fullpath = path.getAbsolutePath();
319    final Configuration conf = (Configuration)context.getAttribute(JspHelper.CURRENT_CONF);
320    final NameNode namenode = (NameNode)context.getAttribute("name.node");
321    final NamenodeProtocols np = namenode.getRpcServer();
322
323    switch(op.getValue()) {
324    case CREATE:
325    {
326      final URI uri = redirectURI(namenode, ugi, delegation, username, doAsUser,
327          fullpath, op.getValue(), -1L,
328          permission, overwrite, bufferSize, replication, blockSize);
329      return Response.temporaryRedirect(uri).type(MediaType.APPLICATION_OCTET_STREAM).build();
330    } 
331    case MKDIRS:
332    {
333      final boolean b = np.mkdirs(fullpath, permission.getFsPermission(), true);
334      final String js = JsonUtil.toJsonString("boolean", b);
335      return Response.ok(js).type(MediaType.APPLICATION_JSON).build();
336    }
337    case CREATESYMLINK:
338    {
339      np.createSymlink(destination.getValue(), fullpath,
340          PermissionParam.getDefaultFsPermission(), createParent.getValue());
341      return Response.ok().type(MediaType.APPLICATION_OCTET_STREAM).build();
342    }
343    case RENAME:
344    {
345      final EnumSet<Options.Rename> s = renameOptions.getValue();
346      if (s.isEmpty()) {
347        final boolean b = np.rename(fullpath, destination.getValue());
348        final String js = JsonUtil.toJsonString("boolean", b);
349        return Response.ok(js).type(MediaType.APPLICATION_JSON).build();
350      } else {
351        np.rename2(fullpath, destination.getValue(),
352            s.toArray(new Options.Rename[s.size()]));
353        return Response.ok().type(MediaType.APPLICATION_OCTET_STREAM).build();
354      }
355    }
356    case SETREPLICATION:
357    {
358      final boolean b = np.setReplication(fullpath, replication.getValue(conf));
359      final String js = JsonUtil.toJsonString("boolean", b);
360      return Response.ok(js).type(MediaType.APPLICATION_JSON).build();
361    }
362    case SETOWNER:
363    {
364      if (owner.getValue() == null && group.getValue() == null) {
365        throw new IllegalArgumentException("Both owner and group are empty.");
366      }
367
368      np.setOwner(fullpath, owner.getValue(), group.getValue());
369      return Response.ok().type(MediaType.APPLICATION_OCTET_STREAM).build();
370    }
371    case SETPERMISSION:
372    {
373      np.setPermission(fullpath, permission.getFsPermission());
374      return Response.ok().type(MediaType.APPLICATION_OCTET_STREAM).build();
375    }
376    case SETTIMES:
377    {
378      np.setTimes(fullpath, modificationTime.getValue(), accessTime.getValue());
379      return Response.ok().type(MediaType.APPLICATION_OCTET_STREAM).build();
380    }
381    case RENEWDELEGATIONTOKEN:
382    {
383      final Token<DelegationTokenIdentifier> token = new Token<DelegationTokenIdentifier>();
384      token.decodeFromUrlString(delegationTokenArgument.getValue());
385      final long expiryTime = np.renewDelegationToken(token);
386      final String js = JsonUtil.toJsonString("long", expiryTime);
387      return Response.ok(js).type(MediaType.APPLICATION_JSON).build();
388    }
389    case CANCELDELEGATIONTOKEN:
390    {
391      final Token<DelegationTokenIdentifier> token = new Token<DelegationTokenIdentifier>();
392      token.decodeFromUrlString(delegationTokenArgument.getValue());
393      np.cancelDelegationToken(token);
394      return Response.ok().type(MediaType.APPLICATION_OCTET_STREAM).build();
395    }
396    default:
397      throw new UnsupportedOperationException(op + " is not supported");
398    }
399
400        } finally {
401          REMOTE_ADDRESS.set(null);
402        }
403      }
404    });
405  }
406
407  /** Handle HTTP POST request for the root. */
408  @POST
409  @Path("/")
410  @Consumes({"*/*"})
411  @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON})
412  public Response postRoot(
413      @Context final UserGroupInformation ugi,
414      @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT)
415          final DelegationParam delegation,
416      @QueryParam(UserParam.NAME) @DefaultValue(UserParam.DEFAULT)
417          final UserParam username,
418      @QueryParam(DoAsParam.NAME) @DefaultValue(DoAsParam.DEFAULT)
419          final DoAsParam doAsUser,
420      @QueryParam(PostOpParam.NAME) @DefaultValue(PostOpParam.DEFAULT)
421          final PostOpParam op,
422      @QueryParam(BufferSizeParam.NAME) @DefaultValue(BufferSizeParam.DEFAULT)
423          final BufferSizeParam bufferSize
424      ) throws IOException, InterruptedException {
425    return post(ugi, delegation, username, doAsUser, ROOT, op, bufferSize);
426  }
427
428  /** Handle HTTP POST request. */
429  @POST
430  @Path("{" + UriFsPathParam.NAME + ":.*}")
431  @Consumes({"*/*"})
432  @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON})
433  public Response post(
434      @Context final UserGroupInformation ugi,
435      @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT)
436          final DelegationParam delegation,
437      @QueryParam(UserParam.NAME) @DefaultValue(UserParam.DEFAULT)
438          final UserParam username,
439      @QueryParam(DoAsParam.NAME) @DefaultValue(DoAsParam.DEFAULT)
440          final DoAsParam doAsUser,
441      @PathParam(UriFsPathParam.NAME) final UriFsPathParam path,
442      @QueryParam(PostOpParam.NAME) @DefaultValue(PostOpParam.DEFAULT)
443          final PostOpParam op,
444      @QueryParam(BufferSizeParam.NAME) @DefaultValue(BufferSizeParam.DEFAULT)
445          final BufferSizeParam bufferSize
446      ) throws IOException, InterruptedException {
447
448    init(ugi, delegation, username, doAsUser, path, op, bufferSize);
449
450    return ugi.doAs(new PrivilegedExceptionAction<Response>() {
451      @Override
452      public Response run() throws IOException, URISyntaxException {
453        REMOTE_ADDRESS.set(request.getRemoteAddr());
454        try {
455
456    final String fullpath = path.getAbsolutePath();
457    final NameNode namenode = (NameNode)context.getAttribute("name.node");
458
459    switch(op.getValue()) {
460    case APPEND:
461    {
462      final URI uri = redirectURI(namenode, ugi, delegation, username, doAsUser,
463          fullpath, op.getValue(), -1L, bufferSize);
464      return Response.temporaryRedirect(uri).type(MediaType.APPLICATION_OCTET_STREAM).build();
465    }
466    default:
467      throw new UnsupportedOperationException(op + " is not supported");
468    }
469
470        } finally {
471          REMOTE_ADDRESS.set(null);
472        }
473      }
474    });
475  }
476
477  /** Handle HTTP GET request for the root. */
478  @GET
479  @Path("/")
480  @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON})
481  public Response getRoot(
482      @Context final UserGroupInformation ugi,
483      @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT)
484          final DelegationParam delegation,
485      @QueryParam(UserParam.NAME) @DefaultValue(UserParam.DEFAULT)
486          final UserParam username,
487      @QueryParam(DoAsParam.NAME) @DefaultValue(DoAsParam.DEFAULT)
488          final DoAsParam doAsUser,
489      @QueryParam(GetOpParam.NAME) @DefaultValue(GetOpParam.DEFAULT)
490          final GetOpParam op,
491      @QueryParam(OffsetParam.NAME) @DefaultValue(OffsetParam.DEFAULT)
492          final OffsetParam offset,
493      @QueryParam(LengthParam.NAME) @DefaultValue(LengthParam.DEFAULT)
494          final LengthParam length,
495      @QueryParam(RenewerParam.NAME) @DefaultValue(RenewerParam.DEFAULT)
496          final RenewerParam renewer,
497      @QueryParam(BufferSizeParam.NAME) @DefaultValue(BufferSizeParam.DEFAULT)
498          final BufferSizeParam bufferSize
499      ) throws IOException, URISyntaxException, InterruptedException {
500    return get(ugi, delegation, username, doAsUser, ROOT, op,
501        offset, length, renewer, bufferSize);
502  }
503
504  /** Handle HTTP GET request. */
505  @GET
506  @Path("{" + UriFsPathParam.NAME + ":.*}")
507  @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON})
508  public Response get(
509      @Context final UserGroupInformation ugi,
510      @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT)
511          final DelegationParam delegation,
512      @QueryParam(UserParam.NAME) @DefaultValue(UserParam.DEFAULT)
513          final UserParam username,
514      @QueryParam(DoAsParam.NAME) @DefaultValue(DoAsParam.DEFAULT)
515          final DoAsParam doAsUser,
516      @PathParam(UriFsPathParam.NAME) final UriFsPathParam path,
517      @QueryParam(GetOpParam.NAME) @DefaultValue(GetOpParam.DEFAULT)
518          final GetOpParam op,
519      @QueryParam(OffsetParam.NAME) @DefaultValue(OffsetParam.DEFAULT)
520          final OffsetParam offset,
521      @QueryParam(LengthParam.NAME) @DefaultValue(LengthParam.DEFAULT)
522          final LengthParam length,
523      @QueryParam(RenewerParam.NAME) @DefaultValue(RenewerParam.DEFAULT)
524          final RenewerParam renewer,
525      @QueryParam(BufferSizeParam.NAME) @DefaultValue(BufferSizeParam.DEFAULT)
526          final BufferSizeParam bufferSize
527      ) throws IOException, InterruptedException {
528
529    init(ugi, delegation, username, doAsUser, path, op,
530        offset, length, renewer, bufferSize);
531
532    return ugi.doAs(new PrivilegedExceptionAction<Response>() {
533      @Override
534      public Response run() throws IOException, URISyntaxException {
535        REMOTE_ADDRESS.set(request.getRemoteAddr());
536        try {
537
538    final NameNode namenode = (NameNode)context.getAttribute("name.node");
539    final String fullpath = path.getAbsolutePath();
540    final NamenodeProtocols np = namenode.getRpcServer();
541
542    switch(op.getValue()) {
543    case OPEN:
544    {
545      final URI uri = redirectURI(namenode, ugi, delegation, username, doAsUser,
546          fullpath, op.getValue(), offset.getValue(), offset, length, bufferSize);
547      return Response.temporaryRedirect(uri).type(MediaType.APPLICATION_OCTET_STREAM).build();
548    }
549    case GET_BLOCK_LOCATIONS:
550    {
551      final long offsetValue = offset.getValue();
552      final Long lengthValue = length.getValue();
553      final LocatedBlocks locatedblocks = np.getBlockLocations(fullpath,
554          offsetValue, lengthValue != null? lengthValue: Long.MAX_VALUE);
555      final String js = JsonUtil.toJsonString(locatedblocks);
556      return Response.ok(js).type(MediaType.APPLICATION_JSON).build();
557    }
558    case GETFILESTATUS:
559    {
560      final HdfsFileStatus status = np.getFileInfo(fullpath);
561      if (status == null) {
562        throw new FileNotFoundException("File does not exist: " + fullpath);
563      }
564
565      final String js = JsonUtil.toJsonString(status, true);
566      return Response.ok(js).type(MediaType.APPLICATION_JSON).build();
567    }
568    case LISTSTATUS:
569    {
570      final StreamingOutput streaming = getListingStream(np, fullpath);
571      return Response.ok(streaming).type(MediaType.APPLICATION_JSON).build();
572    }
573    case GETCONTENTSUMMARY:
574    {
575      final ContentSummary contentsummary = np.getContentSummary(fullpath);
576      final String js = JsonUtil.toJsonString(contentsummary);
577      return Response.ok(js).type(MediaType.APPLICATION_JSON).build();
578    }
579    case GETFILECHECKSUM:
580    {
581      final URI uri = redirectURI(namenode, ugi, delegation, username, doAsUser,
582          fullpath, op.getValue(), -1L);
583      return Response.temporaryRedirect(uri).type(MediaType.APPLICATION_OCTET_STREAM).build();
584    }
585    case GETDELEGATIONTOKEN:
586    {
587      if (delegation.getValue() != null) {
588        throw new IllegalArgumentException(delegation.getName()
589            + " parameter is not null.");
590      }
591      final Token<? extends TokenIdentifier> token = generateDelegationToken(
592          namenode, ugi, renewer.getValue());
593      final String js = JsonUtil.toJsonString(token);
594      return Response.ok(js).type(MediaType.APPLICATION_JSON).build();
595    }
596    case GETHOMEDIRECTORY:
597    {
598      final String js = JsonUtil.toJsonString(
599          org.apache.hadoop.fs.Path.class.getSimpleName(),
600          WebHdfsFileSystem.getHomeDirectoryString(ugi));
601      return Response.ok(js).type(MediaType.APPLICATION_JSON).build();
602    }
603    default:
604      throw new UnsupportedOperationException(op + " is not supported");
605    }    
606
607        } finally {
608          REMOTE_ADDRESS.set(null);
609        }
610      }
611    });
612  }
613
614  private static DirectoryListing getDirectoryListing(final NamenodeProtocols np,
615      final String p, byte[] startAfter) throws IOException {
616    final DirectoryListing listing = np.getListing(p, startAfter, false);
617    if (listing == null) { // the directory does not exist
618      throw new FileNotFoundException("File " + p + " does not exist.");
619    }
620    return listing;
621  }
622  
623  private static StreamingOutput getListingStream(final NamenodeProtocols np, 
624      final String p) throws IOException {
625    // allows exceptions like FNF or ACE to prevent http response of 200 for
626    // a failure since we can't (currently) return error responses in the
627    // middle of a streaming operation
628    final DirectoryListing firstDirList = getDirectoryListing(np, p,
629        HdfsFileStatus.EMPTY_NAME);
630
631    // must save ugi because the streaming object will be executed outside
632    // the remote user's ugi
633    final UserGroupInformation ugi = UserGroupInformation.getCurrentUser();
634    return new StreamingOutput() {
635      @Override
636      public void write(final OutputStream outstream) throws IOException {
637        final PrintStream out = new PrintStream(outstream);
638        out.println("{\"" + FileStatus.class.getSimpleName() + "es\":{\""
639            + FileStatus.class.getSimpleName() + "\":[");
640
641        try {
642          // restore remote user's ugi
643          ugi.doAs(new PrivilegedExceptionAction<Void>() {
644            @Override
645            public Void run() throws IOException {
646              long n = 0;
647              for (DirectoryListing dirList = firstDirList; ;
648                   dirList = getDirectoryListing(np, p, dirList.getLastName())
649              ) {
650                // send each segment of the directory listing
651                for (HdfsFileStatus s : dirList.getPartialListing()) {
652                  if (n++ > 0) {
653                    out.println(',');
654                  }
655                  out.print(JsonUtil.toJsonString(s, false));
656                }
657                // stop if last segment
658                if (!dirList.hasMore()) {
659                  break;
660                }
661              }
662              return null;
663            }
664          });
665        } catch (InterruptedException e) {
666          throw new IOException(e);
667        }
668        
669        out.println();
670        out.println("]}}");
671      }
672    };
673  }
674
675  /** Handle HTTP DELETE request for the root. */
676  @DELETE
677  @Path("/")
678  @Produces(MediaType.APPLICATION_JSON)
679  public Response deleteRoot(
680      @Context final UserGroupInformation ugi,
681      @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT)
682          final DelegationParam delegation,
683      @QueryParam(UserParam.NAME) @DefaultValue(UserParam.DEFAULT)
684          final UserParam username,
685      @QueryParam(DoAsParam.NAME) @DefaultValue(DoAsParam.DEFAULT)
686          final DoAsParam doAsUser,
687      @QueryParam(DeleteOpParam.NAME) @DefaultValue(DeleteOpParam.DEFAULT)
688          final DeleteOpParam op,
689      @QueryParam(RecursiveParam.NAME) @DefaultValue(RecursiveParam.DEFAULT)
690          final RecursiveParam recursive
691      ) throws IOException, InterruptedException {
692    return delete(ugi, delegation, username, doAsUser, ROOT, op, recursive);
693  }
694
695  /** Handle HTTP DELETE request. */
696  @DELETE
697  @Path("{" + UriFsPathParam.NAME + ":.*}")
698  @Produces(MediaType.APPLICATION_JSON)
699  public Response delete(
700      @Context final UserGroupInformation ugi,
701      @QueryParam(DelegationParam.NAME) @DefaultValue(DelegationParam.DEFAULT)
702          final DelegationParam delegation,
703      @QueryParam(UserParam.NAME) @DefaultValue(UserParam.DEFAULT)
704          final UserParam username,
705      @QueryParam(DoAsParam.NAME) @DefaultValue(DoAsParam.DEFAULT)
706          final DoAsParam doAsUser,
707      @PathParam(UriFsPathParam.NAME) final UriFsPathParam path,
708      @QueryParam(DeleteOpParam.NAME) @DefaultValue(DeleteOpParam.DEFAULT)
709          final DeleteOpParam op,
710      @QueryParam(RecursiveParam.NAME) @DefaultValue(RecursiveParam.DEFAULT)
711          final RecursiveParam recursive
712      ) throws IOException, InterruptedException {
713
714    init(ugi, delegation, username, doAsUser, path, op, recursive);
715
716    return ugi.doAs(new PrivilegedExceptionAction<Response>() {
717      @Override
718      public Response run() throws IOException {
719        REMOTE_ADDRESS.set(request.getRemoteAddr());
720        try {
721
722        final NameNode namenode = (NameNode)context.getAttribute("name.node");
723        final String fullpath = path.getAbsolutePath();
724
725        switch(op.getValue()) {
726        case DELETE:
727        {
728          final boolean b = namenode.getRpcServer().delete(fullpath, recursive.getValue());
729          final String js = JsonUtil.toJsonString("boolean", b);
730          return Response.ok(js).type(MediaType.APPLICATION_JSON).build();
731        }
732        default:
733          throw new UnsupportedOperationException(op + " is not supported");
734        }
735
736        } finally {
737          REMOTE_ADDRESS.set(null);
738        }
739      }
740    });
741  }
742}