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.hdfs.web;
019
020import org.apache.hadoop.fs.*;
021import org.apache.hadoop.fs.permission.AclEntry;
022import org.apache.hadoop.fs.permission.AclStatus;
023import org.apache.hadoop.fs.permission.FsPermission;
024import org.apache.hadoop.hdfs.DFSUtil;
025import org.apache.hadoop.hdfs.XAttrHelper;
026import org.apache.hadoop.hdfs.protocol.*;
027import org.apache.hadoop.hdfs.protocol.DatanodeInfo.AdminStates;
028import org.apache.hadoop.hdfs.security.token.block.BlockTokenIdentifier;
029import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier;
030import org.apache.hadoop.hdfs.server.blockmanagement.BlockStoragePolicySuite;
031import org.apache.hadoop.hdfs.server.namenode.INodeId;
032import org.apache.hadoop.ipc.RemoteException;
033import org.apache.hadoop.security.token.Token;
034import org.apache.hadoop.security.token.TokenIdentifier;
035import org.apache.hadoop.util.DataChecksum;
036import org.apache.hadoop.util.StringUtils;
037import org.codehaus.jackson.map.ObjectMapper;
038import org.codehaus.jackson.map.ObjectReader;
039
040import com.google.common.collect.Lists;
041import com.google.common.collect.Maps;
042
043import java.io.ByteArrayInputStream;
044import java.io.DataInputStream;
045import java.io.IOException;
046import java.util.*;
047
048/** JSON Utilities */
049public class JsonUtil {
050  private static final Object[] EMPTY_OBJECT_ARRAY = {};
051  private static final DatanodeInfo[] EMPTY_DATANODE_INFO_ARRAY = {};
052
053  /** Convert a token object to a Json string. */
054  public static String toJsonString(final Token<? extends TokenIdentifier> token
055      ) throws IOException {
056    return toJsonString(Token.class, toJsonMap(token));
057  }
058
059  private static Map<String, Object> toJsonMap(
060      final Token<? extends TokenIdentifier> token) throws IOException {
061    if (token == null) {
062      return null;
063    }
064
065    final Map<String, Object> m = new TreeMap<String, Object>();
066    m.put("urlString", token.encodeToUrlString());
067    return m;
068  }
069
070  /** Convert a Json map to a Token. */
071  public static Token<? extends TokenIdentifier> toToken(
072      final Map<?, ?> m) throws IOException {
073    if (m == null) {
074      return null;
075    }
076
077    final Token<DelegationTokenIdentifier> token
078        = new Token<DelegationTokenIdentifier>();
079    token.decodeFromUrlString((String)m.get("urlString"));
080    return token;
081  }
082
083  /** Convert a Json map to a Token of DelegationTokenIdentifier. */
084  @SuppressWarnings("unchecked")
085  public static Token<DelegationTokenIdentifier> toDelegationToken(
086      final Map<?, ?> json) throws IOException {
087    final Map<?, ?> m = (Map<?, ?>)json.get(Token.class.getSimpleName());
088    return (Token<DelegationTokenIdentifier>)toToken(m);
089  }
090
091  /** Convert a Json map to a Token of BlockTokenIdentifier. */
092  @SuppressWarnings("unchecked")
093  private static Token<BlockTokenIdentifier> toBlockToken(
094      final Map<?, ?> m) throws IOException {
095    return (Token<BlockTokenIdentifier>)toToken(m);
096  }
097
098  /** Convert an exception object to a Json string. */
099  public static String toJsonString(final Exception e) {
100    final Map<String, Object> m = new TreeMap<String, Object>();
101    m.put("exception", e.getClass().getSimpleName());
102    m.put("message", e.getMessage());
103    m.put("javaClassName", e.getClass().getName());
104    return toJsonString(RemoteException.class, m);
105  }
106
107  /** Convert a Json map to a RemoteException. */
108  public static RemoteException toRemoteException(final Map<?, ?> json) {
109    final Map<?, ?> m = (Map<?, ?>)json.get(RemoteException.class.getSimpleName());
110    final String message = (String)m.get("message");
111    final String javaClassName = (String)m.get("javaClassName");
112    return new RemoteException(javaClassName, message);
113  }
114
115  private static String toJsonString(final Class<?> clazz, final Object value) {
116    return toJsonString(clazz.getSimpleName(), value);
117  }
118
119  /** Convert a key-value pair to a Json string. */
120  public static String toJsonString(final String key, final Object value) {
121    final Map<String, Object> m = new TreeMap<String, Object>();
122    m.put(key, value);
123    ObjectMapper mapper = new ObjectMapper();
124    try {
125      return mapper.writeValueAsString(m);
126    } catch (IOException ignored) {
127    }
128    return null;
129  }
130
131  /** Convert a FsPermission object to a string. */
132  private static String toString(final FsPermission permission) {
133    return String.format("%o", permission.toShort());
134  }
135
136  /** Convert a string to a FsPermission object. */
137  private static FsPermission toFsPermission(final String s, Boolean aclBit,
138      Boolean encBit) {
139    FsPermission perm = new FsPermission(Short.parseShort(s, 8));
140    final boolean aBit = (aclBit != null) ? aclBit : false;
141    final boolean eBit = (encBit != null) ? encBit : false;
142    if (aBit || eBit) {
143      return new FsPermissionExtension(perm, aBit, eBit);
144    } else {
145      return perm;
146    }
147  }
148
149  static enum PathType {
150    FILE, DIRECTORY, SYMLINK;
151    
152    static PathType valueOf(HdfsFileStatus status) {
153      return status.isDir()? DIRECTORY: status.isSymlink()? SYMLINK: FILE;
154    }
155  }
156
157  /** Convert a HdfsFileStatus object to a Json string. */
158  public static String toJsonString(final HdfsFileStatus status,
159      boolean includeType) {
160    if (status == null) {
161      return null;
162    }
163    final Map<String, Object> m = new TreeMap<String, Object>();
164    m.put("pathSuffix", status.getLocalName());
165    m.put("type", PathType.valueOf(status));
166    if (status.isSymlink()) {
167      m.put("symlink", status.getSymlink());
168    }
169
170    m.put("length", status.getLen());
171    m.put("owner", status.getOwner());
172    m.put("group", status.getGroup());
173    FsPermission perm = status.getPermission();
174    m.put("permission", toString(perm));
175    if (perm.getAclBit()) {
176      m.put("aclBit", true);
177    }
178    if (perm.getEncryptedBit()) {
179      m.put("encBit", true);
180    }
181    m.put("accessTime", status.getAccessTime());
182    m.put("modificationTime", status.getModificationTime());
183    m.put("blockSize", status.getBlockSize());
184    m.put("replication", status.getReplication());
185    m.put("fileId", status.getFileId());
186    m.put("childrenNum", status.getChildrenNum());
187    m.put("storagePolicy", status.getStoragePolicy());
188    ObjectMapper mapper = new ObjectMapper();
189    try {
190      return includeType ?
191          toJsonString(FileStatus.class, m) : mapper.writeValueAsString(m);
192    } catch (IOException ignored) {
193    }
194    return null;
195  }
196
197  /** Convert a Json map to a HdfsFileStatus object. */
198  public static HdfsFileStatus toFileStatus(final Map<?, ?> json, boolean includesType) {
199    if (json == null) {
200      return null;
201    }
202
203    final Map<?, ?> m = includesType ? 
204        (Map<?, ?>)json.get(FileStatus.class.getSimpleName()) : json;
205    final String localName = (String) m.get("pathSuffix");
206    final PathType type = PathType.valueOf((String) m.get("type"));
207    final byte[] symlink = type != PathType.SYMLINK? null
208        : DFSUtil.string2Bytes((String)m.get("symlink"));
209
210    final long len = ((Number) m.get("length")).longValue();
211    final String owner = (String) m.get("owner");
212    final String group = (String) m.get("group");
213    final FsPermission permission = toFsPermission((String) m.get("permission"),
214      (Boolean)m.get("aclBit"), (Boolean)m.get("encBit"));
215    final long aTime = ((Number) m.get("accessTime")).longValue();
216    final long mTime = ((Number) m.get("modificationTime")).longValue();
217    final long blockSize = ((Number) m.get("blockSize")).longValue();
218    final short replication = ((Number) m.get("replication")).shortValue();
219    final long fileId = m.containsKey("fileId") ?
220        ((Number) m.get("fileId")).longValue() : INodeId.GRANDFATHER_INODE_ID;
221    final int childrenNum = getInt(m, "childrenNum", -1);
222    final byte storagePolicy = m.containsKey("storagePolicy") ?
223        (byte) ((Number) m.get("storagePolicy")).longValue() :
224        BlockStoragePolicySuite.ID_UNSPECIFIED;
225    return new HdfsFileStatus(len, type == PathType.DIRECTORY, replication,
226        blockSize, mTime, aTime, permission, owner, group, symlink,
227        DFSUtil.string2Bytes(localName), fileId, childrenNum, null, storagePolicy);
228  }
229
230  /** Convert an ExtendedBlock to a Json map. */
231  private static Map<String, Object> toJsonMap(final ExtendedBlock extendedblock) {
232    if (extendedblock == null) {
233      return null;
234    }
235
236    final Map<String, Object> m = new TreeMap<String, Object>();
237    m.put("blockPoolId", extendedblock.getBlockPoolId());
238    m.put("blockId", extendedblock.getBlockId());
239    m.put("numBytes", extendedblock.getNumBytes());
240    m.put("generationStamp", extendedblock.getGenerationStamp());
241    return m;
242  }
243
244  /** Convert a Json map to an ExtendedBlock object. */
245  private static ExtendedBlock toExtendedBlock(final Map<?, ?> m) {
246    if (m == null) {
247      return null;
248    }
249    
250    final String blockPoolId = (String)m.get("blockPoolId");
251    final long blockId = ((Number) m.get("blockId")).longValue();
252    final long numBytes = ((Number) m.get("numBytes")).longValue();
253    final long generationStamp =
254        ((Number) m.get("generationStamp")).longValue();
255    return new ExtendedBlock(blockPoolId, blockId, numBytes, generationStamp);
256  }
257  
258  /** Convert a DatanodeInfo to a Json map. */
259  static Map<String, Object> toJsonMap(final DatanodeInfo datanodeinfo) {
260    if (datanodeinfo == null) {
261      return null;
262    }
263
264    // TODO: Fix storageID
265    final Map<String, Object> m = new TreeMap<String, Object>();
266    m.put("ipAddr", datanodeinfo.getIpAddr());
267    // 'name' is equivalent to ipAddr:xferPort. Older clients (1.x, 0.23.x) 
268    // expects this instead of the two fields.
269    m.put("name", datanodeinfo.getXferAddr());
270    m.put("hostName", datanodeinfo.getHostName());
271    m.put("storageID", datanodeinfo.getDatanodeUuid());
272    m.put("xferPort", datanodeinfo.getXferPort());
273    m.put("infoPort", datanodeinfo.getInfoPort());
274    m.put("infoSecurePort", datanodeinfo.getInfoSecurePort());
275    m.put("ipcPort", datanodeinfo.getIpcPort());
276
277    m.put("capacity", datanodeinfo.getCapacity());
278    m.put("dfsUsed", datanodeinfo.getDfsUsed());
279    m.put("remaining", datanodeinfo.getRemaining());
280    m.put("blockPoolUsed", datanodeinfo.getBlockPoolUsed());
281    m.put("cacheCapacity", datanodeinfo.getCacheCapacity());
282    m.put("cacheUsed", datanodeinfo.getCacheUsed());
283    m.put("lastUpdate", datanodeinfo.getLastUpdate());
284    m.put("lastUpdateMonotonic", datanodeinfo.getLastUpdateMonotonic());
285    m.put("xceiverCount", datanodeinfo.getXceiverCount());
286    m.put("networkLocation", datanodeinfo.getNetworkLocation());
287    m.put("adminState", datanodeinfo.getAdminState().name());
288    return m;
289  }
290
291  private static int getInt(Map<?, ?> m, String key, final int defaultValue) {
292    Object value = m.get(key);
293    if (value == null) {
294      return defaultValue;
295    }
296    return ((Number) value).intValue();
297  }
298
299  private static long getLong(Map<?, ?> m, String key, final long defaultValue) {
300    Object value = m.get(key);
301    if (value == null) {
302      return defaultValue;
303    }
304    return ((Number) value).longValue();
305  }
306
307  private static String getString(Map<?, ?> m, String key,
308      final String defaultValue) {
309    Object value = m.get(key);
310    if (value == null) {
311      return defaultValue;
312    }
313    return (String) value;
314  }
315
316  static List<?> getList(Map<?, ?> m, String key) {
317    Object list = m.get(key);
318    if (list instanceof List<?>) {
319      return (List<?>) list;
320    } else {
321      return null;
322    }
323  }
324
325  /** Convert a Json map to an DatanodeInfo object. */
326  static DatanodeInfo toDatanodeInfo(final Map<?, ?> m)
327      throws IOException {
328    if (m == null) {
329      return null;
330    }
331
332    // ipAddr and xferPort are the critical fields for accessing data.
333    // If any one of the two is missing, an exception needs to be thrown.
334
335    // Handle the case of old servers (1.x, 0.23.x) sending 'name' instead
336    // of ipAddr and xferPort.
337    Object tmpValue = m.get("ipAddr");
338    String ipAddr = (tmpValue == null) ? null : (String)tmpValue;
339    tmpValue = m.get("xferPort");
340    int xferPort = (tmpValue == null) ? -1 : (int)(long)(Long)tmpValue;
341    if (ipAddr == null) {
342      tmpValue = m.get("name");
343      if (tmpValue != null) {
344        String name = (String)tmpValue;
345        int colonIdx = name.indexOf(':');
346        if (colonIdx > 0) {
347          ipAddr = name.substring(0, colonIdx);
348          xferPort = Integer.parseInt(name.substring(colonIdx +1));
349        } else {
350          throw new IOException(
351              "Invalid value in server response: name=[" + name + "]");
352        }
353      } else {
354        throw new IOException(
355            "Missing both 'ipAddr' and 'name' in server response.");
356      }
357      // ipAddr is non-null & non-empty string at this point.
358    }
359
360    // Check the validity of xferPort.
361    if (xferPort == -1) {
362      throw new IOException(
363          "Invalid or missing 'xferPort' in server response.");
364    }
365
366    // TODO: Fix storageID
367    return new DatanodeInfo(
368        ipAddr,
369        (String)m.get("hostName"),
370        (String)m.get("storageID"),
371        xferPort,
372        ((Number) m.get("infoPort")).intValue(),
373        getInt(m, "infoSecurePort", 0),
374        ((Number) m.get("ipcPort")).intValue(),
375
376        getLong(m, "capacity", 0l),
377        getLong(m, "dfsUsed", 0l),
378        getLong(m, "remaining", 0l),
379        getLong(m, "blockPoolUsed", 0l),
380        getLong(m, "cacheCapacity", 0l),
381        getLong(m, "cacheUsed", 0l),
382        getLong(m, "lastUpdate", 0l),
383        getLong(m, "lastUpdateMonotonic", 0l),
384        getInt(m, "xceiverCount", 0),
385        getString(m, "networkLocation", ""),
386        AdminStates.valueOf(getString(m, "adminState", "NORMAL")));
387  }
388
389  /** Convert a DatanodeInfo[] to a Json array. */
390  private static Object[] toJsonArray(final DatanodeInfo[] array) {
391    if (array == null) {
392      return null;
393    } else if (array.length == 0) {
394      return EMPTY_OBJECT_ARRAY;
395    } else {
396      final Object[] a = new Object[array.length];
397      for(int i = 0; i < array.length; i++) {
398        a[i] = toJsonMap(array[i]);
399      }
400      return a;
401    }
402  }
403
404  /** Convert an Object[] to a DatanodeInfo[]. */
405  private static DatanodeInfo[] toDatanodeInfoArray(final List<?> objects)
406      throws IOException {
407    if (objects == null) {
408      return null;
409    } else if (objects.isEmpty()) {
410      return EMPTY_DATANODE_INFO_ARRAY;
411    } else {
412      final DatanodeInfo[] array = new DatanodeInfo[objects.size()];
413      int i = 0;
414      for (Object object : objects) {
415        array[i++] = toDatanodeInfo((Map<?, ?>) object);
416      }
417      return array;
418    }
419  }
420  
421  /** Convert a LocatedBlock to a Json map. */
422  private static Map<String, Object> toJsonMap(final LocatedBlock locatedblock
423      ) throws IOException {
424    if (locatedblock == null) {
425      return null;
426    }
427 
428    final Map<String, Object> m = new TreeMap<String, Object>();
429    m.put("blockToken", toJsonMap(locatedblock.getBlockToken()));
430    m.put("isCorrupt", locatedblock.isCorrupt());
431    m.put("startOffset", locatedblock.getStartOffset());
432    m.put("block", toJsonMap(locatedblock.getBlock()));
433    m.put("locations", toJsonArray(locatedblock.getLocations()));
434    m.put("cachedLocations", toJsonArray(locatedblock.getCachedLocations()));
435    return m;
436  }
437
438  /** Convert a Json map to LocatedBlock. */
439  private static LocatedBlock toLocatedBlock(final Map<?, ?> m) throws IOException {
440    if (m == null) {
441      return null;
442    }
443
444    final ExtendedBlock b = toExtendedBlock((Map<?, ?>)m.get("block"));
445    final DatanodeInfo[] locations = toDatanodeInfoArray(
446        getList(m, "locations"));
447    final long startOffset = ((Number) m.get("startOffset")).longValue();
448    final boolean isCorrupt = (Boolean)m.get("isCorrupt");
449    final DatanodeInfo[] cachedLocations = toDatanodeInfoArray(
450        getList(m, "cachedLocations"));
451
452    final LocatedBlock locatedblock = new LocatedBlock(b, locations,
453        null, null, startOffset, isCorrupt, cachedLocations);
454    locatedblock.setBlockToken(toBlockToken((Map<?, ?>)m.get("blockToken")));
455    return locatedblock;
456  }
457
458  /** Convert a LocatedBlock[] to a Json array. */
459  private static Object[] toJsonArray(final List<LocatedBlock> array
460      ) throws IOException {
461    if (array == null) {
462      return null;
463    } else if (array.size() == 0) {
464      return EMPTY_OBJECT_ARRAY;
465    } else {
466      final Object[] a = new Object[array.size()];
467      for(int i = 0; i < array.size(); i++) {
468        a[i] = toJsonMap(array.get(i));
469      }
470      return a;
471    }
472  }
473
474  /** Convert an List of Object to a List of LocatedBlock. */
475  private static List<LocatedBlock> toLocatedBlockList(
476      final List<?> objects) throws IOException {
477    if (objects == null) {
478      return null;
479    } else if (objects.isEmpty()) {
480      return Collections.emptyList();
481    } else {
482      final List<LocatedBlock> list = new ArrayList<>(objects.size());
483      for (Object object : objects) {
484        list.add(toLocatedBlock((Map<?, ?>) object));
485      }
486      return list;
487    }
488  }
489
490  /** Convert LocatedBlocks to a Json string. */
491  public static String toJsonString(final LocatedBlocks locatedblocks
492      ) throws IOException {
493    if (locatedblocks == null) {
494      return null;
495    }
496
497    final Map<String, Object> m = new TreeMap<String, Object>();
498    m.put("fileLength", locatedblocks.getFileLength());
499    m.put("isUnderConstruction", locatedblocks.isUnderConstruction());
500
501    m.put("locatedBlocks", toJsonArray(locatedblocks.getLocatedBlocks()));
502    m.put("lastLocatedBlock", toJsonMap(locatedblocks.getLastLocatedBlock()));
503    m.put("isLastBlockComplete", locatedblocks.isLastBlockComplete());
504    return toJsonString(LocatedBlocks.class, m);
505  }
506
507  /** Convert a Json map to LocatedBlock. */
508  public static LocatedBlocks toLocatedBlocks(final Map<?, ?> json
509      ) throws IOException {
510    if (json == null) {
511      return null;
512    }
513
514    final Map<?, ?> m = (Map<?, ?>)json.get(LocatedBlocks.class.getSimpleName());
515    final long fileLength = ((Number) m.get("fileLength")).longValue();
516    final boolean isUnderConstruction = (Boolean)m.get("isUnderConstruction");
517    final List<LocatedBlock> locatedBlocks = toLocatedBlockList(
518        getList(m, "locatedBlocks"));
519    final LocatedBlock lastLocatedBlock = toLocatedBlock(
520        (Map<?, ?>)m.get("lastLocatedBlock"));
521    final boolean isLastBlockComplete = (Boolean)m.get("isLastBlockComplete");
522    return new LocatedBlocks(fileLength, isUnderConstruction, locatedBlocks,
523        lastLocatedBlock, isLastBlockComplete, null);
524  }
525
526  /** Convert a ContentSummary to a Json string. */
527  public static String toJsonString(final ContentSummary contentsummary) {
528    if (contentsummary == null) {
529      return null;
530    }
531
532    final Map<String, Object> m = new TreeMap<String, Object>();
533    m.put("length", contentsummary.getLength());
534    m.put("fileCount", contentsummary.getFileCount());
535    m.put("directoryCount", contentsummary.getDirectoryCount());
536    m.put("quota", contentsummary.getQuota());
537    m.put("spaceConsumed", contentsummary.getSpaceConsumed());
538    m.put("spaceQuota", contentsummary.getSpaceQuota());
539    return toJsonString(ContentSummary.class, m);
540  }
541
542  /** Convert a Json map to a ContentSummary. */
543  public static ContentSummary toContentSummary(final Map<?, ?> json) {
544    if (json == null) {
545      return null;
546    }
547
548    final Map<?, ?> m = (Map<?, ?>)json.get(ContentSummary.class.getSimpleName());
549    final long length = ((Number) m.get("length")).longValue();
550    final long fileCount = ((Number) m.get("fileCount")).longValue();
551    final long directoryCount = ((Number) m.get("directoryCount")).longValue();
552    final long quota = ((Number) m.get("quota")).longValue();
553    final long spaceConsumed = ((Number) m.get("spaceConsumed")).longValue();
554    final long spaceQuota = ((Number) m.get("spaceQuota")).longValue();
555
556    return new ContentSummary.Builder().length(length).fileCount(fileCount).
557        directoryCount(directoryCount).quota(quota).spaceConsumed(spaceConsumed).
558        spaceQuota(spaceQuota).build();
559  }
560
561  /** Convert a MD5MD5CRC32FileChecksum to a Json string. */
562  public static String toJsonString(final MD5MD5CRC32FileChecksum checksum) {
563    if (checksum == null) {
564      return null;
565    }
566
567    final Map<String, Object> m = new TreeMap<String, Object>();
568    m.put("algorithm", checksum.getAlgorithmName());
569    m.put("length", checksum.getLength());
570    m.put("bytes", StringUtils.byteToHexString(checksum.getBytes()));
571    return toJsonString(FileChecksum.class, m);
572  }
573
574  /** Convert a Json map to a MD5MD5CRC32FileChecksum. */
575  public static MD5MD5CRC32FileChecksum toMD5MD5CRC32FileChecksum(
576      final Map<?, ?> json) throws IOException {
577    if (json == null) {
578      return null;
579    }
580
581    final Map<?, ?> m = (Map<?, ?>)json.get(FileChecksum.class.getSimpleName());
582    final String algorithm = (String)m.get("algorithm");
583    final int length = ((Number) m.get("length")).intValue();
584    final byte[] bytes = StringUtils.hexStringToByte((String)m.get("bytes"));
585
586    final DataInputStream in = new DataInputStream(new ByteArrayInputStream(bytes));
587    final DataChecksum.Type crcType = 
588        MD5MD5CRC32FileChecksum.getCrcTypeFromAlgorithmName(algorithm);
589    final MD5MD5CRC32FileChecksum checksum;
590
591    // Recreate what DFSClient would have returned.
592    switch(crcType) {
593      case CRC32:
594        checksum = new MD5MD5CRC32GzipFileChecksum();
595        break;
596      case CRC32C:
597        checksum = new MD5MD5CRC32CastagnoliFileChecksum();
598        break;
599      default:
600        throw new IOException("Unknown algorithm: " + algorithm);
601    }
602    checksum.readFields(in);
603
604    //check algorithm name
605    if (!checksum.getAlgorithmName().equals(algorithm)) {
606      throw new IOException("Algorithm not matched. Expected " + algorithm
607          + ", Received " + checksum.getAlgorithmName());
608    }
609    //check length
610    if (length != checksum.getLength()) {
611      throw new IOException("Length not matched: length=" + length
612          + ", checksum.getLength()=" + checksum.getLength());
613    }
614
615    return checksum;
616  }
617  /** Convert a AclStatus object to a Json string. */
618  public static String toJsonString(final AclStatus status) {
619    if (status == null) {
620      return null;
621    }
622
623    final Map<String, Object> m = new TreeMap<String, Object>();
624    m.put("owner", status.getOwner());
625    m.put("group", status.getGroup());
626    m.put("stickyBit", status.isStickyBit());
627
628    final List<String> stringEntries = new ArrayList<>();
629    for (AclEntry entry : status.getEntries()) {
630      stringEntries.add(entry.toString());
631    }
632    m.put("entries", stringEntries);
633
634    FsPermission perm = status.getPermission();
635    if (perm != null) {
636      m.put("permission", toString(perm));
637      if (perm.getAclBit()) {
638        m.put("aclBit", true);
639      }
640      if (perm.getEncryptedBit()) {
641        m.put("encBit", true);
642      }
643    }
644    final Map<String, Map<String, Object>> finalMap =
645        new TreeMap<String, Map<String, Object>>();
646    finalMap.put(AclStatus.class.getSimpleName(), m);
647
648    ObjectMapper mapper = new ObjectMapper();
649    try {
650      return mapper.writeValueAsString(finalMap);
651    } catch (IOException ignored) {
652    }
653    return null;
654  }
655
656  /** Convert a Json map to a AclStatus object. */
657  public static AclStatus toAclStatus(final Map<?, ?> json) {
658    if (json == null) {
659      return null;
660    }
661
662    final Map<?, ?> m = (Map<?, ?>) json.get(AclStatus.class.getSimpleName());
663
664    AclStatus.Builder aclStatusBuilder = new AclStatus.Builder();
665    aclStatusBuilder.owner((String) m.get("owner"));
666    aclStatusBuilder.group((String) m.get("group"));
667    aclStatusBuilder.stickyBit((Boolean) m.get("stickyBit"));
668    String permString = (String) m.get("permission");
669    if (permString != null) {
670      final FsPermission permission = toFsPermission(permString,
671          (Boolean) m.get("aclBit"), (Boolean) m.get("encBit"));
672      aclStatusBuilder.setPermission(permission);
673    }
674    final List<?> entries = (List<?>) m.get("entries");
675
676    List<AclEntry> aclEntryList = new ArrayList<AclEntry>();
677    for (Object entry : entries) {
678      AclEntry aclEntry = AclEntry.parseAclEntry((String) entry, true);
679      aclEntryList.add(aclEntry);
680    }
681    aclStatusBuilder.addEntries(aclEntryList);
682    return aclStatusBuilder.build();
683  }
684  
685  private static Map<String, Object> toJsonMap(final XAttr xAttr,
686      final XAttrCodec encoding) throws IOException {
687    if (xAttr == null) {
688      return null;
689    }
690 
691    final Map<String, Object> m = new TreeMap<String, Object>();
692    m.put("name", XAttrHelper.getPrefixName(xAttr));
693    m.put("value", xAttr.getValue() != null ? 
694        XAttrCodec.encodeValue(xAttr.getValue(), encoding) : null);
695    return m;
696  }
697  
698  private static Object[] toJsonArray(final List<XAttr> array,
699      final XAttrCodec encoding) throws IOException {
700    if (array == null) {
701      return null;
702    } else if (array.size() == 0) {
703      return EMPTY_OBJECT_ARRAY;
704    } else {
705      final Object[] a = new Object[array.size()];
706      for(int i = 0; i < array.size(); i++) {
707        a[i] = toJsonMap(array.get(i), encoding);
708      }
709      return a;
710    }
711  }
712  
713  public static String toJsonString(final List<XAttr> xAttrs, 
714      final XAttrCodec encoding) throws IOException {
715    final Map<String, Object> finalMap = new TreeMap<String, Object>();
716    finalMap.put("XAttrs", toJsonArray(xAttrs, encoding));
717    ObjectMapper mapper = new ObjectMapper();
718    return mapper.writeValueAsString(finalMap);
719  }
720  
721  public static String toJsonString(final List<XAttr> xAttrs)
722      throws IOException {
723    final List<String> names = Lists.newArrayListWithCapacity(xAttrs.size());
724    for (XAttr xAttr : xAttrs) {
725      names.add(XAttrHelper.getPrefixName(xAttr));
726    }
727    ObjectMapper mapper = new ObjectMapper();
728    String ret = mapper.writeValueAsString(names);
729    final Map<String, Object> finalMap = new TreeMap<String, Object>();
730    finalMap.put("XAttrNames", ret);
731    return mapper.writeValueAsString(finalMap);
732  }
733  
734  public static byte[] getXAttr(final Map<?, ?> json, final String name) 
735      throws IOException {
736    if (json == null) {
737      return null;
738    }
739    
740    Map<String, byte[]> xAttrs = toXAttrs(json);
741    if (xAttrs != null) {
742      return xAttrs.get(name);
743    }
744    
745    return null;
746  }
747
748  public static Map<String, byte[]> toXAttrs(final Map<?, ?> json) 
749      throws IOException {
750    if (json == null) {
751      return null;
752    }
753    return toXAttrMap(getList(json, "XAttrs"));
754  }
755  
756  public static List<String> toXAttrNames(final Map<?, ?> json)
757      throws IOException {
758    if (json == null) {
759      return null;
760    }
761
762    final String namesInJson = (String) json.get("XAttrNames");
763    ObjectReader reader = new ObjectMapper().reader(List.class);
764    final List<Object> xattrs = reader.readValue(namesInJson);
765    final List<String> names =
766      Lists.newArrayListWithCapacity(json.keySet().size());
767
768    for (Object xattr : xattrs) {
769      names.add((String) xattr);
770    }
771    return names;
772  }
773
774  private static Map<String, byte[]> toXAttrMap(final List<?> objects)
775      throws IOException {
776    if (objects == null) {
777      return null;
778    } else if (objects.isEmpty()) {
779      return Maps.newHashMap();
780    } else {
781      final Map<String, byte[]> xAttrs = Maps.newHashMap();
782      for (Object object : objects) {
783        Map<?, ?> m = (Map<?, ?>) object;
784        String name = (String) m.get("name");
785        String value = (String) m.get("value");
786        xAttrs.put(name, decodeXAttrValue(value));
787      }
788      return xAttrs;
789    }
790  }
791  
792  private static byte[] decodeXAttrValue(String value) throws IOException {
793    if (value != null) {
794      return XAttrCodec.decodeValue(value);
795    } else {
796      return new byte[0];
797    }
798  }
799}