org.pixmob.droidlink.net.NetworkClient.java Source code

Java tutorial

Introduction

Here is the source code for org.pixmob.droidlink.net.NetworkClient.java

Source

/*
 * Copyright (C) 2011 Pixmob (http://github.com/pixmob)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.pixmob.droidlink.net;

import static org.pixmob.droidlink.Constants.APPLICATION_NAME;
import static org.pixmob.droidlink.Constants.DEVELOPER_MODE;
import static org.pixmob.droidlink.Constants.REMOTE_API_VERSION;
import static org.pixmob.droidlink.Constants.SERVER_HOST;
import static org.pixmob.droidlink.Constants.SHARED_PREFERENCES_FILE;
import static org.pixmob.droidlink.Constants.SP_KEY_ACCOUNT;
import static org.pixmob.droidlink.Constants.SP_KEY_DEVICE_ID;
import static org.pixmob.droidlink.Constants.TAG;

import java.io.IOException;
import java.io.UnsupportedEncodingException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.StringEntity;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.pixmob.appengine.client.AppEngineAuthenticationException;
import org.pixmob.appengine.client.AppEngineClient;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;

/**
 * Network client for sending requests to the remote server. Requests are made
 * using the REST pattern, where data is encoded with JSON.
 * @author Pixmob
 */
public class NetworkClient {
    private static final String CHARSET = "UTF-8";
    private static String applicationVersion;

    private final AppEngineClient client;
    private final String deviceId;
    private final String account;

    private NetworkClient(final AppEngineClient client, final String accountName, final String deviceId) {
        this.client = client;
        this.account = accountName;
        this.deviceId = deviceId;
    }

    public static NetworkClient newInstance(Context context) {
        final SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE);

        // An account name is required for sending authenticated requests.
        final String account = prefs.getString(SP_KEY_ACCOUNT, null);
        if (account == null) {
            Log.w(TAG, "No account set for this device");
            return null;
        }

        // A device identifier is nearly required for every requests.
        final String deviceId = prefs.getString(SP_KEY_DEVICE_ID, null);
        if (deviceId == null) {
            Log.w(TAG, "No device identifier set");
            return null;
        }

        final AppEngineClient gaeClient = new AppEngineClient(context, SERVER_HOST);
        gaeClient.setAccount(account);
        gaeClient.setHttpUserAgent(generateUserAgent(context));

        return new NetworkClient(gaeClient, account, deviceId);
    }

    private static final String generateUserAgent(Context context) {
        if (applicationVersion == null) {
            try {
                applicationVersion = context.getPackageManager().getPackageInfo(context.getPackageName(),
                        0).versionName;
            } catch (NameNotFoundException e) {
                applicationVersion = "0.0.0";
            }
        }
        return APPLICATION_NAME + "/" + applicationVersion + " (" + Build.MANUFACTURER + " " + Build.MODEL
                + " with Android " + Build.VERSION.RELEASE + "/" + Build.VERSION.SDK_INT + ")";
    }

    public String getDeviceId() {
        return deviceId;
    }

    public String getAccount() {
        return account;
    }

    public JSONObject get(String serviceUri) throws IOException, AppEngineAuthenticationException {
        return (JSONObject) execute(HttpMethod.GET, serviceUri, null);
    }

    public JSONArray getAsArray(String serviceUri) throws IOException, AppEngineAuthenticationException {
        return (JSONArray) execute(HttpMethod.GET, serviceUri, null);
    }

    public JSONObject put(String serviceUri, JSONObject data) throws IOException, AppEngineAuthenticationException {
        return (JSONObject) execute(HttpMethod.PUT, serviceUri, data);
    }

    public JSONObject post(String serviceUri, JSONObject data)
            throws IOException, AppEngineAuthenticationException {
        return (JSONObject) execute(HttpMethod.POST, serviceUri, data);
    }

    public void delete(String serviceUri) throws IOException, AppEngineAuthenticationException {
        execute(HttpMethod.DELETE, serviceUri, null);
    }

    public void close() {
        client.close();
    }

    public HttpResponse execute(HttpUriRequest request) throws IOException, AppEngineAuthenticationException {
        HttpResponse resp = null;
        try {
            resp = client.execute(request);

            final int statusCode = resp.getStatusLine().getStatusCode();
            if (DEVELOPER_MODE) {
                Log.i(TAG, "Result for request " + request.getURI().toASCIIString() + ": " + statusCode);
            }

            return resp;
        } catch (AppEngineAuthenticationException e) {
            closeResources(request, resp);
            throw e;
        } catch (IOException e) {
            closeResources(request, resp);
            throw e;
        }
    }

    private Object execute(HttpMethod httpMethod, String serviceUri, JSONObject data)
            throws IOException, AppEngineAuthenticationException {
        final String requestUri = createServiceUri(serviceUri);
        final HttpUriRequest request = httpMethod.createRequest(requestUri, data);
        prepareJsonRequest(request);

        if (DEVELOPER_MODE) {
            Log.i(TAG, "Sending request to remote server: " + requestUri);
            if (request instanceof HttpEntityEnclosingRequestBase) {
                final HttpEntityEnclosingRequestBase r = (HttpEntityEnclosingRequestBase) request;
                final HttpEntity body = r.getEntity();
                if (body != null) {
                    final String strBody = EntityUtils.toString(body, CHARSET);
                    Log.d(TAG, "Body for request " + requestUri + ": " + strBody);
                }
            }
        }

        final HttpResponse resp = execute(request);
        final int statusCode = resp.getStatusLine().getStatusCode();
        if (isStatusNotFound(statusCode)) {
            throw new NetworkClientException(requestUri, statusCode, "Resource not found");
        }
        if (isStatusError(statusCode)) {
            throw new NetworkClientException(requestUri, statusCode, "Request failed on remote server");
        }
        if (!isStatusOK(statusCode)) {
            throw new NetworkClientException(requestUri, statusCode, "Request failed with error " + statusCode);
        }

        final HttpEntity entity = resp.getEntity();
        if (entity == null) {
            if (DEVELOPER_MODE) {
                Log.d(TAG, "No JSON result for request " + requestUri);
            }
            return null;
        }

        final String strResp = EntityUtils.toString(entity, CHARSET);
        if (TextUtils.isEmpty(strResp)) {
            if (DEVELOPER_MODE) {
                Log.d(TAG, "Empty JSON result for request " + requestUri);
            }
            return null;
        }

        if (DEVELOPER_MODE) {
            Log.d(TAG, "JSON result for request " + requestUri + ": " + strResp);
        }
        try {
            if (strResp.startsWith("[")) {
                // This is a JSON array.
                return new JSONArray(strResp);
            }
            return new JSONObject(strResp);
        } catch (JSONException e) {
            throw new NetworkClientException(requestUri, "Invalid JSON result", e);
        }
    }

    private static void closeResources(HttpUriRequest request, HttpResponse response) {
        try {
            request.abort();
        } catch (UnsupportedOperationException ignore) {
        }
        if (response != null) {
            final HttpEntity entity = response.getEntity();
            if (entity != null) {
                try {
                    entity.consumeContent();
                } catch (IOException ignore) {
                }
            }
        }
    }

    /**
     * Create a service Uri.
     */
    private static String createServiceUri(String resource) {
        if (!resource.startsWith("/")) {
            resource = "/" + resource;
        }
        return "https://" + SERVER_HOST + "/api/" + REMOTE_API_VERSION + resource;
    }

    /**
     * Check if the status code is a valid response.
     */
    private static boolean isStatusOK(int statusCode) {
        return statusCode == 200 || statusCode == 204 || statusCode == 201;
    }

    /**
     * Check if the status code is an error indicating that a resource was not
     * found.
     */
    private static boolean isStatusNotFound(int statusCode) {
        return statusCode == 404;
    }

    /**
     * Check if the status code is an error indicating the server failed to
     * process the request.
     */
    private static boolean isStatusError(int statusCode) {
        return statusCode == 500;
    }

    /**
     * Prepare a request for sending a Json formatted query.
     */
    private static void prepareJsonRequest(HttpRequest req) {
        req.setHeader(HTTP.CONTENT_TYPE, "application/json");
        req.addHeader("Accept", "application/json");
    }

    private static enum HttpMethod {
        GET, PUT, POST, DELETE;

        public HttpUriRequest createRequest(String requestUri, JSONObject data)
                throws UnsupportedEncodingException {
            switch (this) {
            case GET:
                return new HttpGet(requestUri);
            case POST:
                final HttpPost post = new HttpPost(requestUri);
                if (data != null) {
                    post.setEntity(new StringEntity(data.toString(), CHARSET));
                }
                return post;
            case PUT:
                final HttpPut put = new HttpPut(requestUri);
                if (data != null) {
                    put.setEntity(new StringEntity(data.toString(), CHARSET));
                }
                return put;
            case DELETE:
                return new HttpDelete(requestUri);
            default:
                // Unlikely to happen.
                throw new IllegalStateException("Unsupported Http method: " + this);
            }
        }
    }
}