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