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.io.MD5Hash;
046    import org.apache.hadoop.ipc.RemoteException;
047    import org.apache.hadoop.security.token.Token;
048    import org.apache.hadoop.security.token.TokenIdentifier;
049    import org.apache.hadoop.util.DataChecksum;
050    import org.apache.hadoop.util.StringUtils;
051    import org.mortbay.util.ajax.JSON;
052    
053    /** JSON Utilities */
054    public class JsonUtil {
055      private static final Object[] EMPTY_OBJECT_ARRAY = {};
056      private static final DatanodeInfo[] EMPTY_DATANODE_INFO_ARRAY = {};
057    
058      /** Convert a token object to a Json string. */
059      public static String toJsonString(final Token<? extends TokenIdentifier> token
060          ) throws IOException {
061        return toJsonString(Token.class, toJsonMap(token));
062      }
063    
064      private static Map<String, Object> toJsonMap(
065          final Token<? extends TokenIdentifier> token) throws IOException {
066        if (token == null) {
067          return null;
068        }
069    
070        final Map<String, Object> m = new TreeMap<String, Object>();
071        m.put("urlString", token.encodeToUrlString());
072        return m;
073      }
074    
075      /** Convert a Json map to a Token. */
076      public static Token<? extends TokenIdentifier> toToken(
077          final Map<?, ?> m) throws IOException {
078        if (m == null) {
079          return null;
080        }
081    
082        final Token<DelegationTokenIdentifier> token
083            = new Token<DelegationTokenIdentifier>();
084        token.decodeFromUrlString((String)m.get("urlString"));
085        return token;
086      }
087    
088      /** Convert a Json map to a Token of DelegationTokenIdentifier. */
089      @SuppressWarnings("unchecked")
090      public static Token<DelegationTokenIdentifier> toDelegationToken(
091          final Map<?, ?> json) throws IOException {
092        final Map<?, ?> m = (Map<?, ?>)json.get(Token.class.getSimpleName());
093        return (Token<DelegationTokenIdentifier>)toToken(m);
094      }
095    
096      /** Convert a Json map to a Token of BlockTokenIdentifier. */
097      @SuppressWarnings("unchecked")
098      private static Token<BlockTokenIdentifier> toBlockToken(
099          final Map<?, ?> m) throws IOException {
100        return (Token<BlockTokenIdentifier>)toToken(m);
101      }
102    
103      /** Convert a Token[] to a JSON array. */
104      private static Object[] toJsonArray(final Token<? extends TokenIdentifier>[] array
105          ) throws IOException {
106        if (array == null) {
107          return null;
108        } else if (array.length == 0) {
109          return EMPTY_OBJECT_ARRAY;
110        } else {
111          final Object[] a = new Object[array.length];
112          for(int i = 0; i < array.length; i++) {
113            a[i] = toJsonMap(array[i]);
114          }
115          return a;
116        }
117      }
118    
119      /** Convert a token object to a JSON string. */
120      public static String toJsonString(final Token<? extends TokenIdentifier>[] tokens
121          ) throws IOException {
122        if (tokens == null) {
123          return null;
124        }
125    
126        final Map<String, Object> m = new TreeMap<String, Object>();
127        m.put(Token.class.getSimpleName(), toJsonArray(tokens));
128        return toJsonString(Token.class.getSimpleName() + "s", m);
129      }
130    
131      /** Convert an Object[] to a List<Token<?>>.  */
132      private static List<Token<?>> toTokenList(final Object[] objects) throws IOException {
133        if (objects == null) {
134          return null;
135        } else if (objects.length == 0) {
136          return Collections.emptyList();
137        } else {
138          final List<Token<?>> list = new ArrayList<Token<?>>(objects.length);
139          for(int i = 0; i < objects.length; i++) {
140            list.add(toToken((Map<?, ?>)objects[i]));
141          }
142          return list;
143        }
144      }
145    
146      /** Convert a JSON map to a List<Token<?>>. */
147      public static List<Token<?>> toTokenList(final Map<?, ?> json) throws IOException {
148        if (json == null) {
149          return null;
150        }
151    
152        final Map<?, ?> m = (Map<?, ?>)json.get(Token.class.getSimpleName() + "s");
153        return toTokenList((Object[])m.get(Token.class.getSimpleName()));
154      }
155    
156      /** Convert an exception object to a Json string. */
157      public static String toJsonString(final Exception e) {
158        final Map<String, Object> m = new TreeMap<String, Object>();
159        m.put("exception", e.getClass().getSimpleName());
160        m.put("message", e.getMessage());
161        m.put("javaClassName", e.getClass().getName());
162        return toJsonString(RemoteException.class, m);
163      }
164    
165      /** Convert a Json map to a RemoteException. */
166      public static RemoteException toRemoteException(final Map<?, ?> json) {
167        final Map<?, ?> m = (Map<?, ?>)json.get(RemoteException.class.getSimpleName());
168        final String message = (String)m.get("message");
169        final String javaClassName = (String)m.get("javaClassName");
170        return new RemoteException(javaClassName, message);
171      }
172    
173      private static String toJsonString(final Class<?> clazz, final Object value) {
174        return toJsonString(clazz.getSimpleName(), value);
175      }
176    
177      /** Convert a key-value pair to a Json string. */
178      public static String toJsonString(final String key, final Object value) {
179        final Map<String, Object> m = new TreeMap<String, Object>();
180        m.put(key, value);
181        return JSON.toString(m);
182      }
183    
184      /** Convert a FsPermission object to a string. */
185      private static String toString(final FsPermission permission) {
186        return String.format("%o", permission.toShort());
187      }
188    
189      /** Convert a string to a FsPermission object. */
190      private static FsPermission toFsPermission(final String s) {
191        return new FsPermission(Short.parseShort(s, 8));
192      }
193    
194      static enum PathType {
195        FILE, DIRECTORY, SYMLINK;
196        
197        static PathType valueOf(HdfsFileStatus status) {
198          return status.isDir()? DIRECTORY: status.isSymlink()? SYMLINK: FILE;
199        }
200      }
201    
202      /** Convert a HdfsFileStatus object to a Json string. */
203      public static String toJsonString(final HdfsFileStatus status,
204          boolean includeType) {
205        if (status == null) {
206          return null;
207        }
208        final Map<String, Object> m = new TreeMap<String, Object>();
209        m.put("pathSuffix", status.getLocalName());
210        m.put("type", PathType.valueOf(status));
211        if (status.isSymlink()) {
212          m.put("symlink", status.getSymlink());
213        }
214    
215        m.put("length", status.getLen());
216        m.put("owner", status.getOwner());
217        m.put("group", status.getGroup());
218        m.put("permission", toString(status.getPermission()));
219        m.put("accessTime", status.getAccessTime());
220        m.put("modificationTime", status.getModificationTime());
221        m.put("blockSize", status.getBlockSize());
222        m.put("replication", status.getReplication());
223        return includeType ? toJsonString(FileStatus.class, m): JSON.toString(m);
224      }
225    
226      /** Convert a Json map to a HdfsFileStatus object. */
227      public static HdfsFileStatus toFileStatus(final Map<?, ?> json, boolean includesType) {
228        if (json == null) {
229          return null;
230        }
231    
232        final Map<?, ?> m = includesType ? 
233            (Map<?, ?>)json.get(FileStatus.class.getSimpleName()) : json;
234        final String localName = (String) m.get("pathSuffix");
235        final PathType type = PathType.valueOf((String) m.get("type"));
236        final byte[] symlink = type != PathType.SYMLINK? null
237            : DFSUtil.string2Bytes((String)m.get("symlink"));
238    
239        final long len = (Long) m.get("length");
240        final String owner = (String) m.get("owner");
241        final String group = (String) m.get("group");
242        final FsPermission permission = toFsPermission((String) m.get("permission"));
243        final long aTime = (Long) m.get("accessTime");
244        final long mTime = (Long) m.get("modificationTime");
245        final long blockSize = (Long) m.get("blockSize");
246        final short replication = (short) (long) (Long) m.get("replication");
247        return new HdfsFileStatus(len, type == PathType.DIRECTORY, replication,
248            blockSize, mTime, aTime, permission, owner, group,
249            symlink, DFSUtil.string2Bytes(localName));
250      }
251    
252      /** Convert an ExtendedBlock to a Json map. */
253      private static Map<String, Object> toJsonMap(final ExtendedBlock extendedblock) {
254        if (extendedblock == null) {
255          return null;
256        }
257    
258        final Map<String, Object> m = new TreeMap<String, Object>();
259        m.put("blockPoolId", extendedblock.getBlockPoolId());
260        m.put("blockId", extendedblock.getBlockId());
261        m.put("numBytes", extendedblock.getNumBytes());
262        m.put("generationStamp", extendedblock.getGenerationStamp());
263        return m;
264      }
265    
266      /** Convert a Json map to an ExtendedBlock object. */
267      private static ExtendedBlock toExtendedBlock(final Map<?, ?> m) {
268        if (m == null) {
269          return null;
270        }
271        
272        final String blockPoolId = (String)m.get("blockPoolId");
273        final long blockId = (Long)m.get("blockId");
274        final long numBytes = (Long)m.get("numBytes");
275        final long generationStamp = (Long)m.get("generationStamp");
276        return new ExtendedBlock(blockPoolId, blockId, numBytes, generationStamp);
277      }
278      
279      /** Convert a DatanodeInfo to a Json map. */
280      private static Map<String, Object> toJsonMap(final DatanodeInfo datanodeinfo) {
281        if (datanodeinfo == null) {
282          return null;
283        }
284    
285        final Map<String, Object> m = new TreeMap<String, Object>();
286        m.put("name", datanodeinfo.getName());
287        m.put("storageID", datanodeinfo.getStorageID());
288        m.put("infoPort", datanodeinfo.getInfoPort());
289    
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("hostName", datanodeinfo.getHostName());
300        m.put("adminState", datanodeinfo.getAdminState().name());
301        return m;
302      }
303    
304      /** Convert a Json map to an DatanodeInfo object. */
305      private static DatanodeInfo toDatanodeInfo(final Map<?, ?> m) {
306        if (m == null) {
307          return null;
308        }
309    
310        return new DatanodeInfo(
311            (String)m.get("name"),
312            (String)m.get("storageID"),
313            (int)(long)(Long)m.get("infoPort"),
314            (int)(long)(Long)m.get("ipcPort"),
315    
316            (Long)m.get("capacity"),
317            (Long)m.get("dfsUsed"),
318            (Long)m.get("remaining"),
319            (Long)m.get("blockPoolUsed"),
320            (Long)m.get("lastUpdate"),
321            (int)(long)(Long)m.get("xceiverCount"),
322            (String)m.get("networkLocation"),
323            (String)m.get("hostName"),
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    }