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.fs.http.client;
019
020import java.util.ArrayList;
021import java.util.EnumSet;
022import java.util.List;
023import org.apache.hadoop.classification.InterfaceAudience;
024import org.apache.hadoop.conf.Configuration;
025import org.apache.hadoop.fs.ContentSummary;
026import org.apache.hadoop.fs.DelegationTokenRenewer;
027import org.apache.hadoop.fs.FSDataInputStream;
028import org.apache.hadoop.fs.FSDataOutputStream;
029import org.apache.hadoop.fs.FileChecksum;
030import org.apache.hadoop.fs.FileStatus;
031import org.apache.hadoop.fs.FileSystem;
032import org.apache.hadoop.fs.Path;
033import org.apache.hadoop.fs.PositionedReadable;
034import org.apache.hadoop.fs.Seekable;
035import org.apache.hadoop.fs.XAttrCodec;
036import org.apache.hadoop.fs.XAttrSetFlag;
037import org.apache.hadoop.fs.permission.AclEntry;
038import org.apache.hadoop.fs.permission.AclStatus;
039import org.apache.hadoop.fs.permission.FsPermission;
040import org.apache.hadoop.hdfs.DFSConfigKeys;
041import org.apache.hadoop.lib.wsrs.EnumSetParam;
042import org.apache.hadoop.security.UserGroupInformation;
043import org.apache.hadoop.security.token.Token;
044import org.apache.hadoop.security.token.TokenIdentifier;
045import org.apache.hadoop.security.token.delegation.web.DelegationTokenAuthenticatedURL;
046import org.apache.hadoop.security.token.delegation.web.DelegationTokenAuthenticator;
047import org.apache.hadoop.security.token.delegation.web.KerberosDelegationTokenAuthenticator;
048import org.apache.hadoop.util.HttpExceptionUtils;
049import org.apache.hadoop.util.Progressable;
050import org.apache.hadoop.util.ReflectionUtils;
051import org.apache.hadoop.util.StringUtils;
052import org.json.simple.JSONArray;
053import org.json.simple.JSONObject;
054import org.json.simple.parser.JSONParser;
055import org.json.simple.parser.ParseException;
056
057import com.google.common.base.Preconditions;
058import com.google.common.collect.Lists;
059import com.google.common.collect.Maps;
060
061import java.io.BufferedInputStream;
062import java.io.BufferedOutputStream;
063import java.io.DataInput;
064import java.io.DataOutput;
065import java.io.FileNotFoundException;
066import java.io.FilterInputStream;
067import java.io.IOException;
068import java.io.InputStream;
069import java.io.OutputStream;
070import java.net.HttpURLConnection;
071import java.net.URI;
072import java.net.URISyntaxException;
073import java.net.URL;
074import java.security.PrivilegedExceptionAction;
075import java.text.MessageFormat;
076import java.util.HashMap;
077import java.util.Map;
078
079/**
080 * HttpFSServer implementation of the FileSystemAccess FileSystem.
081 * <p>
082 * This implementation allows a user to access HDFS over HTTP via a HttpFSServer server.
083 */
084@InterfaceAudience.Private
085public class HttpFSFileSystem extends FileSystem
086  implements DelegationTokenRenewer.Renewable {
087
088  public static final String SERVICE_NAME = HttpFSUtils.SERVICE_NAME;
089
090  public static final String SERVICE_VERSION = HttpFSUtils.SERVICE_VERSION;
091
092  public static final String SCHEME = "webhdfs";
093
094  public static final String OP_PARAM = "op";
095  public static final String DO_AS_PARAM = "doas";
096  public static final String OVERWRITE_PARAM = "overwrite";
097  public static final String REPLICATION_PARAM = "replication";
098  public static final String BLOCKSIZE_PARAM = "blocksize";
099  public static final String PERMISSION_PARAM = "permission";
100  public static final String ACLSPEC_PARAM = "aclspec";
101  public static final String DESTINATION_PARAM = "destination";
102  public static final String RECURSIVE_PARAM = "recursive";
103  public static final String SOURCES_PARAM = "sources";
104  public static final String OWNER_PARAM = "owner";
105  public static final String GROUP_PARAM = "group";
106  public static final String MODIFICATION_TIME_PARAM = "modificationtime";
107  public static final String ACCESS_TIME_PARAM = "accesstime";
108  public static final String XATTR_NAME_PARAM = "xattr.name";
109  public static final String XATTR_VALUE_PARAM = "xattr.value";
110  public static final String XATTR_SET_FLAG_PARAM = "flag";
111  public static final String XATTR_ENCODING_PARAM = "encoding";
112  public static final String NEW_LENGTH_PARAM = "newlength";
113
114  public static final Short DEFAULT_PERMISSION = 0755;
115  public static final String ACLSPEC_DEFAULT = "";
116
117  public static final String RENAME_JSON = "boolean";
118
119  public static final String TRUNCATE_JSON = "boolean";
120
121  public static final String DELETE_JSON = "boolean";
122
123  public static final String MKDIRS_JSON = "boolean";
124
125  public static final String HOME_DIR_JSON = "Path";
126
127  public static final String SET_REPLICATION_JSON = "boolean";
128
129  public static final String UPLOAD_CONTENT_TYPE= "application/octet-stream";
130
131  public static enum FILE_TYPE {
132    FILE, DIRECTORY, SYMLINK;
133
134    public static FILE_TYPE getType(FileStatus fileStatus) {
135      if (fileStatus.isFile()) {
136        return FILE;
137      }
138      if (fileStatus.isDirectory()) {
139        return DIRECTORY;
140      }
141      if (fileStatus.isSymlink()) {
142        return SYMLINK;
143      }
144      throw new IllegalArgumentException("Could not determine filetype for: " +
145                                         fileStatus.getPath());
146    }
147  }
148
149  public static final String FILE_STATUSES_JSON = "FileStatuses";
150  public static final String FILE_STATUS_JSON = "FileStatus";
151  public static final String PATH_SUFFIX_JSON = "pathSuffix";
152  public static final String TYPE_JSON = "type";
153  public static final String LENGTH_JSON = "length";
154  public static final String OWNER_JSON = "owner";
155  public static final String GROUP_JSON = "group";
156  public static final String PERMISSION_JSON = "permission";
157  public static final String ACCESS_TIME_JSON = "accessTime";
158  public static final String MODIFICATION_TIME_JSON = "modificationTime";
159  public static final String BLOCK_SIZE_JSON = "blockSize";
160  public static final String REPLICATION_JSON = "replication";
161  public static final String XATTRS_JSON = "XAttrs";
162  public static final String XATTR_NAME_JSON = "name";
163  public static final String XATTR_VALUE_JSON = "value";
164  public static final String XATTRNAMES_JSON = "XAttrNames";
165
166  public static final String FILE_CHECKSUM_JSON = "FileChecksum";
167  public static final String CHECKSUM_ALGORITHM_JSON = "algorithm";
168  public static final String CHECKSUM_BYTES_JSON = "bytes";
169  public static final String CHECKSUM_LENGTH_JSON = "length";
170
171  public static final String CONTENT_SUMMARY_JSON = "ContentSummary";
172  public static final String CONTENT_SUMMARY_DIRECTORY_COUNT_JSON = "directoryCount";
173  public static final String CONTENT_SUMMARY_FILE_COUNT_JSON = "fileCount";
174  public static final String CONTENT_SUMMARY_LENGTH_JSON = "length";
175  public static final String CONTENT_SUMMARY_QUOTA_JSON = "quota";
176  public static final String CONTENT_SUMMARY_SPACE_CONSUMED_JSON = "spaceConsumed";
177  public static final String CONTENT_SUMMARY_SPACE_QUOTA_JSON = "spaceQuota";
178
179  public static final String ACL_STATUS_JSON = "AclStatus";
180  public static final String ACL_STICKY_BIT_JSON = "stickyBit";
181  public static final String ACL_ENTRIES_JSON = "entries";
182  public static final String ACL_BIT_JSON = "aclBit";
183
184  public static final int HTTP_TEMPORARY_REDIRECT = 307;
185
186  private static final String HTTP_GET = "GET";
187  private static final String HTTP_PUT = "PUT";
188  private static final String HTTP_POST = "POST";
189  private static final String HTTP_DELETE = "DELETE";
190
191  @InterfaceAudience.Private
192  public static enum Operation {
193    OPEN(HTTP_GET), GETFILESTATUS(HTTP_GET), LISTSTATUS(HTTP_GET),
194    GETHOMEDIRECTORY(HTTP_GET), GETCONTENTSUMMARY(HTTP_GET),
195    GETFILECHECKSUM(HTTP_GET),  GETFILEBLOCKLOCATIONS(HTTP_GET),
196    INSTRUMENTATION(HTTP_GET), GETACLSTATUS(HTTP_GET),
197    APPEND(HTTP_POST), CONCAT(HTTP_POST), TRUNCATE(HTTP_POST),
198    CREATE(HTTP_PUT), MKDIRS(HTTP_PUT), RENAME(HTTP_PUT), SETOWNER(HTTP_PUT),
199    SETPERMISSION(HTTP_PUT), SETREPLICATION(HTTP_PUT), SETTIMES(HTTP_PUT),
200    MODIFYACLENTRIES(HTTP_PUT), REMOVEACLENTRIES(HTTP_PUT),
201    REMOVEDEFAULTACL(HTTP_PUT), REMOVEACL(HTTP_PUT), SETACL(HTTP_PUT),
202    DELETE(HTTP_DELETE), SETXATTR(HTTP_PUT), GETXATTRS(HTTP_GET),
203    REMOVEXATTR(HTTP_PUT), LISTXATTRS(HTTP_GET);
204
205    private String httpMethod;
206
207    Operation(String httpMethod) {
208      this.httpMethod = httpMethod;
209    }
210
211    public String getMethod() {
212      return httpMethod;
213    }
214
215  }
216
217  private DelegationTokenAuthenticatedURL authURL;
218  private DelegationTokenAuthenticatedURL.Token authToken =
219      new DelegationTokenAuthenticatedURL.Token();
220  private URI uri;
221  private Path workingDir;
222  private UserGroupInformation realUser;
223
224
225
226  /**
227   * Convenience method that creates a <code>HttpURLConnection</code> for the
228   * HttpFSServer file system operations.
229   * <p>
230   * This methods performs and injects any needed authentication credentials
231   * via the {@link #getConnection(URL, String)} method
232   *
233   * @param method the HTTP method.
234   * @param params the query string parameters.
235   * @param path the file path
236   * @param makeQualified if the path should be 'makeQualified'
237   *
238   * @return a <code>HttpURLConnection</code> for the HttpFSServer server,
239   *         authenticated and ready to use for the specified path and file system operation.
240   *
241   * @throws IOException thrown if an IO error occurrs.
242   */
243  private HttpURLConnection getConnection(final String method,
244      Map<String, String> params, Path path, boolean makeQualified)
245      throws IOException {
246    return getConnection(method, params, null, path, makeQualified);
247  }
248
249  /**
250   * Convenience method that creates a <code>HttpURLConnection</code> for the
251   * HttpFSServer file system operations.
252   * <p/>
253   * This methods performs and injects any needed authentication credentials
254   * via the {@link #getConnection(URL, String)} method
255   *
256   * @param method the HTTP method.
257   * @param params the query string parameters.
258   * @param multiValuedParams multi valued parameters of the query string
259   * @param path the file path
260   * @param makeQualified if the path should be 'makeQualified'
261   *
262   * @return HttpURLConnection a <code>HttpURLConnection</code> for the
263   *         HttpFSServer server, authenticated and ready to use for the
264   *         specified path and file system operation.
265   *
266   * @throws IOException thrown if an IO error occurrs.
267   */
268  private HttpURLConnection getConnection(final String method,
269      Map<String, String> params, Map<String, List<String>> multiValuedParams,
270      Path path, boolean makeQualified) throws IOException {
271    if (makeQualified) {
272      path = makeQualified(path);
273    }
274    final URL url = HttpFSUtils.createURL(path, params, multiValuedParams);
275    try {
276      return UserGroupInformation.getCurrentUser().doAs(
277          new PrivilegedExceptionAction<HttpURLConnection>() {
278            @Override
279            public HttpURLConnection run() throws Exception {
280              return getConnection(url, method);
281            }
282          }
283      );
284    } catch (Exception ex) {
285      if (ex instanceof IOException) {
286        throw (IOException) ex;
287      } else {
288        throw new IOException(ex);
289      }
290    }
291  }
292
293  /**
294   * Convenience method that creates a <code>HttpURLConnection</code> for the specified URL.
295   * <p>
296   * This methods performs and injects any needed authentication credentials.
297   *
298   * @param url url to connect to.
299   * @param method the HTTP method.
300   *
301   * @return a <code>HttpURLConnection</code> for the HttpFSServer server, authenticated and ready to use for
302   *         the specified path and file system operation.
303   *
304   * @throws IOException thrown if an IO error occurrs.
305   */
306  private HttpURLConnection getConnection(URL url, String method) throws IOException {
307    try {
308      HttpURLConnection conn = authURL.openConnection(url, authToken);
309      conn.setRequestMethod(method);
310      if (method.equals(HTTP_POST) || method.equals(HTTP_PUT)) {
311        conn.setDoOutput(true);
312      }
313      return conn;
314    } catch (Exception ex) {
315      throw new IOException(ex);
316    }
317  }
318
319  /**
320   * Called after a new FileSystem instance is constructed.
321   *
322   * @param name a uri whose authority section names the host, port, etc. for this FileSystem
323   * @param conf the configuration
324   */
325  @Override
326  public void initialize(URI name, Configuration conf) throws IOException {
327    UserGroupInformation ugi = UserGroupInformation.getCurrentUser();
328
329    //the real use is the one that has the Kerberos credentials needed for
330    //SPNEGO to work
331    realUser = ugi.getRealUser();
332    if (realUser == null) {
333      realUser = UserGroupInformation.getLoginUser();
334    }
335    super.initialize(name, conf);
336    try {
337      uri = new URI(name.getScheme() + "://" + name.getAuthority());
338    } catch (URISyntaxException ex) {
339      throw new IOException(ex);
340    }
341
342    Class<? extends DelegationTokenAuthenticator> klass =
343        getConf().getClass("httpfs.authenticator.class",
344            KerberosDelegationTokenAuthenticator.class,
345            DelegationTokenAuthenticator.class);
346    DelegationTokenAuthenticator authenticator =
347        ReflectionUtils.newInstance(klass, getConf());
348    authURL = new DelegationTokenAuthenticatedURL(authenticator);
349  }
350
351  @Override
352  public String getScheme() {
353    return SCHEME;
354  }
355
356  /**
357   * Returns a URI whose scheme and authority identify this FileSystem.
358   *
359   * @return the URI whose scheme and authority identify this FileSystem.
360   */
361  @Override
362  public URI getUri() {
363    return uri;
364  }
365
366  /**
367   * Get the default port for this file system.
368   * @return the default port or 0 if there isn't one
369   */
370  @Override
371  protected int getDefaultPort() {
372    return getConf().getInt(DFSConfigKeys.DFS_NAMENODE_HTTP_PORT_KEY,
373        DFSConfigKeys.DFS_NAMENODE_HTTP_PORT_DEFAULT);
374  }
375
376  /**
377   * HttpFSServer subclass of the <code>FSDataInputStream</code>.
378   * <p>
379   * This implementation does not support the
380   * <code>PositionReadable</code> and <code>Seekable</code> methods.
381   */
382  private static class HttpFSDataInputStream extends FilterInputStream implements Seekable, PositionedReadable {
383
384    protected HttpFSDataInputStream(InputStream in, int bufferSize) {
385      super(new BufferedInputStream(in, bufferSize));
386    }
387
388    @Override
389    public int read(long position, byte[] buffer, int offset, int length) throws IOException {
390      throw new UnsupportedOperationException();
391    }
392
393    @Override
394    public void readFully(long position, byte[] buffer, int offset, int length) throws IOException {
395      throw new UnsupportedOperationException();
396    }
397
398    @Override
399    public void readFully(long position, byte[] buffer) throws IOException {
400      throw new UnsupportedOperationException();
401    }
402
403    @Override
404    public void seek(long pos) throws IOException {
405      throw new UnsupportedOperationException();
406    }
407
408    @Override
409    public long getPos() throws IOException {
410      throw new UnsupportedOperationException();
411    }
412
413    @Override
414    public boolean seekToNewSource(long targetPos) throws IOException {
415      throw new UnsupportedOperationException();
416    }
417  }
418
419  /**
420   * Opens an FSDataInputStream at the indicated Path.
421   * <p>
422   * IMPORTANT: the returned <code>FSDataInputStream</code> does not support the
423   * <code>PositionReadable</code> and <code>Seekable</code> methods.
424   *
425   * @param f the file name to open
426   * @param bufferSize the size of the buffer to be used.
427   */
428  @Override
429  public FSDataInputStream open(Path f, int bufferSize) throws IOException {
430    Map<String, String> params = new HashMap<String, String>();
431    params.put(OP_PARAM, Operation.OPEN.toString());
432    HttpURLConnection conn = getConnection(Operation.OPEN.getMethod(), params,
433                                           f, true);
434    HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
435    return new FSDataInputStream(
436      new HttpFSDataInputStream(conn.getInputStream(), bufferSize));
437  }
438
439  /**
440   * HttpFSServer subclass of the <code>FSDataOutputStream</code>.
441   * <p>
442   * This implementation closes the underlying HTTP connection validating the Http connection status
443   * at closing time.
444   */
445  private static class HttpFSDataOutputStream extends FSDataOutputStream {
446    private HttpURLConnection conn;
447    private int closeStatus;
448
449    public HttpFSDataOutputStream(HttpURLConnection conn, OutputStream out, int closeStatus, Statistics stats)
450      throws IOException {
451      super(out, stats);
452      this.conn = conn;
453      this.closeStatus = closeStatus;
454    }
455
456    @Override
457    public void close() throws IOException {
458      try {
459        super.close();
460      } finally {
461        HttpExceptionUtils.validateResponse(conn, closeStatus);
462      }
463    }
464
465  }
466
467  /**
468   * Converts a <code>FsPermission</code> to a Unix octal representation.
469   *
470   * @param p the permission.
471   *
472   * @return the Unix string symbolic reprentation.
473   */
474  public static String permissionToString(FsPermission p) {
475    return  Integer.toString((p == null) ? DEFAULT_PERMISSION : p.toShort(), 8);
476  }
477
478  /*
479   * Common handling for uploading data for create and append operations.
480   */
481  private FSDataOutputStream uploadData(String method, Path f, Map<String, String> params,
482                                        int bufferSize, int expectedStatus) throws IOException {
483    HttpURLConnection conn = getConnection(method, params, f, true);
484    conn.setInstanceFollowRedirects(false);
485    boolean exceptionAlreadyHandled = false;
486    try {
487      if (conn.getResponseCode() == HTTP_TEMPORARY_REDIRECT) {
488        exceptionAlreadyHandled = true;
489        String location = conn.getHeaderField("Location");
490        if (location != null) {
491          conn = getConnection(new URL(location), method);
492          conn.setRequestProperty("Content-Type", UPLOAD_CONTENT_TYPE);
493          try {
494            OutputStream os = new BufferedOutputStream(conn.getOutputStream(), bufferSize);
495            return new HttpFSDataOutputStream(conn, os, expectedStatus, statistics);
496          } catch (IOException ex) {
497            HttpExceptionUtils.validateResponse(conn, expectedStatus);
498            throw ex;
499          }
500        } else {
501          HttpExceptionUtils.validateResponse(conn, HTTP_TEMPORARY_REDIRECT);
502          throw new IOException("Missing HTTP 'Location' header for [" + conn.getURL() + "]");
503        }
504      } else {
505        throw new IOException(
506          MessageFormat.format("Expected HTTP status was [307], received [{0}]",
507                               conn.getResponseCode()));
508      }
509    } catch (IOException ex) {
510      if (exceptionAlreadyHandled) {
511        throw ex;
512      } else {
513        HttpExceptionUtils.validateResponse(conn, HTTP_TEMPORARY_REDIRECT);
514        throw ex;
515      }
516    }
517  }
518
519
520  /**
521   * Opens an FSDataOutputStream at the indicated Path with write-progress
522   * reporting.
523   * <p>
524   * IMPORTANT: The <code>Progressable</code> parameter is not used.
525   *
526   * @param f the file name to open.
527   * @param permission file permission.
528   * @param overwrite if a file with this name already exists, then if true,
529   * the file will be overwritten, and if false an error will be thrown.
530   * @param bufferSize the size of the buffer to be used.
531   * @param replication required block replication for the file.
532   * @param blockSize block size.
533   * @param progress progressable.
534   *
535   * @throws IOException
536   * @see #setPermission(Path, FsPermission)
537   */
538  @Override
539  public FSDataOutputStream create(Path f, FsPermission permission,
540                                   boolean overwrite, int bufferSize,
541                                   short replication, long blockSize,
542                                   Progressable progress) throws IOException {
543    Map<String, String> params = new HashMap<String, String>();
544    params.put(OP_PARAM, Operation.CREATE.toString());
545    params.put(OVERWRITE_PARAM, Boolean.toString(overwrite));
546    params.put(REPLICATION_PARAM, Short.toString(replication));
547    params.put(BLOCKSIZE_PARAM, Long.toString(blockSize));
548    params.put(PERMISSION_PARAM, permissionToString(permission));
549    return uploadData(Operation.CREATE.getMethod(), f, params, bufferSize,
550                      HttpURLConnection.HTTP_CREATED);
551  }
552
553
554  /**
555   * Append to an existing file (optional operation).
556   * <p>
557   * IMPORTANT: The <code>Progressable</code> parameter is not used.
558   *
559   * @param f the existing file to be appended.
560   * @param bufferSize the size of the buffer to be used.
561   * @param progress for reporting progress if it is not null.
562   *
563   * @throws IOException
564   */
565  @Override
566  public FSDataOutputStream append(Path f, int bufferSize,
567                                   Progressable progress) throws IOException {
568    Map<String, String> params = new HashMap<String, String>();
569    params.put(OP_PARAM, Operation.APPEND.toString());
570    return uploadData(Operation.APPEND.getMethod(), f, params, bufferSize,
571                      HttpURLConnection.HTTP_OK);
572  }
573
574  /**
575   * Truncate a file.
576   * 
577   * @param f the file to be truncated.
578   * @param newLength The size the file is to be truncated to.
579   *
580   * @throws IOException
581   */
582  @Override
583  public boolean truncate(Path f, long newLength) throws IOException {
584    Map<String, String> params = new HashMap<String, String>();
585    params.put(OP_PARAM, Operation.TRUNCATE.toString());
586    params.put(NEW_LENGTH_PARAM, Long.toString(newLength));
587    HttpURLConnection conn = getConnection(Operation.TRUNCATE.getMethod(),
588        params, f, true);
589    JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
590    return (Boolean) json.get(TRUNCATE_JSON);
591  }
592
593  /**
594   * Concat existing files together.
595   * @param f the path to the target destination.
596   * @param psrcs the paths to the sources to use for the concatenation.
597   *
598   * @throws IOException
599   */
600  @Override
601  public void concat(Path f, Path[] psrcs) throws IOException {
602    List<String> strPaths = new ArrayList<String>(psrcs.length);
603    for(Path psrc : psrcs) {
604      strPaths.add(psrc.toUri().getPath());
605    }
606    String srcs = StringUtils.join(",", strPaths);
607
608    Map<String, String> params = new HashMap<String, String>();
609    params.put(OP_PARAM, Operation.CONCAT.toString());
610    params.put(SOURCES_PARAM, srcs);
611    HttpURLConnection conn = getConnection(Operation.CONCAT.getMethod(),
612        params, f, true);
613    HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
614  }
615
616  /**
617   * Renames Path src to Path dst.  Can take place on local fs
618   * or remote DFS.
619   */
620  @Override
621  public boolean rename(Path src, Path dst) throws IOException {
622    Map<String, String> params = new HashMap<String, String>();
623    params.put(OP_PARAM, Operation.RENAME.toString());
624    params.put(DESTINATION_PARAM, dst.toString());
625    HttpURLConnection conn = getConnection(Operation.RENAME.getMethod(),
626                                           params, src, true);
627    HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
628    JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
629    return (Boolean) json.get(RENAME_JSON);
630  }
631
632  /**
633   * Delete a file.
634   *
635   * @deprecated Use delete(Path, boolean) instead
636   */
637  @Deprecated
638  @Override
639  public boolean delete(Path f) throws IOException {
640    return delete(f, false);
641  }
642
643  /**
644   * Delete a file.
645   *
646   * @param f the path to delete.
647   * @param recursive if path is a directory and set to
648   * true, the directory is deleted else throws an exception. In
649   * case of a file the recursive can be set to either true or false.
650   *
651   * @return true if delete is successful else false.
652   *
653   * @throws IOException
654   */
655  @Override
656  public boolean delete(Path f, boolean recursive) throws IOException {
657    Map<String, String> params = new HashMap<String, String>();
658    params.put(OP_PARAM, Operation.DELETE.toString());
659    params.put(RECURSIVE_PARAM, Boolean.toString(recursive));
660    HttpURLConnection conn = getConnection(Operation.DELETE.getMethod(),
661                                           params, f, true);
662    HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
663    JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
664    return (Boolean) json.get(DELETE_JSON);
665  }
666
667  /**
668   * List the statuses of the files/directories in the given path if the path is
669   * a directory.
670   *
671   * @param f given path
672   *
673   * @return the statuses of the files/directories in the given patch
674   *
675   * @throws IOException
676   */
677  @Override
678  public FileStatus[] listStatus(Path f) throws IOException {
679    Map<String, String> params = new HashMap<String, String>();
680    params.put(OP_PARAM, Operation.LISTSTATUS.toString());
681    HttpURLConnection conn = getConnection(Operation.LISTSTATUS.getMethod(),
682                                           params, f, true);
683    HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
684    JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
685    json = (JSONObject) json.get(FILE_STATUSES_JSON);
686    JSONArray jsonArray = (JSONArray) json.get(FILE_STATUS_JSON);
687    FileStatus[] array = new FileStatus[jsonArray.size()];
688    f = makeQualified(f);
689    for (int i = 0; i < jsonArray.size(); i++) {
690      array[i] = createFileStatus(f, (JSONObject) jsonArray.get(i));
691    }
692    return array;
693  }
694
695  /**
696   * Set the current working directory for the given file system. All relative
697   * paths will be resolved relative to it.
698   *
699   * @param newDir new directory.
700   */
701  @Override
702  public void setWorkingDirectory(Path newDir) {
703    workingDir = newDir;
704  }
705
706  /**
707   * Get the current working directory for the given file system
708   *
709   * @return the directory pathname
710   */
711  @Override
712  public Path getWorkingDirectory() {
713    if (workingDir == null) {
714      workingDir = getHomeDirectory();
715    }
716    return workingDir;
717  }
718
719  /**
720   * Make the given file and all non-existent parents into
721   * directories. Has the semantics of Unix 'mkdir -p'.
722   * Existence of the directory hierarchy is not an error.
723   */
724  @Override
725  public boolean mkdirs(Path f, FsPermission permission) throws IOException {
726    Map<String, String> params = new HashMap<String, String>();
727    params.put(OP_PARAM, Operation.MKDIRS.toString());
728    params.put(PERMISSION_PARAM, permissionToString(permission));
729    HttpURLConnection conn = getConnection(Operation.MKDIRS.getMethod(),
730                                           params, f, true);
731    HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
732    JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
733    return (Boolean) json.get(MKDIRS_JSON);
734  }
735
736  /**
737   * Return a file status object that represents the path.
738   *
739   * @param f The path we want information from
740   *
741   * @return a FileStatus object
742   *
743   * @throws FileNotFoundException when the path does not exist;
744   * IOException see specific implementation
745   */
746  @Override
747  public FileStatus getFileStatus(Path f) throws IOException {
748    Map<String, String> params = new HashMap<String, String>();
749    params.put(OP_PARAM, Operation.GETFILESTATUS.toString());
750    HttpURLConnection conn = getConnection(Operation.GETFILESTATUS.getMethod(),
751                                           params, f, true);
752    HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
753    JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
754    json = (JSONObject) json.get(FILE_STATUS_JSON);
755    f = makeQualified(f);
756    return createFileStatus(f, json);
757  }
758
759  /**
760   * Return the current user's home directory in this filesystem.
761   * The default implementation returns "/user/$USER/".
762   */
763  @Override
764  public Path getHomeDirectory() {
765    Map<String, String> params = new HashMap<String, String>();
766    params.put(OP_PARAM, Operation.GETHOMEDIRECTORY.toString());
767    try {
768      HttpURLConnection conn =
769        getConnection(Operation.GETHOMEDIRECTORY.getMethod(), params,
770                      new Path(getUri().toString(), "/"), false);
771      HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
772      JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
773      return new Path((String) json.get(HOME_DIR_JSON));
774    } catch (IOException ex) {
775      throw new RuntimeException(ex);
776    }
777  }
778
779  /**
780   * Set owner of a path (i.e. a file or a directory).
781   * The parameters username and groupname cannot both be null.
782   *
783   * @param p The path
784   * @param username If it is null, the original username remains unchanged.
785   * @param groupname If it is null, the original groupname remains unchanged.
786   */
787  @Override
788  public void setOwner(Path p, String username, String groupname)
789    throws IOException {
790    Map<String, String> params = new HashMap<String, String>();
791    params.put(OP_PARAM, Operation.SETOWNER.toString());
792    params.put(OWNER_PARAM, username);
793    params.put(GROUP_PARAM, groupname);
794    HttpURLConnection conn = getConnection(Operation.SETOWNER.getMethod(),
795                                           params, p, true);
796    HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
797  }
798
799  /**
800   * Set permission of a path.
801   *
802   * @param p path.
803   * @param permission permission.
804   */
805  @Override
806  public void setPermission(Path p, FsPermission permission) throws IOException {
807    Map<String, String> params = new HashMap<String, String>();
808    params.put(OP_PARAM, Operation.SETPERMISSION.toString());
809    params.put(PERMISSION_PARAM, permissionToString(permission));
810    HttpURLConnection conn = getConnection(Operation.SETPERMISSION.getMethod(), params, p, true);
811    HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
812  }
813
814  /**
815   * Set access time of a file
816   *
817   * @param p The path
818   * @param mtime Set the modification time of this file.
819   * The number of milliseconds since Jan 1, 1970.
820   * A value of -1 means that this call should not set modification time.
821   * @param atime Set the access time of this file.
822   * The number of milliseconds since Jan 1, 1970.
823   * A value of -1 means that this call should not set access time.
824   */
825  @Override
826  public void setTimes(Path p, long mtime, long atime) throws IOException {
827    Map<String, String> params = new HashMap<String, String>();
828    params.put(OP_PARAM, Operation.SETTIMES.toString());
829    params.put(MODIFICATION_TIME_PARAM, Long.toString(mtime));
830    params.put(ACCESS_TIME_PARAM, Long.toString(atime));
831    HttpURLConnection conn = getConnection(Operation.SETTIMES.getMethod(),
832                                           params, p, true);
833    HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
834  }
835
836  /**
837   * Set replication for an existing file.
838   *
839   * @param src file name
840   * @param replication new replication
841   *
842   * @return true if successful;
843   *         false if file does not exist or is a directory
844   *
845   * @throws IOException
846   */
847  @Override
848  public boolean setReplication(Path src, short replication)
849    throws IOException {
850    Map<String, String> params = new HashMap<String, String>();
851    params.put(OP_PARAM, Operation.SETREPLICATION.toString());
852    params.put(REPLICATION_PARAM, Short.toString(replication));
853    HttpURLConnection conn =
854      getConnection(Operation.SETREPLICATION.getMethod(), params, src, true);
855    HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
856    JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
857    return (Boolean) json.get(SET_REPLICATION_JSON);
858  }
859
860  /**
861   * Modify the ACL entries for a file.
862   *
863   * @param path Path to modify
864   * @param aclSpec describing modifications
865   * @throws IOException
866   */
867  @Override
868  public void modifyAclEntries(Path path, List<AclEntry> aclSpec)
869          throws IOException {
870    Map<String, String> params = new HashMap<String, String>();
871    params.put(OP_PARAM, Operation.MODIFYACLENTRIES.toString());
872    params.put(ACLSPEC_PARAM, AclEntry.aclSpecToString(aclSpec));
873    HttpURLConnection conn = getConnection(
874            Operation.MODIFYACLENTRIES.getMethod(), params, path, true);
875    HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
876  }
877
878  /**
879   * Remove the specified ACL entries from a file
880   * @param path Path to modify
881   * @param aclSpec describing entries to remove
882   * @throws IOException
883   */
884  @Override
885  public void removeAclEntries(Path path, List<AclEntry> aclSpec)
886          throws IOException {
887    Map<String, String> params = new HashMap<String, String>();
888    params.put(OP_PARAM, Operation.REMOVEACLENTRIES.toString());
889    params.put(ACLSPEC_PARAM, AclEntry.aclSpecToString(aclSpec));
890    HttpURLConnection conn = getConnection(
891            Operation.REMOVEACLENTRIES.getMethod(), params, path, true);
892    HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
893  }
894
895  /**
896   * Removes the default ACL for the given file
897   * @param path Path from which to remove the default ACL.
898   * @throws IOException
899   */
900  @Override
901  public void removeDefaultAcl(Path path) throws IOException {
902    Map<String, String> params = new HashMap<String, String>();
903    params.put(OP_PARAM, Operation.REMOVEDEFAULTACL.toString());
904    HttpURLConnection conn = getConnection(
905            Operation.REMOVEDEFAULTACL.getMethod(), params, path, true);
906    HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
907  }
908
909  /**
910   * Remove all ACLs from a file
911   * @param path Path from which to remove all ACLs
912   * @throws IOException
913   */
914  @Override
915  public void removeAcl(Path path) throws IOException {
916    Map<String, String> params = new HashMap<String, String>();
917    params.put(OP_PARAM, Operation.REMOVEACL.toString());
918    HttpURLConnection conn = getConnection(Operation.REMOVEACL.getMethod(),
919            params, path, true);
920    HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
921  }
922
923  /**
924   * Set the ACLs for the given file
925   * @param path Path to modify
926   * @param aclSpec describing modifications, must include
927   *                entries for user, group, and others for compatibility
928   *                with permission bits.
929   * @throws IOException
930   */
931  @Override
932  public void setAcl(Path path, List<AclEntry> aclSpec) throws IOException {
933    Map<String, String> params = new HashMap<String, String>();
934    params.put(OP_PARAM, Operation.SETACL.toString());
935    params.put(ACLSPEC_PARAM, AclEntry.aclSpecToString(aclSpec));
936    HttpURLConnection conn = getConnection(Operation.SETACL.getMethod(),
937                                           params, path, true);
938    HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
939  }
940
941  /**
942   * Get the ACL information for a given file
943   * @param path Path to acquire ACL info for
944   * @return the ACL information in JSON format
945   * @throws IOException
946   */
947  @Override
948  public AclStatus getAclStatus(Path path) throws IOException {
949    Map<String, String> params = new HashMap<String, String>();
950    params.put(OP_PARAM, Operation.GETACLSTATUS.toString());
951    HttpURLConnection conn = getConnection(Operation.GETACLSTATUS.getMethod(),
952            params, path, true);
953    HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
954    JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
955    json = (JSONObject) json.get(ACL_STATUS_JSON);
956    return createAclStatus(json);
957  }
958
959  private FileStatus createFileStatus(Path parent, JSONObject json) {
960    String pathSuffix = (String) json.get(PATH_SUFFIX_JSON);
961    Path path = (pathSuffix.equals("")) ? parent : new Path(parent, pathSuffix);
962    FILE_TYPE type = FILE_TYPE.valueOf((String) json.get(TYPE_JSON));
963    long len = (Long) json.get(LENGTH_JSON);
964    String owner = (String) json.get(OWNER_JSON);
965    String group = (String) json.get(GROUP_JSON);
966    FsPermission permission =
967      new FsPermission(Short.parseShort((String) json.get(PERMISSION_JSON), 8));
968    long aTime = (Long) json.get(ACCESS_TIME_JSON);
969    long mTime = (Long) json.get(MODIFICATION_TIME_JSON);
970    long blockSize = (Long) json.get(BLOCK_SIZE_JSON);
971    short replication = ((Long) json.get(REPLICATION_JSON)).shortValue();
972    FileStatus fileStatus = null;
973
974    switch (type) {
975      case FILE:
976      case DIRECTORY:
977        fileStatus = new FileStatus(len, (type == FILE_TYPE.DIRECTORY),
978                                    replication, blockSize, mTime, aTime,
979                                    permission, owner, group, path);
980        break;
981      case SYMLINK:
982        Path symLink = null;
983        fileStatus = new FileStatus(len, false,
984                                    replication, blockSize, mTime, aTime,
985                                    permission, owner, group, symLink,
986                                    path);
987    }
988    return fileStatus;
989  }
990
991  /**
992   * Convert the given JSON object into an AclStatus
993   * @param json Input JSON representing the ACLs
994   * @return Resulting AclStatus
995   */
996  private AclStatus createAclStatus(JSONObject json) {
997    AclStatus.Builder aclStatusBuilder = new AclStatus.Builder()
998            .owner((String) json.get(OWNER_JSON))
999            .group((String) json.get(GROUP_JSON))
1000            .stickyBit((Boolean) json.get(ACL_STICKY_BIT_JSON));
1001    JSONArray entries = (JSONArray) json.get(ACL_ENTRIES_JSON);
1002    for ( Object e : entries ) {
1003      aclStatusBuilder.addEntry(AclEntry.parseAclEntry(e.toString(), true));
1004    }
1005    return aclStatusBuilder.build();
1006  }
1007
1008  @Override
1009  public ContentSummary getContentSummary(Path f) throws IOException {
1010    Map<String, String> params = new HashMap<String, String>();
1011    params.put(OP_PARAM, Operation.GETCONTENTSUMMARY.toString());
1012    HttpURLConnection conn =
1013      getConnection(Operation.GETCONTENTSUMMARY.getMethod(), params, f, true);
1014    HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
1015    JSONObject json = (JSONObject) ((JSONObject)
1016      HttpFSUtils.jsonParse(conn)).get(CONTENT_SUMMARY_JSON);
1017    return new ContentSummary.Builder().
1018        length((Long) json.get(CONTENT_SUMMARY_LENGTH_JSON)).
1019        fileCount((Long) json.get(CONTENT_SUMMARY_FILE_COUNT_JSON)).
1020        directoryCount((Long) json.get(CONTENT_SUMMARY_DIRECTORY_COUNT_JSON)).
1021        quota((Long) json.get(CONTENT_SUMMARY_QUOTA_JSON)).
1022        spaceConsumed((Long) json.get(CONTENT_SUMMARY_SPACE_CONSUMED_JSON)).
1023        spaceQuota((Long) json.get(CONTENT_SUMMARY_SPACE_QUOTA_JSON)).build();
1024  }
1025
1026  @Override
1027  public FileChecksum getFileChecksum(Path f) throws IOException {
1028    Map<String, String> params = new HashMap<String, String>();
1029    params.put(OP_PARAM, Operation.GETFILECHECKSUM.toString());
1030    HttpURLConnection conn =
1031      getConnection(Operation.GETFILECHECKSUM.getMethod(), params, f, true);
1032    HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
1033    final JSONObject json = (JSONObject) ((JSONObject)
1034      HttpFSUtils.jsonParse(conn)).get(FILE_CHECKSUM_JSON);
1035    return new FileChecksum() {
1036      @Override
1037      public String getAlgorithmName() {
1038        return (String) json.get(CHECKSUM_ALGORITHM_JSON);
1039      }
1040
1041      @Override
1042      public int getLength() {
1043        return ((Long) json.get(CHECKSUM_LENGTH_JSON)).intValue();
1044      }
1045
1046      @Override
1047      public byte[] getBytes() {
1048        return StringUtils.hexStringToByte((String) json.get(CHECKSUM_BYTES_JSON));
1049      }
1050
1051      @Override
1052      public void write(DataOutput out) throws IOException {
1053        throw new UnsupportedOperationException();
1054      }
1055
1056      @Override
1057      public void readFields(DataInput in) throws IOException {
1058        throw new UnsupportedOperationException();
1059      }
1060    };
1061  }
1062
1063
1064  @Override
1065  public Token<?> getDelegationToken(final String renewer)
1066    throws IOException {
1067    try {
1068      return UserGroupInformation.getCurrentUser().doAs(
1069          new PrivilegedExceptionAction<Token<?>>() {
1070            @Override
1071            public Token<?> run() throws Exception {
1072              return authURL.getDelegationToken(uri.toURL(), authToken,
1073                  renewer);
1074            }
1075          }
1076      );
1077    } catch (Exception ex) {
1078      if (ex instanceof IOException) {
1079        throw (IOException) ex;
1080      } else {
1081        throw new IOException(ex);
1082      }
1083    }
1084  }
1085
1086  public long renewDelegationToken(final Token<?> token) throws IOException {
1087    try {
1088      return UserGroupInformation.getCurrentUser().doAs(
1089          new PrivilegedExceptionAction<Long>() {
1090            @Override
1091            public Long run() throws Exception {
1092              return authURL.renewDelegationToken(uri.toURL(), authToken);
1093            }
1094          }
1095      );
1096    } catch (Exception ex) {
1097      if (ex instanceof IOException) {
1098        throw (IOException) ex;
1099      } else {
1100        throw new IOException(ex);
1101      }
1102    }
1103  }
1104
1105  public void cancelDelegationToken(final Token<?> token) throws IOException {
1106    authURL.cancelDelegationToken(uri.toURL(), authToken);
1107  }
1108
1109  @Override
1110  public Token<?> getRenewToken() {
1111    return null; //TODO : for renewer
1112  }
1113
1114  @Override
1115  @SuppressWarnings("unchecked")
1116  public <T extends TokenIdentifier> void setDelegationToken(Token<T> token) {
1117    //TODO : for renewer
1118  }
1119
1120  @Override
1121  public void setXAttr(Path f, String name, byte[] value,
1122      EnumSet<XAttrSetFlag> flag) throws IOException {
1123    Map<String, String> params = new HashMap<String, String>();
1124    params.put(OP_PARAM, Operation.SETXATTR.toString());
1125    params.put(XATTR_NAME_PARAM, name);
1126    if (value != null) {
1127      params.put(XATTR_VALUE_PARAM, 
1128          XAttrCodec.encodeValue(value, XAttrCodec.HEX));
1129    }
1130    params.put(XATTR_SET_FLAG_PARAM, EnumSetParam.toString(flag));
1131    HttpURLConnection conn = getConnection(Operation.SETXATTR.getMethod(),
1132        params, f, true);
1133    HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
1134  }
1135
1136  @Override
1137  public byte[] getXAttr(Path f, String name) throws IOException {
1138    Map<String, String> params = new HashMap<String, String>();
1139    params.put(OP_PARAM, Operation.GETXATTRS.toString());
1140    params.put(XATTR_NAME_PARAM, name);
1141    HttpURLConnection conn = getConnection(Operation.GETXATTRS.getMethod(),
1142        params, f, true);
1143    HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
1144    JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
1145    Map<String, byte[]> xAttrs = createXAttrMap(
1146        (JSONArray) json.get(XATTRS_JSON));
1147    return xAttrs != null ? xAttrs.get(name) : null;
1148  }
1149
1150  /** Convert xAttrs json to xAttrs map */
1151  private Map<String, byte[]> createXAttrMap(JSONArray jsonArray) 
1152      throws IOException {
1153    Map<String, byte[]> xAttrs = Maps.newHashMap();
1154    for (Object obj : jsonArray) {
1155      JSONObject jsonObj = (JSONObject) obj;
1156      final String name = (String)jsonObj.get(XATTR_NAME_JSON);
1157      final byte[] value = XAttrCodec.decodeValue(
1158          (String)jsonObj.get(XATTR_VALUE_JSON));
1159      xAttrs.put(name, value);
1160    }
1161
1162    return xAttrs;
1163  }
1164
1165  /** Convert xAttr names json to names list */
1166  private List<String> createXAttrNames(String xattrNamesStr) throws IOException {
1167    JSONParser parser = new JSONParser();
1168    JSONArray jsonArray;
1169    try {
1170      jsonArray = (JSONArray)parser.parse(xattrNamesStr);
1171      List<String> names = Lists.newArrayListWithCapacity(jsonArray.size());
1172      for (Object name : jsonArray) {
1173        names.add((String) name);
1174      }
1175      return names;
1176    } catch (ParseException e) {
1177      throw new IOException("JSON parser error, " + e.getMessage(), e);
1178    }
1179  }
1180
1181  @Override
1182  public Map<String, byte[]> getXAttrs(Path f) throws IOException {
1183    Map<String, String> params = new HashMap<String, String>();
1184    params.put(OP_PARAM, Operation.GETXATTRS.toString());
1185    HttpURLConnection conn = getConnection(Operation.GETXATTRS.getMethod(),
1186        params, f, true);
1187    HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
1188    JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
1189    return createXAttrMap((JSONArray) json.get(XATTRS_JSON));
1190  }
1191
1192  @Override
1193  public Map<String, byte[]> getXAttrs(Path f, List<String> names)
1194      throws IOException {
1195    Preconditions.checkArgument(names != null && !names.isEmpty(), 
1196        "XAttr names cannot be null or empty.");
1197    Map<String, String> params = new HashMap<String, String>();
1198    params.put(OP_PARAM, Operation.GETXATTRS.toString());
1199    Map<String, List<String>> multiValuedParams = Maps.newHashMap();
1200    multiValuedParams.put(XATTR_NAME_PARAM, names);
1201    HttpURLConnection conn = getConnection(Operation.GETXATTRS.getMethod(),
1202        params, multiValuedParams, f, true);
1203    HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
1204    JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
1205    return createXAttrMap((JSONArray) json.get(XATTRS_JSON));
1206  }
1207
1208  @Override
1209  public List<String> listXAttrs(Path f) throws IOException {
1210    Map<String, String> params = new HashMap<String, String>();
1211    params.put(OP_PARAM, Operation.LISTXATTRS.toString());
1212    HttpURLConnection conn = getConnection(Operation.LISTXATTRS.getMethod(),
1213        params, f, true);
1214    HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
1215    JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
1216    return createXAttrNames((String) json.get(XATTRNAMES_JSON));
1217  }
1218
1219  @Override
1220  public void removeXAttr(Path f, String name) throws IOException {
1221    Map<String, String> params = new HashMap<String, String>();
1222    params.put(OP_PARAM, Operation.REMOVEXATTR.toString());
1223    params.put(XATTR_NAME_PARAM, name);
1224    HttpURLConnection conn = getConnection(Operation.REMOVEXATTR.getMethod(),
1225        params, f, true);
1226    HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
1227  }
1228}