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 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}