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