/////////////////////////////////////////////////////////////////////////
//
// © Institute for Software Science, University of Vienna, 2004
//
// Copyright in this software belongs to Institute for Software Science, 
// University of Vienna, Nordbergstrasse 15/C/3, 1090 Vienna, Austria
//
// This software may not be used, sold, licensed, transferred, copied
// or reproduced in whole or in part in any manner or form or in or
// on any media by any person other than in accordance with the terms
// of the Licence Agreement supplied with the software, or otherwise
// without the prior written consent of the copyright owners.
//
// This software is distributed WITHOUT ANY WARRANTY, without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE, except where stated in the Licence Agreement supplied with
// the software.
//
//	Created By :		Gerhard Engelbrecht
//	Created Date :		2004/04/20
//	Created for Project:	GEMSS
//
////////////////////////////////////////////////////////////////////////
//
// Dependencies: None
//
/////////////////////////////////////////////////////////////////////////
//
//	Last commit info:	$Author: gerry $
//					$Date: 2004/12/13 11:31:29 $
//					$Revision: 1.10 $
//
/////////////////////////////////////////////////////////////////////////

package at.ac.univie.iss.service.app.impl;


// import at.ac.univie.iss.common.logging.Tracer;
import at.ac.univie.iss.common.session.IDGenerator;
import at.ac.univie.iss.service.app.AppHandler;
import at.ac.univie.iss.descriptors.ApplicationDescriptor;
import at.ac.univie.iss.descriptors.ApplicationDescriptorImpl;
import at.ac.univie.iss.service.global.ServiceInfo;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilePermission;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

/*
 import java.lang.Process;
 import java.lang.Runtime;
 import java.lang.InterruptedException;
 */

import javax.activation.DataHandler;
import javax.activation.FileDataSource;


final public class AppHandlerImpl implements AppHandler {

    private static Logger logger = null;
    private ApplicationDescriptor appDsc = null;

    private static final String WINDOWS = "Windows";
    private static final String START_SCRIPT = "StartScript";
    private static final String STATUS_SCRIPT = "StatusScript";
    private static String fileSeparator = null;
    private static boolean unix_os = true;
    private static String osScriptingCmd = "";
    private static final int BUFFERSIZE = 1000000;

    public AppHandlerImpl() throws RemoteException {

        try {
            // get logger, file separator & application descriptor

            logger = ServiceInfo.getLogger("app");

            logger.log(Level.INFO, "Started.\n");

            fileSeparator = System.getProperty("file.separator");
            appDsc = ServiceInfo.getAppDsc();

            // if working dir fails (not given by app-desc or does not
            // does not exist or is not a directory): throw an exception

            if ((appDsc.getWorkingDirectory() == null) || !(new File(appDsc.getWorkingDirectory())).exists()
                    || !(new File(appDsc.getWorkingDirectory())).isDirectory()) {

                throw new Exception("Working-directory failure (" + appDsc.getWorkingDirectory() + ")!");
            }

            // check operation system

            String operatingSystem = System.getProperty("os.name");

            if (operatingSystem.toUpperCase().indexOf("WINDOWS") > -1) {
                unix_os = false;
            }
            osScriptingCmd = ((unix_os == true) ? "/bin/sh --login " : "");

            logger.log(Level.INFO, "Finished.\n");
        } catch (Exception e) {
            if (logger == null) {
                e.printStackTrace();
            } else {
                logger.log(Level.SEVERE, "", e);
            }
            throw new RemoteException("AppHandlerImpl()", e);
        }
    }

    public void upload(String cid, byte[] byteArray) throws java.rmi.RemoteException {

        logger.log(Level.FINE, "" + cid + " - Started.\n");

        String uploadFile = this.prepareUpload(cid);

        // writes input data into a file
        this.writeFile(new File(uploadFile), new ByteArrayInputStream(byteArray));

        logger.log(Level.FINE, "" + cid + " - Finished.\n");
    }

    public void uploadAttachment(String cid, DataHandler dataHandler) throws RemoteException {

        try {
            logger.log(Level.FINE, "" + cid + " - Started.\n");

            String uploadFile = this.prepareUpload(cid);

            // writes input data into a file
            BufferedOutputStream bufOut = new BufferedOutputStream(new FileOutputStream(new File(uploadFile)));

            dataHandler.writeTo(bufOut);

            bufOut.flush();

            bufOut.close();

            try {
                File tmpFile = new File(dataHandler.getName());

                if (tmpFile.exists()) {

                    logger.log(Level.FINER, "" + cid + " - Deleting temp-file (" + tmpFile + ").\n");
                    tmpFile.delete();
                }
            } catch (Exception tempDelException) {
                logger.log(Level.INFO, "" + cid + " - An error occured while trying to delete the temp-file.\n");
				logger.log(Level.INFO, "" + cid + " - Error message: " + tempDelException.getMessage() + "\n");
            }

	        logger.log(Level.FINE, "" + cid + " - Finished.\n");

        } catch (Exception e) {
            logger.log(Level.SEVERE, "AppHandlerImpl.uploadAttachment()", e);
            throw new RemoteException("AppHandlerImpl.uploadAttachment()", e);
        }
    }

    public void uploadString(String cid, String data) throws java.rmi.RemoteException {

        logger.log(Level.FINE, "" + cid + " - Started.\n");
        try {

            // logger.log(Level.FINE, "" + cid + " - Input String length: " + data.length() + ".\n");
            byte[] byteData = Base64.decode(data);

            String uploadFile = this.prepareUpload(cid);

            // writes input data into a file
            this.writeFile(new File(uploadFile), new ByteArrayInputStream(byteData));

	        logger.log(Level.FINE, "" + cid + " - Finished.\n");

        } catch (Exception e) {
            logger.log(Level.SEVERE, "AppHandlerImpl.uploadString()", e);
            throw new RemoteException("AppHandlerImpl.uploadString()", e);
        }
    }

    private String prepareUpload(String cid) throws RemoteException {

        String uploadFile = null;

        try {
            //logger.log(Level.INFO, "" + cid + " - Started.\n");

            String sessionDirectory = appDsc.getWorkingDirectory() + fileSeparator + String.valueOf(cid);

            uploadFile = sessionDirectory + fileSeparator + appDsc.getInputFileName();

            File sessionDir = new File(sessionDirectory);

            // if session dir does not exist
            if (!sessionDir.exists()) {

                // create session directory
                if (!sessionDir.mkdir()) {
                    RemoteException e = new RemoteException("The directory: " + sessionDir + " could not be created.");

                    logger.log(Level.SEVERE, "AppHandlerImpl.prepareUpload()", e);
                    throw e;
                }
            }
            else {	
            	// if session dir exists: reset it
	            resetSessionDir(cid);
            }

            // logger.log(Level.CONFIG, "" + cid + " - Uploaded data has been written to file: " + uploadFile + "\n");

        } catch (Exception e) {
            logger.log(Level.SEVERE, "AppHandlerImpl.prepareUpload()", e);
            throw new RemoteException("AppHandlerImpl.prepareUpload()", e);
        }

        return uploadFile;
    }

    public byte[] download(String cid, String filename) throws RemoteException {

        logger.log(Level.FINE, "" + cid + " - Started.\n");

        try {

            byte[] buffer = null;

            File file = this.genericDownload(cid, filename);

            buffer = this.readFile(file);

            logger.log(Level.FINE, "" + cid + " - File: " + file + " has been written into a buffer.\n");

            return buffer;

        } catch (Exception e) {
            logger.log(Level.SEVERE, "AppHandlerImpl.download(cid, filename)", e);
            throw new RemoteException("AppHandlerImpl.download()", e);
        }
    }

    public DataHandler downloadAttachment(String cid, String fileName) throws RemoteException {

        logger.log(Level.FINE, "" + cid + " - Started.\n");

        try {
            File file = this.genericDownload(cid, fileName);

            DataHandler dataHandler = new DataHandler(new FileDataSource(file));

            logger.log(Level.CONFIG,
                    "" + cid + " - Size of output file: " + dataHandler.getInputStream().available() + " bytes.\n");

            return (dataHandler);

        } catch (Exception e) {
            System.out.println("AppHandlerImpl.downloadAttachment()" + e);
            throw new RemoteException("AppHandlerImpl.downloadAttachment()" + e);
        }
    }

    public String downloadString(String cid, String filename) throws RemoteException {

        logger.log(Level.FINE, "" + cid + " - Started.\n");

        try {

            String buffer = null;

            File file = this.genericDownload(cid, filename);

            buffer = Base64.encode(this.readFile(file));

            logger.log(Level.FINE, "" + cid + " - File: " + file + " has been written into a buffer.\n");

            return buffer;

        } catch (Exception e) {
            logger.log(Level.SEVERE, "AppHandlerImpl.downloadString(cid, filename)", e);
            throw new RemoteException("AppHandlerImpl.downloadString()", e);
        }
    }

    private File genericDownload(String cid, String fileName) throws RemoteException {

        try {
            logger.log(Level.INFO, "" + cid + " - With " + fileName + " started.\n");

            // if no filename is present just take the first one of
            // the application descriptor - gerry

            if ((fileName == null) || (fileName.equals(""))) {

                fileName = appDsc.getOutputFileNames()[0];
            }
            // else

            if (!this.checkOutputFiles(appDsc.getOutputFileNames(), fileName)) {

                logger.log(Level.SEVERE,
                        "Given filename (" + fileName + ") is not specified in application descriptor!\n");
                throw new Exception(
                        "0815: Given filename (" + fileName + ") is not specified in application descriptor!");
            }

            String sessionDirectory = appDsc.getWorkingDirectory() + fileSeparator + String.valueOf(cid);
            String outputFile = sessionDirectory + fileSeparator + fileName;
            String finishFile = sessionDirectory + fileSeparator + appDsc.getFinishFileName();

            File file = new File(outputFile);
            File directory = new File(sessionDirectory);
            File finish = new File(finishFile);

            byte[] buffer = null;

            logger.log(Level.FINER, "" + cid + " - Reading data from file: " + outputFile + "\n");
            logger.log(Level.FINER, "" + cid + " - Name of finish file: " + finish + "\n");

            if (!file.exists()) {
                logger.log(Level.WARNING, "" + cid + " - Output file (" + fileName + " does not exist. \n");
                throw new RemoteException("AppHandlerImpl.download() - Output file (" + fileName + " does not exist. \n");

            } else if (!finish.exists()) {
                logger.log(Level.WARNING, "" + cid + " - Finish file: " + finishFile + " does not exist!\n");
                throw new RemoteException(
                        "AppHandlerImpl.download() - Finish file: " + finishFile + " does not exist!\n");
            } else {
                logger.log(Level.FINE, "" + cid + " - Output file exist. \n");

                return file;
            }

        } catch (Exception e) {
            logger.log(Level.SEVERE, "AppHandlerImpl.download(cid, fileName)", e);
            throw new RemoteException("AppHandlerImpl.download()", e);
        }
    }

    public void start(String cid) throws RemoteException {
        try {
            logger.log(Level.INFO, "" + cid + " - Started.\n");

            String startCommand = appDsc.getJobScript();
            String sessionDirectory = appDsc.getWorkingDirectory() + fileSeparator + String.valueOf(cid);
            String outputFileName = appDsc.getOutputFileName();
            String inputFileName = appDsc.getInputFileName();
            String finishFileName = appDsc.getFinishFileName();

            String startScript = START_SCRIPT + "." + ((unix_os == true) ? "sh" : "bat");

            logger.log(Level.FINE, "" + cid + " - Job Script from application descriptor: " + startCommand + "\n");

            if (new File(startCommand).isFile()) {

                this.copyFile(cid, new File(startCommand), new File(sessionDirectory + fileSeparator + startScript));
                startCommand = startScript;

                if (unix_os) { // give the execution permission by executing chmod cmd
                    String chmod = "chmod u+rwx " + sessionDirectory + fileSeparator + startScript;

                    this.executeCmd(cid, chmod, false, true);
                    logger.log(Level.FINE, "" + cid + " - chmod done.\n");
                }

                String cmdLine = new String(
                        osScriptingCmd + sessionDirectory + fileSeparator + startCommand + " " + sessionDirectory + " "
                        + inputFileName + " " + outputFileName + " " + finishFileName);

                logger.log(Level.CONFIG, "" + cid + " - Executing: " + cmdLine + "\n");

                // execute jobscript with sessiondir, input- and output-file
                // as well as finish-file as parameter

                // NOTE: currently job-script-parameters are not supported
                // gerry

                this.executeCmd(cid, cmdLine, true, false);
            } else {
                throw new RemoteException("AppHandlerImpl.start() - Job Script seems not to be a script!");
            }
        } catch (Exception e) {
            logger.log(Level.SEVERE, "AppHandlerImpl.start()", e);
            throw new RemoteException("AppHandlerImpl.start()", e);
        }
    }

    public void kill(String cid) throws RemoteException {
        try {
            logger.log(Level.INFO, "" + cid + " - Started.\n");

            String killCommand = appDsc.getKillScript();
            String sessionDirectory = appDsc.getWorkingDirectory() + fileSeparator + String.valueOf(cid);

            if ((killCommand != null) && (new File(killCommand)).exists() && (new File(killCommand)).isFile()) {

                String cmdLine = new String(osScriptingCmd + killCommand + " " + sessionDirectory);

                logger.log(Level.CONFIG, "" + cid + " - Executing: " + cmdLine + "\n");

                this.executeCmd(cid, cmdLine, true, true);

            } else {
                // throw new RemoteException("AppHandlerImpl.kill() - Kill Script seems not to be a script!");
                if (killCommand == null) {
                    logger.log(Level.SEVERE, "" + cid + " - kill script not defined in AppDesc.\n");
                    throw new RemoteException("Kill Script is not defined in Application Descriptor!");
                } else {
                    logger.log(Level.SEVERE,
                            "" + cid + " - kill script (" + killCommand + ") does not exist or is not a file.\n");
                    throw new RemoteException("Kill Script (" + killCommand + ") does not exist or is not a file!");
                }
            }
        } catch (Exception e) {
            logger.log(Level.SEVERE, "AppHandlerImpl.kill()", e);
            throw new RemoteException("AppHandlerImpl.kill()", e);
        }
    }

    // just for testing. we need a service side job admin that saves jobs for clients and
    // changes their state. we know when input was uploaded, job was started, ... but
    // don't track it! rainer

    public String getStatusAsString(String cid) throws RemoteException {

        return (Base64.encode(this.getStatus(cid)));
    }

    public byte[] getStatus(String cid) throws RemoteException {

        byte[] statusBuffer = null;

        logger.log(Level.INFO, "" + cid + " - Started.\n");

        String sessionDirectory = appDsc.getWorkingDirectory() + fileSeparator + String.valueOf(cid);
        String statusScript = appDsc.getStatusScript();
        String statusFileName = appDsc.getStatusFile();
        String statusFile = sessionDirectory + fileSeparator + statusFileName;
        String finishFile = sessionDirectory + fileSeparator + appDsc.getFinishFileName();

        String statusScriptName = STATUS_SCRIPT + "." + ((unix_os == true) ? "sh" : "bat");

        // check if finish file exists (if so return byte-encoded "FINISHED" string)

        if ((new File(finishFile)).exists()) {
            return (new String("FINISHED")).getBytes();
        }

        // check if status script is available (defined in app-desc and if it exists)

        if ((statusScript == null) || !(new File(statusScript)).exists() || !(new File(statusScript)).isFile()) {

            // if not not defined or not existing or not a file: do some logging

            if (statusScript == null) {
                logger.log(Level.CONFIG, "" + cid + " - status script not defined in AppDesc. \n");
            } else {
                logger.log(Level.CONFIG,
                        "" + cid + " - status script (" + statusScript + ") does not exist or is not a file. \n");
            }
        } else { // status script exists and is a file

            logger.log(Level.FINE, "" + cid + " - Status script (" + statusScript + ") exists. \n");
            try {
                this.copyFile(cid, new File(statusScript), new File(sessionDirectory + fileSeparator + statusScriptName));
            } catch (Exception e) {
                logger.log(Level.SEVERE, "Error while copying!", e);
                throw new RemoteException("", e);
            }

            if (unix_os) { // give the execution permission by executing chmod cmd
                String chmod = "chmod u+rwx " + sessionDirectory + fileSeparator + statusScriptName;

                this.executeCmd(cid, chmod, false, true);
                logger.log(Level.FINE, "" + cid + " - chmod done.\n");
            }

            // execute status script on a command line (shell)
            if (statusFileName == null) {
                statusFileName = new String("");
            }
            String cmdLine = new String(
                    osScriptingCmd + sessionDirectory + fileSeparator + statusScriptName + " " + sessionDirectory + " "
                    + statusFileName);

            logger.log(Level.FINE, "" + cid + " - Start: " + cmdLine + "\n");
            this.executeCmd(cid, cmdLine, true, true);
        }

        // check if status-file is available (defined in app-desc and if it exists and is a file)

        if ((statusFile == null) || !(new File(statusFile)).exists() || !(new File(statusFile)).isFile()) {

            // if no status-file defined or not existing or not a file: so some logging

            if (statusFile == null) {
                logger.log(Level.CONFIG, "" + cid + " - status file not defined in AppDesc. \n");
            } else {
                logger.log(Level.CONFIG,
                        "" + cid + " - status file (" + statusFile + ") does not exist or is not a file. \n");
            }

            return (new String("NOT-FINISHED")).getBytes();
        } else { // if status file exists and is a file

            logger.log(Level.FINE, "" + cid + " - Status file (" + statusFile + ") exists. \n");

            // read file to status-buffer

            statusBuffer = this.readFile(new File(statusFile));

            logger.log(Level.FINE, "" + cid + " - File: " + statusFile + " has been written into a buffer.\n");
        }
        return statusBuffer;
    }

    public String getCId() throws RemoteException {

        logger.log(Level.FINE, "Trying to generate new cid for " + this + " ... \n");
        try {
            String cid = IDGenerator.getSessionId();

            logger.log(Level.INFO, "" + cid + " - Done.\n");
            return cid;
        } catch (Exception e) {
            logger.log(Level.SEVERE, "\nAppHandlerImpl.getCId()", e);
            throw new RemoteException("AppHandlerImpl.getCId()", e);
        }
    }

    public void acknowledgeResults(String cid) throws RemoteException {

        logger.log(Level.INFO, "" + cid + " - Started.\n");

        try {
            String sessionDirectory = appDsc.getWorkingDirectory() + fileSeparator + String.valueOf(cid);

            String cmdLine = appDsc.getWorkingDirectory() + fileSeparator + ".." + fileSeparator + "rmSession."
                    + ((unix_os == true) ? "sh" : "bat") + " " + sessionDirectory;

            logger.log(Level.FINE, "" + cid + " - Start: " + cmdLine + "\n");
            this.executeCmd(cid, cmdLine, true, true);

        } catch (Exception e) {
            logger.log(Level.SEVERE, "AppHandlerImpl.acknowledgeResults()", e);
            throw new RemoteException("AppHandlerImpl.acknowledgeResults()", e);
        }
    }

    public void resetSessionDir(String cid) throws RemoteException {

        try {
            String sessionDirectory = appDsc.getWorkingDirectory() + fileSeparator + String.valueOf(cid);
            String finishFileName = sessionDirectory + fileSeparator + appDsc.getFinishFileName();
            String statusFileName = sessionDirectory + fileSeparator + appDsc.getStatusFile();

            File finishFile = new File(finishFileName);
            File statusFile = new File(statusFileName);

            if (finishFile.isFile()) {

                finishFile.delete();
                logger.log(Level.FINE, "" + cid + "Removed finish file (" + finishFileName + ").\n");
            } else {
                logger.log(Level.FINE, "" + cid + "Finish file could NOT be removed (" + finishFileName + ").\n");
            }

            if (statusFile.isFile()) {

                statusFile.delete();
                logger.log(Level.FINE, "" + cid + "Removed status file (" + statusFileName + ").\n");
            } else {
                logger.log(Level.FINE, "" + cid + "Status file could NOT be removed (" + statusFileName + ").\n");
            }
        } catch (Exception e) {

            logger.log(Level.SEVERE, "AppHandlerImpl.resetSessionDir()", e);
            throw new RemoteException("", e);
        }
    }

    private boolean checkOutputFiles(String[] appDscOutputFiles, String filename) {

        for (int count = 0; count < appDscOutputFiles.length; count++) {

            if (appDscOutputFiles[count].equals(filename)) {

                return true;
            }
        }

        StringBuffer errorMsg = new StringBuffer(
                "Specified output-file (" + filename + ") is not specified in app-desc [");

        for (int count = 0; count < appDscOutputFiles.length; count++) {

            errorMsg.append(appDscOutputFiles[count]);
            if (count < (appDscOutputFiles.length - 1)) {

                errorMsg.append(", ");
            }
        }
        errorMsg.append("]\n");

        logger.log(Level.WARNING, errorMsg.toString());
        return false;
    }

    /*
     private void debugStdOutnError(String cmd, Process p) {

     String line = null;
     BufferedReader stdInput = new BufferedReader(new InputStreamReader(p.getInputStream()));
     BufferedReader stdError = new BufferedReader(new InputStreamReader(p.getErrorStream()));

     try {
     while ((line = stdInput.readLine()) != null) {
     logger.log(Level.FINE, line + "\n");
     }
     } catch (IOException e) {
     logger.log(Level.SEVERE, cmd, e);
     }

     try {
     while ((line = stdError.readLine()) != null) {
     logger.log(Level.FINE, line + "\n");
     }
     } catch (IOException e) {
     logger.log(Level.SEVERE, cmd, e);
     }
     }
     */
    private void executeCmd(String cid, String cmd, boolean output, boolean waitForTermination) throws RemoteException {

        Process process = null;

        try { // try to execute given command line

            process = Runtime.getRuntime().exec(cmd);
        } catch (IOException e) {
            logger.log(Level.SEVERE, "AppHandlerImpl.executeCmd(): " + cmd, e);
            throw new RemoteException("", e);
        } catch (NullPointerException e) {
            logger.log(Level.SEVERE, "AppHandlerImpl.executeCmd(): " + cmd, e);
            throw new RemoteException("AppHandlerImpl.executeCmd(): " + cmd, e);
        } catch (IllegalArgumentException e) {
            logger.log(Level.SEVERE, "AppHandlerImpl.executeCmd(): " + cmd, e);
            throw new RemoteException("AppHandlerImpl.executeCmd(): " + cmd, e);
        } catch (IndexOutOfBoundsException e) {
            logger.log(Level.SEVERE, "AppHandlerImpl.executeCmd(): " + cmd, e);
            throw new RemoteException("AppHandlerImpl.executeCmd(): " + cmd, e);
        }

        if (output) { // if standard output & error should be logged get corresponding
            // streams and start an appropriate logging stream
            StdOutThread stdOutThread = null;

            try {
                stdOutThread = new StdOutThread(cid, process.getInputStream(), process.getErrorStream());
            } catch (RemoteException e) {

                logger.log(Level.SEVERE, "", e);
                throw new RemoteException("", e);
            }
            stdOutThread.start();
        }

        if (waitForTermination) { // wait for the termination of command execution

            try {
                logger.log(Level.FINE, "" + cid + " - Waiting for " + cmd + " to finish ...\n");
                process.waitFor();
                logger.log(Level.FINE, "" + cid + " - Waiting for " + cmd + " to finish ...done.\n");
            } catch (InterruptedException e) {
                logger.log(Level.SEVERE, "AppHandlerImpl.executeCmd(): " + cmd, e);
                throw new RemoteException("AppHandlerImpl.executeCmd(): " + cmd, e);
            }
        }
    }

    private void writeFile(File file, byte[] buffer) throws RemoteException {

        try {
            // write given data (buffer) to given file

            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));

            bos.write(buffer, 0, buffer.length);
            bos.close();

        } catch (NullPointerException e) {
            e.printStackTrace();
            if (file == null) {
                throw new RemoteException("writeFile() file is null", e);
            } else {
                throw new RemoteException("writeFile() buffer is null", e);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            throw new RemoteException("writeFile()", e);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RemoteException("writeFile() error writing data", e);
        }
    }

    private void writeFile(File file, InputStream inputStream) throws RemoteException {

        try {
            byte[] buffer = new byte[BUFFERSIZE];
            int readBytes = -1;
            boolean moreBytes = true;

            // write given data (buffer) to given file

            BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file));

            while (moreBytes) {

                readBytes = inputStream.read(buffer, 0, BUFFERSIZE);

                if (readBytes > 0) {

                    outputStream.write(buffer, 0, readBytes);
                }

                if (readBytes < BUFFERSIZE) {

                    moreBytes = false;
                }
            }
            outputStream.close();

        } catch (NullPointerException e) {
            e.printStackTrace();
            if (file == null) {
                throw new RemoteException("writeFile() file is null", e);
            } else {
                throw new RemoteException("writeFile() buffer is null", e);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            throw new RemoteException("writeFile()", e);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RemoteException("writeFile() error writing data", e);
        }
    }

    private byte[] readFile(File file) throws RemoteException {

        byte[] buffer;

        try { // read data from given file and return the resulting byte stream

            long fileSize = file.length();

            buffer = new byte[new Long(fileSize).intValue()];

            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));

            bis.read(buffer);
            bis.close();

        } catch (NullPointerException e) {
            e.printStackTrace();
            if (file == null) {
                throw new RemoteException("readFile() file is null", e);
            } else {
                throw new RemoteException("readFile() buffer is null", e);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            throw new RemoteException("readFile()", e);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RemoteException("readFile() error writing data", e);
        }

        return buffer;
    }

    private void copyFile(String cid, File sourceFile, File destinationFile) throws Exception {

        // read bytes from sourceFile

        byte[] buffer = this.readFile(sourceFile);

        // write bytes to destinationFile

        this.writeFile(destinationFile, buffer);

        // give special file permissions to destination file

        FilePermission fp = new FilePermission(destinationFile.toString(), "read,write,execute,delete");

        logger.log(Level.FINER, "" + cid + " - File: " + sourceFile + " has been copied to " + destinationFile + ".\n");
    }
}
