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.fs.http.client;
019    
020    import org.apache.hadoop.classification.InterfaceAudience;
021    import org.apache.hadoop.fs.Path;
022    import org.json.simple.JSONObject;
023    import org.json.simple.parser.JSONParser;
024    import org.json.simple.parser.ParseException;
025    
026    import java.io.IOException;
027    import java.io.InputStreamReader;
028    import java.lang.reflect.Constructor;
029    import java.net.HttpURLConnection;
030    import java.net.URI;
031    import java.net.URL;
032    import java.net.URLEncoder;
033    import java.text.MessageFormat;
034    import java.util.Map;
035    
036    /**
037     * Utility methods used by HttpFS classes.
038     */
039    @InterfaceAudience.Private
040    public class HttpFSUtils {
041    
042      public static final String SERVICE_NAME = "/webhdfs";
043    
044      public static final String SERVICE_VERSION = "/v1";
045    
046      private static final String SERVICE_PATH = SERVICE_NAME + SERVICE_VERSION;
047    
048      /**
049       * Convenience method that creates an HTTP <code>URL</code> for the
050       * HttpFSServer file system operations.
051       * <p/>
052       *
053       * @param path the file path.
054       * @param params the query string parameters.
055       *
056       * @return a <code>URL</code> for the HttpFSServer server,
057       *
058       * @throws IOException thrown if an IO error occurrs.
059       */
060      static URL createHttpURL(Path path, Map<String, String> params)
061        throws IOException {
062        URI uri = path.toUri();
063        String realScheme;
064        if (uri.getScheme().equalsIgnoreCase(HttpFSFileSystem.SCHEME)) {
065          realScheme = "http";
066        } else {
067          throw new IllegalArgumentException(MessageFormat.format(
068            "Invalid scheme [{0}] it should be 'webhdfs'", uri));
069        }
070        StringBuilder sb = new StringBuilder();
071        sb.append(realScheme).append("://").append(uri.getAuthority()).
072          append(SERVICE_PATH).append(uri.getPath());
073    
074        String separator = "?";
075        for (Map.Entry<String, String> entry : params.entrySet()) {
076          sb.append(separator).append(entry.getKey()).append("=").
077            append(URLEncoder.encode(entry.getValue(), "UTF8"));
078          separator = "&";
079        }
080        return new URL(sb.toString());
081      }
082    
083      /**
084       * Validates the status of an <code>HttpURLConnection</code> against an
085       * expected HTTP status code. If the current status code is not the expected
086       * one it throws an exception with a detail message using Server side error
087       * messages if available.
088       *
089       * @param conn the <code>HttpURLConnection</code>.
090       * @param expected the expected HTTP status code.
091       *
092       * @throws IOException thrown if the current status code does not match the
093       * expected one.
094       */
095      @SuppressWarnings({"unchecked", "deprecation"})
096      static void validateResponse(HttpURLConnection conn, int expected)
097        throws IOException {
098        int status = conn.getResponseCode();
099        if (status != expected) {
100          try {
101            JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
102            json = (JSONObject) json.get(HttpFSFileSystem.ERROR_JSON);
103            String message = (String) json.get(HttpFSFileSystem.ERROR_MESSAGE_JSON);
104            String exception = (String)
105              json.get(HttpFSFileSystem.ERROR_EXCEPTION_JSON);
106            String className = (String)
107              json.get(HttpFSFileSystem.ERROR_CLASSNAME_JSON);
108    
109            try {
110              ClassLoader cl = HttpFSFileSystem.class.getClassLoader();
111              Class klass = cl.loadClass(className);
112              Constructor constr = klass.getConstructor(String.class);
113              throw (IOException) constr.newInstance(message);
114            } catch (IOException ex) {
115              throw ex;
116            } catch (Exception ex) {
117              throw new IOException(MessageFormat.format("{0} - {1}", exception,
118                                                         message));
119            }
120          } catch (IOException ex) {
121            if (ex.getCause() instanceof IOException) {
122              throw (IOException) ex.getCause();
123            }
124            throw new IOException(
125              MessageFormat.format("HTTP status [{0}], {1}",
126                                   status, conn.getResponseMessage()));
127          }
128        }
129      }
130    
131      /**
132       * Convenience method that JSON Parses the <code>InputStream</code> of a
133       * <code>HttpURLConnection</code>.
134       *
135       * @param conn the <code>HttpURLConnection</code>.
136       *
137       * @return the parsed JSON object.
138       *
139       * @throws IOException thrown if the <code>InputStream</code> could not be
140       * JSON parsed.
141       */
142      static Object jsonParse(HttpURLConnection conn) throws IOException {
143        try {
144          JSONParser parser = new JSONParser();
145          return parser.parse(new InputStreamReader(conn.getInputStream()));
146        } catch (ParseException ex) {
147          throw new IOException("JSON parser error, " + ex.getMessage(), ex);
148        }
149      }
150    }