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     */
018    package org.apache.hadoop.hdfs.web;
019    
020    import org.apache.hadoop.fs.*;
021    import org.apache.hadoop.fs.permission.AclEntry;
022    import org.apache.hadoop.fs.permission.AclStatus;
023    import org.apache.hadoop.fs.permission.FsPermission;
024    import org.apache.hadoop.hdfs.protocol.BlockStoragePolicy;
025    import org.apache.hadoop.hdfs.DFSUtil;
026    import org.apache.hadoop.hdfs.XAttrHelper;
027    import org.apache.hadoop.hdfs.protocol.*;
028    import org.apache.hadoop.hdfs.protocol.DatanodeInfo.AdminStates;
029    import org.apache.hadoop.hdfs.security.token.block.BlockTokenIdentifier;
030    import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier;
031    import org.apache.hadoop.hdfs.server.blockmanagement.BlockStoragePolicySuite;
032    import org.apache.hadoop.hdfs.server.namenode.INodeId;
033    import org.apache.hadoop.ipc.RemoteException;
034    import org.apache.hadoop.security.token.Token;
035    import org.apache.hadoop.security.token.TokenIdentifier;
036    import org.apache.hadoop.util.DataChecksum;
037    import org.apache.hadoop.util.StringUtils;
038    import org.mortbay.util.ajax.JSON;
039    
040    import com.google.common.collect.Lists;
041    import com.google.common.collect.Maps;
042    
043    import java.io.ByteArrayInputStream;
044    import java.io.DataInputStream;
045    import java.io.IOException;
046    import java.util.*;
047    
048    /** JSON Utilities */
049    public 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    }