com.antew.redditinpictures.library.service.RESTService.java Source code

Java tutorial

Introduction

Here is the source code for com.antew.redditinpictures.library.service.RESTService.java

Source

/*
 * Copyright (C) 2014 Antew
 *
 * 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 com.antew.redditinpictures.library.service;

import android.app.IntentService;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import com.antew.redditinpictures.library.Constants;
import com.antew.redditinpictures.library.util.Ln;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.GZIPInputStream;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
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.HttpRequestBase;
import org.apache.http.entity.HttpEntityWrapper;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;

/**
 * This is based an article by Neil Goodman, I've added the EXTRA_REQUEST_CODE so that callers can
 * have multiple request types
 *
 * @author Neil Goodman
 * @see <a
 * href="http://neilgoodman.net/2012/01/01/modern-techniques-for-implementing-rest-clients-on-android-4-0-and-below-part-2/">The
 * article</a>
 */
public class RESTService extends IntentService {
    public static final int GET = 0x1;
    public static final int POST = 0x2;
    public static final int PUT = 0x3;
    public static final int DELETE = 0x4;
    public static final String EXTRA_BUNDLE = "EXTRA_BUNDLE";
    public static final String EXTRA_HTTP_VERB = "EXTRA_HTTP_VERB";
    public static final String EXTRA_PARAMS = "EXTRA_PARAMS";
    public static final String EXTRA_COOKIE = "EXTRA_COOKIE";
    public static final String EXTRA_REPLACE_ALL = "EXTRA_REPLACE_ALL";
    public static final String EXTRA_REQUEST_CODE = "EXTRA_REQUEST_CODE";
    public static final String EXTRA_RESULT = "EXTRA_RESULT";
    public static final String EXTRA_STATUS_CODE = "EXTRA_STATUS_CODE";
    public static final String EXTRA_USER_AGENT = "EXTRA_USER_AGENT";
    public static final String EXTRA_PASS_THROUGH = "EXTRA_PASS_THROUGH";
    public static final String REST_RESULT = "REST_RESULT";
    private static final String TAG = RESTService.class.getSimpleName();

    public RESTService() {
        super(TAG);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Uri action = intent.getData();
        Bundle extras = intent.getExtras();

        if (extras == null || action == null) {
            Ln.e("You did not pass extras or data with the Intent.");
            return;
        }

        // We default to GET if no verb was specified.
        int verb = extras.getInt(EXTRA_HTTP_VERB, GET);
        RequestCode requestCode = (RequestCode) extras.getSerializable(EXTRA_REQUEST_CODE);
        Bundle params = extras.getParcelable(EXTRA_PARAMS);
        String cookie = extras.getString(EXTRA_COOKIE);
        String userAgent = extras.getString(EXTRA_USER_AGENT);

        // Items in this bundle are simply passed on in onRequestComplete
        Bundle passThrough = extras.getBundle(EXTRA_PASS_THROUGH);

        HttpEntity responseEntity = null;
        Intent result = new Intent(Constants.Broadcast.BROADCAST_HTTP_FINISHED);
        result.putExtra(EXTRA_PASS_THROUGH, passThrough);
        Bundle resultData = new Bundle();

        try {
            // Here we define our base request object which we will
            // send to our REST service via HttpClient.
            HttpRequestBase request = null;

            // Let's build our request based on the HTTP verb we were
            // given.
            switch (verb) {
            case GET: {
                request = new HttpGet();
                attachUriWithQuery(request, action, params);
            }
                break;

            case DELETE: {
                request = new HttpDelete();
                attachUriWithQuery(request, action, params);
            }
                break;

            case POST: {
                request = new HttpPost();
                request.setURI(new URI(action.toString()));

                // Attach form entity if necessary. Note: some REST APIs
                // require you to POST JSON. This is easy to do, simply use
                // postRequest.setHeader('Content-Type', 'application/json')
                // and StringEntity instead. Same thing for the PUT case
                // below.
                HttpPost postRequest = (HttpPost) request;

                if (params != null) {
                    UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(paramsToList(params));
                    postRequest.setEntity(formEntity);
                }
            }
                break;

            case PUT: {
                request = new HttpPut();
                request.setURI(new URI(action.toString()));

                // Attach form entity if necessary.
                HttpPut putRequest = (HttpPut) request;

                if (params != null) {
                    UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(paramsToList(params));
                    putRequest.setEntity(formEntity);
                }
            }
                break;
            }

            if (request != null) {
                DefaultHttpClient client = new DefaultHttpClient();

                // GZip requests
                // antew on 3/12/2014 - Disabling GZIP for now, need to figure this CloseGuard error:
                // 03-12 21:02:09.248    9674-9683/com.antew.redditinpictures.pro E/StrictMode A resource was acquired at attached stack trace but never released. See java.io.Closeable for information on avoiding resource leaks.
                //         java.lang.Throwable: Explicit termination method 'end' not called
                // at dalvik.system.CloseGuard.open(CloseGuard.java:184)
                // at java.util.zip.Inflater.<init>(Inflater.java:82)
                // at java.util.zip.GZIPInputStream.<init>(GZIPInputStream.java:96)
                // at java.util.zip.GZIPInputStream.<init>(GZIPInputStream.java:81)
                // at com.antew.redditinpictures.library.service.RESTService$GzipDecompressingEntity.getContent(RESTService.java:346)
                client.addRequestInterceptor(getGzipRequestInterceptor());
                client.addResponseInterceptor(getGzipResponseInterceptor());

                if (cookie != null) {
                    request.addHeader("Cookie", cookie);
                }

                if (userAgent != null) {
                    request.addHeader("User-Agent", Constants.Reddit.USER_AGENT);
                }

                // Let's send some useful debug information so we can monitor things
                // in LogCat.
                Ln.d("Executing request: %s %s ", verbToString(verb), action.toString());

                // Finally, we send our request using HTTP. This is the synchronous
                // long operation that we need to run on this thread.
                HttpResponse response = client.execute(request);

                responseEntity = response.getEntity();
                StatusLine responseStatus = response.getStatusLine();
                int statusCode = responseStatus != null ? responseStatus.getStatusCode() : 0;

                if (responseEntity != null) {
                    resultData.putString(REST_RESULT, EntityUtils.toString(responseEntity));
                    resultData.putSerializable(EXTRA_REQUEST_CODE, requestCode);
                    resultData.putInt(EXTRA_STATUS_CODE, statusCode);
                    result.putExtra(EXTRA_BUNDLE, resultData);

                    onRequestComplete(result);
                } else {
                    onRequestFailed(result, statusCode);
                }
            }
        } catch (URISyntaxException e) {
            Ln.e(e, "URI syntax was incorrect. %s %s ", verbToString(verb), action.toString());
            onRequestFailed(result, 0);
        } catch (UnsupportedEncodingException e) {
            Ln.e(e, "A UrlEncodedFormEntity was created with an unsupported encoding.");
            onRequestFailed(result, 0);
        } catch (ClientProtocolException e) {
            Ln.e(e, "There was a problem when sending the request.");
            onRequestFailed(result, 0);
        } catch (IOException e) {
            Ln.e(e, "There was a problem when sending the request.");
            onRequestFailed(result, 0);
        } finally {
            if (responseEntity != null) {
                try {
                    responseEntity.consumeContent();
                } catch (IOException ignored) {
                }
            }
        }
    }

    private static void attachUriWithQuery(HttpRequestBase request, Uri uri, Bundle params) {
        try {
            if (params == null) {
                // No params were given or they have already been
                // attached to the Uri.
                request.setURI(new URI(uri.toString()));
            } else {
                Uri.Builder uriBuilder = uri.buildUpon();

                // Loop through our params and append them to the Uri.
                for (BasicNameValuePair param : paramsToList(params)) {
                    uriBuilder.appendQueryParameter(param.getName(), param.getValue());
                }

                uri = uriBuilder.build();
                request.setURI(new URI(uri.toString()));
            }
        } catch (URISyntaxException e) {
            Ln.e(e, "URI syntax was incorrect: %s", uri.toString());
        }
    }

    private static List<BasicNameValuePair> paramsToList(Bundle params) {
        ArrayList<BasicNameValuePair> formList = new ArrayList<BasicNameValuePair>(params.size());

        for (String key : params.keySet()) {
            Object value = params.get(key);

            // We can only put Strings in a form entity, so we call the toString()
            // method to enforce. We also probably don't need to check for null here
            // but we do anyway because Bundle.get() can return null.
            if (value != null) {
                formList.add(new BasicNameValuePair(key, value.toString()));
            }
        }

        return formList;
    }

    /**
     * From apache examples
     *
     * @return
     *
     * @see <a
     * href="http://hc.apache.org/httpcomponents-client-ga/httpclient/examples/org/apache/http/examples/client/ClientGZipContentCompression.java">Apache
     * examples</a>
     */
    private HttpRequestInterceptor getGzipRequestInterceptor() {
        return new HttpRequestInterceptor() {

            public void process(final HttpRequest request, final HttpContext context)
                    throws HttpException, IOException {
                if (!request.containsHeader("Accept-Encoding")) {
                    request.addHeader("Accept-Encoding", "gzip");
                }
            }
        };
    }

    /**
     * From apache examples
     *
     * @return
     *
     * @see <a
     * href="http://hc.apache.org/httpcomponents-client-ga/httpclient/examples/org/apache/http/examples/client/ClientGZipContentCompression.java">Apache
     * examples</a>
     */
    private HttpResponseInterceptor getGzipResponseInterceptor() {
        return new HttpResponseInterceptor() {

            public void process(final HttpResponse response, final HttpContext context)
                    throws HttpException, IOException {
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    Header ceheader = entity.getContentEncoding();
                    if (ceheader != null) {
                        HeaderElement[] codecs = ceheader.getElements();
                        for (int i = 0; i < codecs.length; i++) {
                            if (codecs[i].getName().equalsIgnoreCase("gzip")) {
                                response.setEntity(new GzipDecompressingEntity(response.getEntity()));
                                return;
                            }
                        }
                    }
                }
            }
        };
    }

    private static String verbToString(int verb) {
        switch (verb) {
        case GET:
            return "GET";

        case POST:
            return "POST";

        case PUT:
            return "PUT";

        case DELETE:
            return "DELETE";
        }

        return "";
    }

    public void onRequestComplete(Intent result) {
        LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(result);
    }

    public void onRequestFailed(Intent result, int statusCode) {
        Bundle args = new Bundle();
        args.putInt(EXTRA_STATUS_CODE, statusCode);
        result.putExtra(EXTRA_BUNDLE, args);

        onRequestComplete(result);
    }

    /**
     * @see <a
     * href="http://svn.apache.org/repos/asf/httpcomponents/httpcore/branches/4.2.x/httpcore-contrib/src/main/java/org/apache/http/contrib/compress/GzipDecompressingEntity.java">Apache
     * project</a>
     */
    static class GzipDecompressingEntity extends HttpEntityWrapper {
        public GzipDecompressingEntity(final HttpEntity entity) {
            super(entity);
        }

        @Override
        public long getContentLength() {
            // length of ungzipped content is not known
            return -1;
        }

        @Override
        public InputStream getContent() throws IOException, IllegalStateException {
            // the wrapped entity's getContent() decides about repeatability
            InputStream wrappedin = wrappedEntity.getContent();
            return new GZIPInputStream(wrappedin);
        }
    }
}