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