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.FsPermission;
022    import org.apache.hadoop.hdfs.DFSUtil;
023    import org.apache.hadoop.hdfs.protocol.*;
024    import org.apache.hadoop.hdfs.protocol.DatanodeInfo.AdminStates;
025    import org.apache.hadoop.hdfs.security.token.block.BlockTokenIdentifier;
026    import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier;
027    import org.apache.hadoop.hdfs.server.namenode.INodeId;
028    import org.apache.hadoop.ipc.RemoteException;
029    import org.apache.hadoop.security.token.Token;
030    import org.apache.hadoop.security.token.TokenIdentifier;
031    import org.apache.hadoop.util.DataChecksum;
032    import org.apache.hadoop.util.StringUtils;
033    import org.mortbay.util.ajax.JSON;
034    
035    import java.io.ByteArrayInputStream;
036    import java.io.DataInputStream;
037    import java.io.IOException;
038    import java.util.*;
039    
040    /** JSON Utilities */
041    public class JsonUtil {
042      private static final Object[] EMPTY_OBJECT_ARRAY = {};
043      private static final DatanodeInfo[] EMPTY_DATANODE_INFO_ARRAY = {};
044    
045      /** Convert a token object to a Json string. */
046      public static String toJsonString(final Token<? extends TokenIdentifier> token
047          ) throws IOException {
048        return toJsonString(Token.class, toJsonMap(token));
049      }
050    
051      private static Map<String, Object> toJsonMap(
052          final Token<? extends TokenIdentifier> token) throws IOException {
053        if (token == null) {
054          return null;
055        }
056    
057        final Map<String, Object> m = new TreeMap<String, Object>();
058        m.put("urlString", token.encodeToUrlString());
059        return m;
060      }
061    
062      /** Convert a Json map to a Token. */
063      public static Token<? extends TokenIdentifier> toToken(
064          final Map<?, ?> m) throws IOException {
065        if (m == null) {
066          return null;
067        }
068    
069        final Token<DelegationTokenIdentifier> token
070            = new Token<DelegationTokenIdentifier>();
071        token.decodeFromUrlString((String)m.get("urlString"));
072        return token;
073      }
074    
075      /** Convert a Json map to a Token of DelegationTokenIdentifier. */
076      @SuppressWarnings("unchecked")
077      public static Token<DelegationTokenIdentifier> toDelegationToken(
078          final Map<?, ?> json) throws IOException {
079        final Map<?, ?> m = (Map<?, ?>)json.get(Token.class.getSimpleName());
080        return (Token<DelegationTokenIdentifier>)toToken(m);
081      }
082    
083      /** Convert a Json map to a Token of BlockTokenIdentifier. */
084      @SuppressWarnings("unchecked")
085      private static Token<BlockTokenIdentifier> toBlockToken(
086          final Map<?, ?> m) throws IOException {
087        return (Token<BlockTokenIdentifier>)toToken(m);
088      }
089    
090      /** Convert a Token[] to a JSON array. */
091      private static Object[] toJsonArray(final Token<? extends TokenIdentifier>[] array
092          ) throws IOException {
093        if (array == null) {
094          return null;
095        } else if (array.length == 0) {
096          return EMPTY_OBJECT_ARRAY;
097        } else {
098          final Object[] a = new Object[array.length];
099          for(int i = 0; i < array.length; i++) {
100            a[i] = toJsonMap(array[i]);
101          }
102          return a;
103        }
104      }
105    
106      /** Convert a token object to a JSON string. */
107      public static String toJsonString(final Token<? extends TokenIdentifier>[] tokens
108          ) throws IOException {
109        if (tokens == null) {
110          return null;
111        }
112    
113        final Map<String, Object> m = new TreeMap<String, Object>();
114        m.put(Token.class.getSimpleName(), toJsonArray(tokens));
115        return toJsonString(Token.class.getSimpleName() + "s", m);
116      }
117    
118      /** Convert an Object[] to a List<Token<?>>.  */
119      private static List<Token<?>> toTokenList(final Object[] objects) throws IOException {
120        if (objects == null) {
121          return null;
122        } else if (objects.length == 0) {
123          return Collections.emptyList();
124        } else {
125          final List<Token<?>> list = new ArrayList<Token<?>>(objects.length);
126          for(int i = 0; i < objects.length; i++) {
127            list.add(toToken((Map<?, ?>)objects[i]));
128          }
129          return list;
130        }
131      }
132    
133      /** Convert a JSON map to a List<Token<?>>. */
134      public static List<Token<?>> toTokenList(final Map<?, ?> json) throws IOException {
135        if (json == null) {
136          return null;
137        }
138    
139        final Map<?, ?> m = (Map<?, ?>)json.get(Token.class.getSimpleName() + "s");
140        return toTokenList((Object[])m.get(Token.class.getSimpleName()));
141      }
142    
143      /** Convert an exception object to a Json string. */
144      public static String toJsonString(final Exception e) {
145        final Map<String, Object> m = new TreeMap<String, Object>();
146        m.put("exception", e.getClass().getSimpleName());
147        m.put("message", e.getMessage());
148        m.put("javaClassName", e.getClass().getName());
149        return toJsonString(RemoteException.class, m);
150      }
151    
152      /** Convert a Json map to a RemoteException. */
153      public static RemoteException toRemoteException(final Map<?, ?> json) {
154        final Map<?, ?> m = (Map<?, ?>)json.get(RemoteException.class.getSimpleName());
155        final String message = (String)m.get("message");
156        final String javaClassName = (String)m.get("javaClassName");
157        return new RemoteException(javaClassName, message);
158      }
159    
160      private static String toJsonString(final Class<?> clazz, final Object value) {
161        return toJsonString(clazz.getSimpleName(), value);
162      }
163    
164      /** Convert a key-value pair to a Json string. */
165      public static String toJsonString(final String key, final Object value) {
166        final Map<String, Object> m = new TreeMap<String, Object>();
167        m.put(key, value);
168        return JSON.toString(m);
169      }
170    
171      /** Convert a FsPermission object to a string. */
172      private static String toString(final FsPermission permission) {
173        return String.format("%o", permission.toShort());
174      }
175    
176      /** Convert a string to a FsPermission object. */
177      private static FsPermission toFsPermission(final String s) {
178        return new FsPermission(Short.parseShort(s, 8));
179      }
180    
181      static enum PathType {
182        FILE, DIRECTORY, SYMLINK;
183        
184        static PathType valueOf(HdfsFileStatus status) {
185          return status.isDir()? DIRECTORY: status.isSymlink()? SYMLINK: FILE;
186        }
187      }
188    
189      /** Convert a HdfsFileStatus object to a Json string. */
190      public static String toJsonString(final HdfsFileStatus status,
191          boolean includeType) {
192        if (status == null) {
193          return null;
194        }
195        final Map<String, Object> m = new TreeMap<String, Object>();
196        m.put("pathSuffix", status.getLocalName());
197        m.put("type", PathType.valueOf(status));
198        if (status.isSymlink()) {
199          m.put("symlink", status.getSymlink());
200        }
201    
202        m.put("length", status.getLen());
203        m.put("owner", status.getOwner());
204        m.put("group", status.getGroup());
205        m.put("permission", toString(status.getPermission()));
206        m.put("accessTime", status.getAccessTime());
207        m.put("modificationTime", status.getModificationTime());
208        m.put("blockSize", status.getBlockSize());
209        m.put("replication", status.getReplication());
210        m.put("fileId", status.getFileId());
211        m.put("childrenNum", status.getChildrenNum());
212        return includeType ? toJsonString(FileStatus.class, m): JSON.toString(m);
213      }
214    
215      /** Convert a Json map to a HdfsFileStatus object. */
216      public static HdfsFileStatus toFileStatus(final Map<?, ?> json, boolean includesType) {
217        if (json == null) {
218          return null;
219        }
220    
221        final Map<?, ?> m = includesType ? 
222            (Map<?, ?>)json.get(FileStatus.class.getSimpleName()) : json;
223        final String localName = (String) m.get("pathSuffix");
224        final PathType type = PathType.valueOf((String) m.get("type"));
225        final byte[] symlink = type != PathType.SYMLINK? null
226            : DFSUtil.string2Bytes((String)m.get("symlink"));
227    
228        final long len = (Long) m.get("length");
229        final String owner = (String) m.get("owner");
230        final String group = (String) m.get("group");
231        final FsPermission permission = toFsPermission((String) m.get("permission"));
232        final long aTime = (Long) m.get("accessTime");
233        final long mTime = (Long) m.get("modificationTime");
234        final long blockSize = (Long) m.get("blockSize");
235        final short replication = (short) (long) (Long) m.get("replication");
236        final long fileId = m.containsKey("fileId") ? (Long) m.get("fileId")
237            : INodeId.GRANDFATHER_INODE_ID;
238        Long childrenNumLong = (Long) m.get("childrenNum");
239        final int childrenNum = (childrenNumLong == null) ? -1
240                : childrenNumLong.intValue();
241        return new HdfsFileStatus(len, type == PathType.DIRECTORY, replication,
242            blockSize, mTime, aTime, permission, owner, group,
243            symlink, DFSUtil.string2Bytes(localName), fileId, childrenNum);
244      }
245    
246      /** Convert an ExtendedBlock to a Json map. */
247      private static Map<String, Object> toJsonMap(final ExtendedBlock extendedblock) {
248        if (extendedblock == null) {
249          return null;
250        }
251    
252        final Map<String, Object> m = new TreeMap<String, Object>();
253        m.put("blockPoolId", extendedblock.getBlockPoolId());
254        m.put("blockId", extendedblock.getBlockId());
255        m.put("numBytes", extendedblock.getNumBytes());
256        m.put("generationStamp", extendedblock.getGenerationStamp());
257        return m;
258      }
259    
260      /** Convert a Json map to an ExtendedBlock object. */
261      private static ExtendedBlock toExtendedBlock(final Map<?, ?> m) {
262        if (m == null) {
263          return null;
264        }
265        
266        final String blockPoolId = (String)m.get("blockPoolId");
267        final long blockId = (Long)m.get("blockId");
268        final long numBytes = (Long)m.get("numBytes");
269        final long generationStamp = (Long)m.get("generationStamp");
270        return new ExtendedBlock(blockPoolId, blockId, numBytes, generationStamp);
271      }
272      
273      /** Convert a DatanodeInfo to a Json map. */
274      private static Map<String, Object> toJsonMap(final DatanodeInfo datanodeinfo) {
275        if (datanodeinfo == null) {
276          return null;
277        }
278    
279        final Map<String, Object> m = new TreeMap<String, Object>();
280        m.put("ipAddr", datanodeinfo.getIpAddr());
281        m.put("hostName", datanodeinfo.getHostName());
282        m.put("storageID", datanodeinfo.getStorageID());
283        m.put("xferPort", datanodeinfo.getXferPort());
284        m.put("infoPort", datanodeinfo.getInfoPort());
285        m.put("infoSecurePort", datanodeinfo.getInfoSecurePort());
286        m.put("ipcPort", datanodeinfo.getIpcPort());
287    
288        m.put("capacity", datanodeinfo.getCapacity());
289        m.put("dfsUsed", datanodeinfo.getDfsUsed());
290        m.put("remaining", datanodeinfo.getRemaining());
291        m.put("blockPoolUsed", datanodeinfo.getBlockPoolUsed());
292        m.put("lastUpdate", datanodeinfo.getLastUpdate());
293        m.put("xceiverCount", datanodeinfo.getXceiverCount());
294        m.put("networkLocation", datanodeinfo.getNetworkLocation());
295        m.put("adminState", datanodeinfo.getAdminState().name());
296        return m;
297      }
298    
299      /** Convert a Json map to an DatanodeInfo object. */
300      private static DatanodeInfo toDatanodeInfo(final Map<?, ?> m) {
301        if (m == null) {
302          return null;
303        }
304    
305        return new DatanodeInfo(
306            (String)m.get("ipAddr"),
307            (String)m.get("hostName"),
308            (String)m.get("storageID"),
309            (int)(long)(Long)m.get("xferPort"),
310            (int)(long)(Long)m.get("infoPort"),
311            (int)(long)(Long)m.get("infoSecurePort"),
312            (int)(long)(Long)m.get("ipcPort"),
313    
314            (Long)m.get("capacity"),
315            (Long)m.get("dfsUsed"),
316            (Long)m.get("remaining"),
317            (Long)m.get("blockPoolUsed"),
318            (Long)m.get("lastUpdate"),
319            (int)(long)(Long)m.get("xceiverCount"),
320            (String)m.get("networkLocation"),
321            AdminStates.valueOf((String)m.get("adminState")));
322      }
323    
324      /** Convert a DatanodeInfo[] to a Json array. */
325      private static Object[] toJsonArray(final DatanodeInfo[] array) {
326        if (array == null) {
327          return null;
328        } else if (array.length == 0) {
329          return EMPTY_OBJECT_ARRAY;
330        } else {
331          final Object[] a = new Object[array.length];
332          for(int i = 0; i < array.length; i++) {
333            a[i] = toJsonMap(array[i]);
334          }
335          return a;
336        }
337      }
338    
339      /** Convert an Object[] to a DatanodeInfo[]. */
340      private static DatanodeInfo[] toDatanodeInfoArray(final Object[] objects) {
341        if (objects == null) {
342          return null;
343        } else if (objects.length == 0) {
344          return EMPTY_DATANODE_INFO_ARRAY;
345        } else {
346          final DatanodeInfo[] array = new DatanodeInfo[objects.length];
347          for(int i = 0; i < array.length; i++) {
348            array[i] = toDatanodeInfo((Map<?, ?>) objects[i]);
349          }
350          return array;
351        }
352      }
353      
354      /** Convert a LocatedBlock to a Json map. */
355      private static Map<String, Object> toJsonMap(final LocatedBlock locatedblock
356          ) throws IOException {
357        if (locatedblock == null) {
358          return null;
359        }
360     
361        final Map<String, Object> m = new TreeMap<String, Object>();
362        m.put("blockToken", toJsonMap(locatedblock.getBlockToken()));
363        m.put("isCorrupt", locatedblock.isCorrupt());
364        m.put("startOffset", locatedblock.getStartOffset());
365        m.put("block", toJsonMap(locatedblock.getBlock()));
366        m.put("locations", toJsonArray(locatedblock.getLocations()));
367        return m;
368      }
369    
370      /** Convert a Json map to LocatedBlock. */
371      private static LocatedBlock toLocatedBlock(final Map<?, ?> m) throws IOException {
372        if (m == null) {
373          return null;
374        }
375    
376        final ExtendedBlock b = toExtendedBlock((Map<?, ?>)m.get("block"));
377        final DatanodeInfo[] locations = toDatanodeInfoArray(
378            (Object[])m.get("locations"));
379        final long startOffset = (Long)m.get("startOffset");
380        final boolean isCorrupt = (Boolean)m.get("isCorrupt");
381    
382        final LocatedBlock locatedblock = new LocatedBlock(b, locations, startOffset, isCorrupt);
383        locatedblock.setBlockToken(toBlockToken((Map<?, ?>)m.get("blockToken")));
384        return locatedblock;
385      }
386    
387      /** Convert a LocatedBlock[] to a Json array. */
388      private static Object[] toJsonArray(final List<LocatedBlock> array
389          ) throws IOException {
390        if (array == null) {
391          return null;
392        } else if (array.size() == 0) {
393          return EMPTY_OBJECT_ARRAY;
394        } else {
395          final Object[] a = new Object[array.size()];
396          for(int i = 0; i < array.size(); i++) {
397            a[i] = toJsonMap(array.get(i));
398          }
399          return a;
400        }
401      }
402    
403      /** Convert an Object[] to a List of LocatedBlock. */
404      private static List<LocatedBlock> toLocatedBlockList(final Object[] objects
405          ) throws IOException {
406        if (objects == null) {
407          return null;
408        } else if (objects.length == 0) {
409          return Collections.emptyList();
410        } else {
411          final List<LocatedBlock> list = new ArrayList<LocatedBlock>(objects.length);
412          for(int i = 0; i < objects.length; i++) {
413            list.add(toLocatedBlock((Map<?, ?>)objects[i]));
414          }
415          return list;
416        }
417      }
418    
419      /** Convert LocatedBlocks to a Json string. */
420      public static String toJsonString(final LocatedBlocks locatedblocks
421          ) throws IOException {
422        if (locatedblocks == null) {
423          return null;
424        }
425    
426        final Map<String, Object> m = new TreeMap<String, Object>();
427        m.put("fileLength", locatedblocks.getFileLength());
428        m.put("isUnderConstruction", locatedblocks.isUnderConstruction());
429    
430        m.put("locatedBlocks", toJsonArray(locatedblocks.getLocatedBlocks()));
431        m.put("lastLocatedBlock", toJsonMap(locatedblocks.getLastLocatedBlock()));
432        m.put("isLastBlockComplete", locatedblocks.isLastBlockComplete());
433        return toJsonString(LocatedBlocks.class, m);
434      }
435    
436      /** Convert a Json map to LocatedBlock. */
437      public static LocatedBlocks toLocatedBlocks(final Map<?, ?> json
438          ) throws IOException {
439        if (json == null) {
440          return null;
441        }
442    
443        final Map<?, ?> m = (Map<?, ?>)json.get(LocatedBlocks.class.getSimpleName());
444        final long fileLength = (Long)m.get("fileLength");
445        final boolean isUnderConstruction = (Boolean)m.get("isUnderConstruction");
446        final List<LocatedBlock> locatedBlocks = toLocatedBlockList(
447            (Object[])m.get("locatedBlocks"));
448        final LocatedBlock lastLocatedBlock = toLocatedBlock(
449            (Map<?, ?>)m.get("lastLocatedBlock"));
450        final boolean isLastBlockComplete = (Boolean)m.get("isLastBlockComplete");
451        return new LocatedBlocks(fileLength, isUnderConstruction, locatedBlocks,
452            lastLocatedBlock, isLastBlockComplete);
453      }
454    
455      /** Convert a ContentSummary to a Json string. */
456      public static String toJsonString(final ContentSummary contentsummary) {
457        if (contentsummary == null) {
458          return null;
459        }
460    
461        final Map<String, Object> m = new TreeMap<String, Object>();
462        m.put("length", contentsummary.getLength());
463        m.put("fileCount", contentsummary.getFileCount());
464        m.put("directoryCount", contentsummary.getDirectoryCount());
465        m.put("quota", contentsummary.getQuota());
466        m.put("spaceConsumed", contentsummary.getSpaceConsumed());
467        m.put("spaceQuota", contentsummary.getSpaceQuota());
468        return toJsonString(ContentSummary.class, m);
469      }
470    
471      /** Convert a Json map to a ContentSummary. */
472      public static ContentSummary toContentSummary(final Map<?, ?> json) {
473        if (json == null) {
474          return null;
475        }
476    
477        final Map<?, ?> m = (Map<?, ?>)json.get(ContentSummary.class.getSimpleName());
478        final long length = (Long)m.get("length");
479        final long fileCount = (Long)m.get("fileCount");
480        final long directoryCount = (Long)m.get("directoryCount");
481        final long quota = (Long)m.get("quota");
482        final long spaceConsumed = (Long)m.get("spaceConsumed");
483        final long spaceQuota = (Long)m.get("spaceQuota");
484    
485        return new ContentSummary(length, fileCount, directoryCount,
486            quota, spaceConsumed, spaceQuota);
487      }
488    
489      /** Convert a MD5MD5CRC32FileChecksum to a Json string. */
490      public static String toJsonString(final MD5MD5CRC32FileChecksum checksum) {
491        if (checksum == null) {
492          return null;
493        }
494    
495        final Map<String, Object> m = new TreeMap<String, Object>();
496        m.put("algorithm", checksum.getAlgorithmName());
497        m.put("length", checksum.getLength());
498        m.put("bytes", StringUtils.byteToHexString(checksum.getBytes()));
499        return toJsonString(FileChecksum.class, m);
500      }
501    
502      /** Convert a Json map to a MD5MD5CRC32FileChecksum. */
503      public static MD5MD5CRC32FileChecksum toMD5MD5CRC32FileChecksum(
504          final Map<?, ?> json) throws IOException {
505        if (json == null) {
506          return null;
507        }
508    
509        final Map<?, ?> m = (Map<?, ?>)json.get(FileChecksum.class.getSimpleName());
510        final String algorithm = (String)m.get("algorithm");
511        final int length = (int)(long)(Long)m.get("length");
512        final byte[] bytes = StringUtils.hexStringToByte((String)m.get("bytes"));
513    
514        final DataInputStream in = new DataInputStream(new ByteArrayInputStream(bytes));
515        final DataChecksum.Type crcType = 
516            MD5MD5CRC32FileChecksum.getCrcTypeFromAlgorithmName(algorithm);
517        final MD5MD5CRC32FileChecksum checksum;
518    
519        // Recreate what DFSClient would have returned.
520        switch(crcType) {
521          case CRC32:
522            checksum = new MD5MD5CRC32GzipFileChecksum();
523            break;
524          case CRC32C:
525            checksum = new MD5MD5CRC32CastagnoliFileChecksum();
526            break;
527          default:
528            throw new IOException("Unknown algorithm: " + algorithm);
529        }
530        checksum.readFields(in);
531    
532        //check algorithm name
533        if (!checksum.getAlgorithmName().equals(algorithm)) {
534          throw new IOException("Algorithm not matched. Expected " + algorithm
535              + ", Received " + checksum.getAlgorithmName());
536        }
537        //check length
538        if (length != checksum.getLength()) {
539          throw new IOException("Length not matched: length=" + length
540              + ", checksum.getLength()=" + checksum.getLength());
541        }
542    
543        return checksum;
544      }
545    }