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