org.wso2.iot.agent.services.FileDownloadService.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.iot.agent.services.FileDownloadService.java

Source

/*
 * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * WSO2 Inc. licenses this file to you 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.wso2.iot.agent.services;

import android.app.IntentService;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.util.Log;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;

import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPConnectionClosedException;
import org.apache.commons.net.ftp.FTPSClient;
import org.json.JSONException;
import org.json.JSONObject;
import org.wso2.iot.agent.AndroidAgentException;
import org.wso2.iot.agent.R;
import org.wso2.iot.agent.beans.Operation;
import org.wso2.iot.agent.utils.Constants;
import org.wso2.iot.agent.utils.HTTPAuthenticator;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Authenticator;
import java.net.ConnectException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;

import static org.wso2.iot.agent.utils.FileTransferUtils.cleanupStreams;

/**
 * Service to download files.
 */
public class FileDownloadService extends IntentService {

    private static final String TAG = FileDownloadService.class.getSimpleName();
    private Resources resources;
    private SharedPreferences.Editor editor;

    public FileDownloadService() {
        super(TAG);
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        if (Constants.DEBUG_MODE_ENABLED) {
            Log.d(TAG, "Starting File upload service");
        }
        resources = getResources();
        editor = PreferenceManager.getDefaultSharedPreferences(this).edit();
        if (intent != null) {
            Operation operation = (Operation) intent.getExtras()
                    .getSerializable(resources.getString(R.string.intent_extra_operation_object));
            try {
                downloadFile(operation);
            } catch (AndroidAgentException e) {
                Log.e(TAG, e.getLocalizedMessage());
            }
        }
        this.stopSelf();
    }

    /**
     * Download the file to the device from the FTP server.
     *
     * @param operation - Operation object.
     */
    public void downloadFile(Operation operation) throws AndroidAgentException {
        String fileName = null;
        validateOperation(operation);
        try {
            JSONObject inputData = new JSONObject(operation.getPayLoad().toString());
            final String fileURL = inputData.getString(Constants.FileTransfer.FILE_URL);
            final String userName = inputData.getString(Constants.FileTransfer.USER_NAME);
            final String password = inputData.getString(Constants.FileTransfer.PASSWORD);
            final String location = inputData.getString(Constants.FileTransfer.FILE_LOCATION);
            String savingLocation = getSavingLocation(operation, location);
            if (fileURL.startsWith(Constants.FileTransfer.HTTP)) {
                downloadFileUsingHTTPClient(operation, fileURL, userName, password, savingLocation);
            } else {
                String[] userInfo = urlSplitter(operation, fileURL, false);
                String ftpUserName;
                if (userName.isEmpty()) {
                    ftpUserName = userInfo[0];
                } else {
                    ftpUserName = userName;
                }
                String fileDirectory = userInfo[1];
                String host = userInfo[2];
                int serverPort = 0;
                if (userInfo[3] != null) {
                    serverPort = Integer.parseInt(userInfo[3]);
                }
                String protocol = userInfo[4];
                fileName = userInfo[5];
                if (Constants.DEBUG_MODE_ENABLED) {
                    printLogs(ftpUserName, host, fileName, fileDirectory, savingLocation, serverPort);
                }
                selectDownloadClient(protocol, operation, host, ftpUserName, password, savingLocation, fileName,
                        serverPort, fileDirectory);
            }
        } catch (ArrayIndexOutOfBoundsException | JSONException | URISyntaxException e) {
            handleOperationError(operation, fileTransferExceptionCause(e, fileName), e);
        } finally {
            if (Constants.DEBUG_MODE_ENABLED) {
                Log.d(TAG, operation.getStatus());
            }
            setResponse(operation);
        }
    }

    private void setResponse(Operation operation) {
        editor.putInt(resources.getString(R.string.FILE_DOWNLOAD_ID), operation.getId());
        editor.putString(resources.getString(R.string.FILE_DOWNLOAD_STATUS), operation.getStatus());
        editor.putString(resources.getString(R.string.FILE_DOWNLOAD_RESPONSE), operation.getOperationResponse());
        editor.apply();
    }

    private void selectDownloadClient(String protocol, Operation operation, String host, String ftpUserName,
            String ftpPassword, String savingLocation, String fileName, int serverPort, String fileDirectory)
            throws AndroidAgentException {
        switch (protocol) {
        case Constants.FileTransfer.SFTP:
            downloadFileUsingSFTPClient(operation, host, ftpUserName, ftpPassword, savingLocation, fileName,
                    serverPort, fileDirectory);
            break;
        case Constants.FileTransfer.FTP:
            downloadFileUsingFTPClient(operation, host, ftpUserName, ftpPassword, savingLocation, fileName,
                    serverPort, fileDirectory);
            break;
        default:
            handleOperationError(operation, "Protocol(" + protocol + ") not supported.", null);
        }

    }

    private void validateOperation(Operation operation) throws AndroidAgentException {
        if (operation == null) {
            throw new AndroidAgentException("Null operation object");
        } else if (operation.getPayLoad() == null) {
            String response = "Operation payload null.";
            operation.setOperationResponse(response);
            operation.setStatus(resources.getString(R.string.operation_value_error));
            setResponse(operation);
            throw new AndroidAgentException(response);
        }
    }

    public void handleOperationError(Operation operation, String message, Exception exception)
            throws AndroidAgentException {
        operation.setStatus(resources.getString(R.string.operation_value_error));
        operation.setOperationResponse(message);
        if (exception != null) {
            throw new AndroidAgentException(message, exception);
        } else {
            throw new AndroidAgentException(message);
        }
    }

    private void printLogs(String ftpUserName, String host, String fileName, String fileDirectory,
            String savingLocation, int serverPort) {
        Log.d(TAG, "FTP User Name: " + ftpUserName);
        Log.d(TAG, "FTP host address: " + host);
        Log.d(TAG, "FTP server port: " + serverPort);
        Log.d(TAG, "File name : " + fileName);
        Log.d(TAG, "File directory: " + fileDirectory);
        Log.d(TAG, "File upload directory: " + savingLocation);
    }

    /**
     * This method is used to download the file using HTTP client.
     *
     * @param operation      - Operation object.
     * @param url            - file url to download file.
     * @param savingLocation - location to save file in device.
     * @throws AndroidAgentException - Android agent exception.
     */
    private void downloadFileUsingHTTPClient(Operation operation, String url, String userName, String password,
            String savingLocation) throws AndroidAgentException {
        checkHTTPAuthentication(userName, password);
        InputStream inputStream = null;
        DataInputStream dataInputStream = null;
        FileOutputStream fileOutputStream = null;
        String fileName = "Unknown";
        try {
            fileName = url.substring(url.lastIndexOf('/') + 1);
            URL link = new URL(url);
            inputStream = link.openStream();
            dataInputStream = new DataInputStream(inputStream);
            byte[] buffer = new byte[1024];
            int length;
            fileOutputStream = new FileOutputStream(new File(savingLocation + File.separator + fileName));
            while ((length = dataInputStream.read(buffer)) > 0) {
                fileOutputStream.write(buffer, 0, length);
            }
            operation.setStatus(resources.getString(R.string.operation_value_completed));
            operation.setOperationResponse("File uploaded to the device successfully ( " + fileName + " ).");
        } catch (IOException e) {
            handleOperationError(operation, fileTransferExceptionCause(e, fileName), e);
        } finally {
            cleanupStreams(inputStream, null, null, fileOutputStream, null, null, dataInputStream, null);
        }
    }

    private void checkHTTPAuthentication(String userName, String password) {
        if (!userName.isEmpty()) {
            HTTPAuthenticator.setPasswordAuthentication(userName, password);
            Authenticator.setDefault(new HTTPAuthenticator());
        }
    }

    private String getSavingLocation(Operation operation, String location) throws AndroidAgentException {
        String savingLocation;
        if (location.isEmpty()) {
            savingLocation = getSavingLocation();
            if (savingLocation == null) {
                handleOperationError(operation, "Error in default saving location.", null);
            }
        } else {
            savingLocation = location;
        }
        return savingLocation;
    }

    private String getSavingLocation() {
        if (!Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).exists()
                && !Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).mkdirs()) {
            return null;
        }
        return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString();
    }

    /**
     * This method downloads the file using sftp client.
     *
     * @param operation      - operation object.
     * @param host           - host address.
     * @param ftpUserName    - ftp user name.
     * @param ftpPassword    - ftp password.
     * @param savingLocation - location in the device to save the file.
     * @param fileName       - name of the file to download.
     * @param serverPort     - ftp server port.
     * @param fileDirectory  - the directory of the file in FTP server.
     * @throws AndroidAgentException - Android agent exception.
     */
    private void downloadFileUsingSFTPClient(Operation operation, String host, String ftpUserName,
            String ftpPassword, String savingLocation, String fileName, int serverPort, String fileDirectory)
            throws AndroidAgentException {
        Channel channel;
        Session session = null;
        ChannelSftp sftpChannel = null;
        BufferedInputStream bufferedInputStream = null;
        BufferedOutputStream bufferedOutputStream = null;
        try {
            JSch jsch = new JSch();
            session = jsch.getSession(ftpUserName, host, serverPort);
            session.setConfig("StrictHostKeyChecking", "no");
            session.setPassword(ftpPassword);
            session.connect();
            channel = session.openChannel(Constants.FileTransfer.SFTP);
            channel.connect();
            sftpChannel = (ChannelSftp) channel;
            if (!fileDirectory.equals(File.separator)) {
                sftpChannel.cd(fileDirectory); //cd to dir that contains file
            }
            byte[] buffer = new byte[1024];
            bufferedInputStream = new BufferedInputStream(sftpChannel.get(fileName));
            File newFile = new File(savingLocation + File.separator + fileName);
            OutputStream outputStream = new FileOutputStream(newFile);
            bufferedOutputStream = new BufferedOutputStream(outputStream);
            int readCount;
            while ((readCount = bufferedInputStream.read(buffer)) > 0) {
                bufferedOutputStream.write(buffer, 0, readCount);
            }
            operation.setStatus(resources.getString(R.string.operation_value_completed));
            operation.setOperationResponse("File uploaded to the device successfully ( " + fileName + " ).");
        } catch (IOException | JSchException | SftpException e) {
            handleOperationError(operation, fileTransferExceptionCause(e, fileName), e);
        } finally {
            if (sftpChannel != null) {
                sftpChannel.exit();
            }
            if (session != null) {
                session.disconnect();
            }
            cleanupStreams(null, null, null, null, bufferedInputStream, bufferedOutputStream, null, null);
        }
    }

    /**
     * This method downloads the file using sftp client.
     *
     * @param operation      - operation object.
     * @param host           - host address.
     * @param ftpUserName    - ftp user name.
     * @param ftpPassword    - ftp password.
     * @param savingLocation - location in the device to save the file.
     * @param fileName       - name of the file to download.
     * @param serverPort     - ftp server port.
     * @param fileDirectory  - the directory of the file in FTP server.
     * @throws AndroidAgentException - Android agent exception.
     */
    private void downloadFileUsingFTPClient(Operation operation, String host, String ftpUserName,
            String ftpPassword, String savingLocation, String fileName, int serverPort, String fileDirectory)
            throws AndroidAgentException {

        FTPClient ftpClient = new FTPClient();
        FileOutputStream fileOutputStream = null;
        OutputStream outputStream = null;
        String response;
        try {
            ftpClient.connect(host, serverPort);
            if (ftpClient.login(ftpUserName, ftpPassword)) {
                ftpClient.enterLocalPassiveMode();
                fileOutputStream = new FileOutputStream(savingLocation + File.separator + fileName);
                outputStream = new BufferedOutputStream(fileOutputStream);
                ftpClient.changeWorkingDirectory(fileDirectory);
                if (ftpClient.retrieveFile(fileName, outputStream)) {
                    response = "File uploaded to the device successfully ( " + fileName + " ).";
                    operation.setStatus(resources.getString(R.string.operation_value_completed));
                } else {
                    response = "File uploaded to the device not completed ( " + fileName + " ).";
                    operation.setStatus(resources.getString(R.string.operation_value_error));
                }
                operation.setOperationResponse(response);
            } else {
                downloadFileUsingFTPSClient(operation, host, ftpUserName, ftpPassword, savingLocation, fileName,
                        serverPort, fileDirectory);
            }
        } catch (FTPConnectionClosedException | ConnectException e) {
            downloadFileUsingFTPSClient(operation, host, ftpUserName, ftpPassword, savingLocation, fileName,
                    serverPort, fileDirectory);
        } catch (IOException e) {
            handleOperationError(operation, fileTransferExceptionCause(e, fileName), e);
        } finally {
            try {
                if (ftpClient.isConnected()) {
                    ftpClient.logout();
                    ftpClient.disconnect();
                }
            } catch (IOException ignored) {
            }
            cleanupStreams(null, outputStream, null, fileOutputStream, null, null, null, null);
        }
    }

    /**
     * This method downloads the file using sftp client.
     *
     * @param operation      - operation object.
     * @param host           - host address.
     * @param ftpUserName    - ftp user name.
     * @param ftpPassword    - ftp password.
     * @param savingLocation - location in the device to save the file.
     * @param fileName       - name of the file to download.
     * @param serverPort     - ftp server port.
     * @param fileDirectory  - the directory of the file in FTP server.
     * @throws AndroidAgentException - Android agent exception.
     */
    private void downloadFileUsingFTPSClient(Operation operation, String host, String ftpUserName,
            String ftpPassword, String savingLocation, String fileName, int serverPort, String fileDirectory)
            throws AndroidAgentException {
        FTPSClient ftpsClient = new FTPSClient();
        FileOutputStream fileOutputStream = null;
        OutputStream outputStream = null;
        String response;
        try {
            ftpsClient.connect(host, serverPort);
            if (ftpsClient.login(ftpUserName, ftpPassword)) {
                ftpsClient.enterLocalPassiveMode();
                ftpsClient.execPROT("P");
                fileOutputStream = new FileOutputStream(savingLocation + File.separator + fileName);
                outputStream = new BufferedOutputStream(fileOutputStream);
                ftpsClient.changeWorkingDirectory(fileDirectory);
                if (ftpsClient.retrieveFile(fileName, outputStream)) {
                    response = "File uploaded to the device successfully ( " + fileName + " ).";
                    operation.setStatus(resources.getString(R.string.operation_value_completed));
                } else {
                    response = "File uploaded to the device not completed ( " + fileName + " ).";
                    operation.setStatus(resources.getString(R.string.operation_value_error));
                }
            } else {
                response = ftpUserName + " - FTP login failed.";
                operation.setStatus(resources.getString(R.string.operation_value_error));
            }
            operation.setOperationResponse(response);
        } catch (IOException e) {
            handleOperationError(operation, fileTransferExceptionCause(e, fileName), e);
        } finally {
            try {
                if (ftpsClient.isConnected()) {
                    ftpsClient.logout();
                    ftpsClient.disconnect();
                }
            } catch (IOException ignored) {
            }
            cleanupStreams(null, outputStream, null, fileOutputStream, null, null, null, null);
        }
    }

    /**
     * This method returns the cause of the exception.
     *
     * @param ex       - Exception object.
     * @param fileName - name of the file which caused exception.
     * @return - Exception cause.
     */
    public String fileTransferExceptionCause(Exception ex, String fileName) {
        if (ex.getCause() != null) {
            return fileName + " upload failed. Error :- " + ex.getCause().getMessage();
        } else {
            return fileName + " upload failed. Error :- " + ex.getLocalizedMessage();
        }
    }

    /**
     * This method splits the provided URL to get user information for downloadFile
     * and uploadFile methods.
     *
     * @param fileURL  - URL to split.
     * @param isUpload - fileURL corresponds to uploadFile or downloadFile operation.
     * @return - an array of ftpUserName, fileDirectory, host, serverPort, protocol &
     * fileName ( optional for isUpload = false ).
     * @throws URISyntaxException - Malformed URL.
     */
    public String[] urlSplitter(Operation operation, String fileURL, boolean isUpload)
            throws URISyntaxException, AndroidAgentException {
        String serverPort = null;
        URI url = new URI(fileURL);
        String protocol = url.getScheme();
        String host = null;
        String ftpUserName = null; // for anonymous FTP login.
        if (protocol != null) {
            if (protocol.equals(Constants.FileTransfer.FTP)) {
                serverPort = String.valueOf(21);
            } else if (protocol.equals(Constants.FileTransfer.SFTP)) {
                serverPort = String.valueOf(22);
            }
            if (url.getAuthority() != null) {
                String[] authority = url.getAuthority().split("@"); //provides username@hostname
                // Since hostname cannot contain any '@' signs, it should be last element.
                host = authority[authority.length - 1];
                if (authority.length > 1) {
                    ftpUserName = url.getAuthority().substring(0, url.getAuthority().lastIndexOf(host) - 1);
                } else {
                    ftpUserName = "anonymous"; // for anonymous FTP login.
                }
            }
            if (host != null && host.contains(":")) {
                serverPort = String.valueOf(host.split(":")[1]);
                host = host.split(":")[0];
            }
        } else {
            handleOperationError(operation, "Invalid URL", null);
        }
        if (isUpload) {
            return new String[] { ftpUserName, url.getPath(), host, serverPort, protocol };
        } else {
            File file = new File(url.getPath());
            return new String[] { ftpUserName, file.getParent(), host, serverPort, protocol, file.getName() };
        }
    }
}