org.openbmap.soapclient.FileUploader.java Source code

Java tutorial

Introduction

Here is the source code for org.openbmap.soapclient.FileUploader.java

Source

/*
   Radiobeacon - Openbmap wifi and cell logger
Copyright (C) 2013  wish7
    
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
    
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.
    
You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.openbmap.soapclient;

import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;

import android.os.AsyncTask;
import android.util.Base64;
import android.util.Log;

/**
 * Uploads xml files as multipart message to webservice.
 */
public class FileUploader extends AsyncTask<String, Integer, Boolean> {

    /**
     * Socket and connection parameters for http upload
     */
    //private static final int SOCKET_TIMEOUT = 30000;
    //private static final int CONNECTION_TIMEOUT = 30000;

    private static final String TAG = FileUploader.class.getSimpleName();

    /**
     * Field for multipart message: username
     */
    private static final String LOGIN_FIELD = "openBmap_login";

    /**
     * Field for multipart message: password
     */
    private static final String PASSWORD_FIELD = "openBmap_passwd";

    /**
     * Field for multipart message: file
     */
    private static final String FILE_FIELD = "file";

    /**
     * Retry upload how many times on failed upload
     * 0 means no retry
     */
    private static final int MAX_RETRIES = 2;

    /**
     * Used for callbacks.
     */
    private final FileUploadListener mListener;

    public interface FileUploadListener {
        void onUploadCompleted(String file);

        void onUploadFailed(final String file, String error);
    }

    /**
     * Message in case of an error
     */
    private String lastErrorMsg = null;

    private final String mUser;
    private final String mPassword;
    private final String mServer;
    private String mFile;

    /**
     * If set to true, following the upload a request is sent to the server, verifying file is actually there
     * Background: On wrong or missing credentials server accepts upload (server response 200),
     * but discards the file later on.
     * 
     * Reference:
     * http://code.google.com/p/openbmap/issues/detail?id=40
     */
    private final boolean mValidateServerSide;

    /**
     * Remote folder, in which uploaded files are searched upon validation
     * No trailing slash!
     */
    private final String mValidationBaseUrl;

    /**
     * 
     * @param listener UploadTaskListener which is informed about upload result
     * @param user      user name
     * @param password password
     * @param server   remote URL
     * @param validateServerSide   additional check, whether file is actually uploaded (more safe than just relying on server response 200)
     * @param validationBaseUrl      base URL for additional check 
     */
    public FileUploader(final FileUploadListener listener, final String user, final String password,
            final String server, final boolean validateServerSide, final String validationBaseUrl) {
        mListener = listener;
        mUser = user;
        mPassword = password;
        mServer = server;
        mValidateServerSide = validateServerSide;
        mValidationBaseUrl = validationBaseUrl;
    }

    /**
     * Background task. Note: When uploading several files
     * upload is continued even on errors (i.e. will try to upload other files)
     * @param params filenames 
     * @return true on success, false if at least one file upload failed
     */
    @Deprecated
    @Override
    protected final Boolean doInBackground(final String... params) {
        Log.i(TAG, "Uploading " + params[0]);
        mFile = params[0];

        final Boolean httpResponseGood = scheduleUpload(mFile);

        // perform additional checks if needed
        if (mValidateServerSide && httpResponseGood && !fileActuallyExists(mFile)) {
            final String filename = mUser + "_"
                    + mFile.substring(mFile.lastIndexOf(File.separator) + 1, mFile.length());

            lastErrorMsg = "Server reported code 200 on " + filename + ", but the file's not there..";
            Log.e(TAG, lastErrorMsg);
            Log.e(TAG, "Hint: Check user/password! Typo?");
            return false;
        }

        return httpResponseGood;
    }

    @Override
    protected final void onPostExecute(final Boolean success) {
        if (success) {
            if (mListener != null) {
                mListener.onUploadCompleted(mFile);
            }
            return;
        } else {
            if (mListener != null) {
                Log.e(TAG, "Upload failed " + lastErrorMsg);
                mListener.onUploadFailed(mFile, lastErrorMsg);
            }
            return;
        }
    }

    /**
     * Sends a head request to the server to check whether uploaded file actually exists on the server
     * @param file   file
     * @return true if file was available
     */
    private boolean fileActuallyExists(final String file) {
        if (mValidationBaseUrl == null) {
            Log.i(TAG, "Validation url not set. Skipping server side validation");
            return true;
        }

        try {
            // not very generic yet
            final String expectedUrl = mValidationBaseUrl + mUser + "_"
                    + file.substring(file.lastIndexOf(File.separator) + 1, file.length());

            final HttpURLConnection connection = (HttpURLConnection) new URL(expectedUrl).openConnection();
            connection.setRequestMethod("HEAD");
            final int responseCode = connection.getResponseCode();
            if (responseCode != 200) {
                // Not OK.
                return false;
            }
        } catch (final MalformedURLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return true;
        } catch (final IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return true;
    }

    /**
     * 
     * @param file File to upload (full path).
     * @return true on success, false on error
     */
    private boolean scheduleUpload(final String file) {
        boolean success = performUpload(file);

        // simple resume upload mechanism on failed upload
        int i = 0;
        while (!success && i < MAX_RETRIES) {
            Log.w(TAG, "Upload failed: Retry " + i + ": " + file);
            success = performUpload(file);
            i++;
        }

        if (!success) {
            lastErrorMsg = "Upload failed after " + i + " retries";
            Log.e(TAG, "Upload failed after " + i + " retries");
        }

        return success;
    }

    /**
     * @param file
     * @return
     */
    private boolean performUpload(final String file) {
        // TODO check network state
        // @see http://developer.android.com/training/basics/network-ops/connecting.html

        // Adjust HttpClient parameters
        final HttpParams httpParameters = new BasicHttpParams();
        // Set the timeout in milliseconds until a connection is established.
        // The default value is zero, that means the timeout is not used. 
        //HttpConnectionParams.setConnectionTimeout(httpParameters, CONNECTION_TIMEOUT);
        // Set the default socket timeout (SO_TIMEOUT) 
        // in milliseconds which is the timeout for waiting for data.
        //HttpConnectionParams.setSoTimeout(httpParameters, SOCKET_TIMEOUT);
        final DefaultHttpClient httpclient = new DefaultHttpClient(httpParameters);

        final HttpPost httppost = new HttpPost(mServer);
        try {

            final String authorizationString = "Basic "
                    + Base64.encodeToString((mUser + ":" + mPassword).getBytes(), Base64.NO_WRAP);
            httppost.setHeader("Authorization", authorizationString);

            final MultipartEntity entity = new MultipartEntity();

            // TODO we don't need passwords for the new service
            entity.addPart(LOGIN_FIELD, new StringBody(mUser));
            entity.addPart(PASSWORD_FIELD, new StringBody(mPassword));
            entity.addPart(FILE_FIELD, new FileBody(new File(file), "text/xml"));

            httppost.setEntity(entity);
            final HttpResponse response = httpclient.execute(httppost);

            final int reply = response.getStatusLine().getStatusCode();
            if (reply == 200) {
                Log.i(TAG, "Uploaded " + file + ": Server reply " + reply);
            } else {
                Log.w(TAG, "Error while uploading" + file + ": Server reply " + reply);
            }
            // everything is ok if we receive HTTP 200
            // TODO: redirects (301, 302) are NOT handled here 
            // thus if something changes on the server side we're dead here
            return (reply == HttpStatus.SC_OK);
        } catch (final ClientProtocolException e) {
            Log.e(TAG, e.getMessage());
        } catch (final IOException e) {
            Log.e(TAG, "I/O exception on file " + file);
        }
        return false;
    }
}