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