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