com.apporiented.hermesftp.cmd.AbstractFtpCmdStor.java Source code

Java tutorial

Introduction

Here is the source code for com.apporiented.hermesftp.cmd.AbstractFtpCmdStor.java

Source

/*
 * ------------------------------------------------------------------------------
 * Hermes FTP Server
 * Copyright (c) 2005-2014 Lars Behnke
 * ------------------------------------------------------------------------------
 * 
 * This file is part of Hermes FTP Server.
 * 
 * Hermes FTP Server is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * Hermes FTP Server 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Hermes FTP Server; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * ------------------------------------------------------------------------------
 */

package com.apporiented.hermesftp.cmd;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.InflaterInputStream;

import com.apporiented.hermesftp.exception.FtpCmdException;
import com.apporiented.hermesftp.exception.FtpException;
import com.apporiented.hermesftp.exception.FtpPermissionException;
import com.apporiented.hermesftp.exception.FtpUniqueConstraintException;
import com.apporiented.hermesftp.streams.BlockModeInputStream;
import com.apporiented.hermesftp.streams.RecordInputStream;
import com.apporiented.hermesftp.streams.RecordReadSupport;
import com.apporiented.hermesftp.streams.TextInputStream;
import com.apporiented.hermesftp.utils.TransferRateLimiter;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Ancestor command class that is extended by commands that store data on the remote file system.
 * 
 * @author Lars Behnke
 */
public abstract class AbstractFtpCmdStor extends AbstractFtpCmd {

    private static Log log = LogFactory.getLog(AbstractFtpCmdStor.class);

    private TransferRateLimiter transferRateLimiter = new TransferRateLimiter();

    private long fileSize;

    private long completed;

    private boolean abortRequested;

    /**
     * Executes the command. This operation acts as a template method calling primitive operations
     * implemented by the sub classes.
     * 
     * @param unique True, if file that is supposed to be stored may not exist on the remote file
     *            system.
     * @throws FtpCmdException Wrapper class for any exception thrown in the command.
     */
    public void execute(boolean unique) throws FtpCmdException {

        /* Get relevant information from context */
        File file = new File(getPathArg());
        int mode = getCtx().getTransmissionMode();
        int struct = getCtx().getStorageStructure();
        int type = getCtx().getDataType();
        String charset = type == DT_ASCII || type == DT_EBCDIC ? getCtx().getCharset() : null;
        long fileOffset = getAndResetFileOffset();
        getTransferRateLimiter().init(getCtx().getMaxUploadRate());

        try {
            /* Check availability and access rights */
            doPerformAccessChecks(unique, file, fileOffset);

            /* Initialize restart markers (block transfer mode) */
            Map<Long, Long> restartMarkers = new HashMap<Long, Long>();
            getCtx().setAttribute(ATTR_RESTART_MARKERS, restartMarkers);

            /* Wrap inbound data stream and call handler method */
            msgOut(MSG150);

            Socket dataSocket = getCtx().getDataSocketProvider().provideSocket();
            InputStream dataIn = dataSocket.getInputStream();
            if (struct == STRUCT_RECORD) {
                RecordReadSupport recordIn = createRecInputStream(dataIn, mode, charset, restartMarkers);
                doStoreRecordData(recordIn, file, fileOffset);
            } else if (struct == STRUCT_FILE) {
                InputStream fileIn = createInputStream(dataIn, mode, restartMarkers, charset);
                doStoreFileData(fileIn, file, fileOffset);
            } else {
                log.error("Unknown data type");
                msgOut(MSG550, "Unsupported data type");
            }
            // TODO delegate event to FtpEventListener
            // getCtx().getEventListener().

        } catch (FtpUniqueConstraintException e) {
            msgOut(MSG553);
        } catch (FtpPermissionException e) {
            msgOut(MSG550_PERM);
        } catch (FtpException e) {
            msgOut(MSG550_MSG, e.getMessage());
            log.warn(e.getMessage());
        } catch (UnsupportedEncodingException e) {
            msgOut(MSG550_MSG, "Unsupported Encoding: " + charset);
            log.error(e.toString());
        } catch (IOException e) {
            msgOut(MSG550);
            log.error(e.toString());
        } catch (RuntimeException e) {
            msgOut(MSG550);
            log.error(e.toString());
        } finally {
            getCtx().closeSockets();
        }
    }

    /**
     * Creates an input stream that supports unstructured file data.
     * 
     * @param is The nested input stream.
     * @param mode The transmission mode.
     * @param charset The encoding or null if binary.
     * @param restartMarkers Optional map that stores restart markers.
     * @return The stream object.
     * @throws UnsupportedEncodingException Thrown if encoding is unknown.
     */
    private InputStream createInputStream(InputStream is, int mode, Map<Long, Long> restartMarkers, String charset)
            throws UnsupportedEncodingException {
        InputStream result = null;
        if (mode == MODE_BLOCK) {
            byte[] eorBytes = getEorBytes(null);
            result = new BlockModeInputStream(is, eorBytes, restartMarkers);
        } else if (mode == MODE_STREAM) {
            result = is;
        } else if (mode == MODE_ZIP) {
            result = new InflaterInputStream(is);
        } else {
            log.error("Unsupported file mode: " + mode);
        }
        if (charset != null) {
            result = new TextInputStream(is, charset);
        }
        return result;

    }

    /**
     * Creates an input stream that supports reading records.
     * 
     * @param is The nested input stream.
     * @param mode The transmission mode.
     * @param charset The encoding or null if binary.
     * @param restartMarkers Optional map that stores restart markers.
     * @return The stream object.
     * @throws UnsupportedEncodingException Thrown if encoding unknown.
     */
    private RecordReadSupport createRecInputStream(InputStream is, int mode, String charset,
            Map<Long, Long> restartMarkers) throws UnsupportedEncodingException {
        RecordReadSupport result = null;
        byte[] eorBytes = charset == null ? new byte[0] : getEorBytes(charset);
        if (mode == MODE_BLOCK) {
            result = new BlockModeInputStream(is, eorBytes, restartMarkers);
        } else if (mode == MODE_STREAM) {
            result = new RecordInputStream(is, getEorBytes(charset));
        } else if (mode == MODE_ZIP) {
            result = new RecordInputStream(new InflaterInputStream(is), getEorBytes(charset));
        } else {
            log.error("Unsupported record mode: " + mode);
        }
        if (charset != null) {
            result = new TextInputStream((InputStream) result, charset);
        }
        return result;

    }

    /**
     * Returns the EOR-byte representation in non-record text files, which corresponds to the line
     * break sequence of the passed character set.
     * 
     * @param charset The character set.
     * @return The EOR marker.
     */
    private static byte[] getEorBytes(String charset) {
        String lineSep = System.getProperty("line.separator");
        try {
            if (charset == null) {
                return lineSep.getBytes();
            } else {
                return lineSep.getBytes(charset);
            }
        } catch (UnsupportedEncodingException e) {
            log.error(e);
            return lineSep.getBytes();
        }
    }

    /**
     * {@inheritDoc}
     */
    public boolean handleAsyncCmd(String req) {
        boolean result;
        if (req == null || isResponded()) {
            result = false;
        } else if (req.toUpperCase().startsWith("STAT")) {
            String stat = "STAT: " + getCompleted() + " from " + getFileSize() + " completed";
            log.info(stat);
            // TODO Return statistics response.
            result = true;
        } else if (req.toUpperCase().startsWith("ABOR")) {
            abortRequested = true;
            result = true;
        } else {
            result = false;
        }
        return result;
    }

    /**
     * Checks availability and access rights for the current folder and passed file. The methods
     * acts as a primitive operation that is called by the template method
     * <code>execute(boolean)</code>;
     * 
     * @param unique True, if destination file may not exist already.
     * @param file The destination file.
     * @param offset The file offset (-1 on append).
     * @throws FtpException Thrown if permission rules have been violated or resource limits have
     *             been exceeded.
     */
    protected abstract void doPerformAccessChecks(boolean unique, File file, long offset) throws FtpException;

    /**
     * Stores record based data as file. The method acts as a primitive operation that is called by
     * the template method <code>execute(boolean)</code>;
     * 
     * @param rrs The wrapped input stream.
     * @param file Destination file.
     * @param offset The file offset (-1 on append).
     * @throws IOException Thrown if IO fails or if at least one resource limit was reached.
     */
    protected abstract void doStoreRecordData(RecordReadSupport rrs, File file, long offset) throws IOException;

    /**
     * Stores unstructured data as file. The method acts as a primitive operation that is called by
     * the template method <code>execute(boolean)</code>;
     * 
     * @param is The input stream.
     * @param file Destination file.
     * @param offset The file offset (-1 on append).
     * @throws IOException Thrown if IO fails or if at least one resource limit was reached
     */
    protected abstract void doStoreFileData(InputStream is, File file, long offset) throws IOException;

    /**
     * Getter method for the java bean <code>completed</code>.
     * 
     * @return Returns the value of the java bean <code>completed</code>.
     */
    public synchronized long getCompleted() {
        return completed;
    }

    /**
     * Setter method for the java bean <code>completed</code>.
     * 
     * @param completed The value of completed to set.
     */
    public synchronized void incCompleted(long completed) {
        this.completed += completed;
    }

    /**
     * Getter method for the java bean <code>fileSize</code>.
     * 
     * @return Returns the value of the java bean <code>fileSize</code>.
     */
    public long getFileSize() {
        return fileSize;
    }

    /**
     * Setter method for the java bean <code>fileSize</code>.
     * 
     * @param fileSize The value of fileSize to set.
     */
    public void setFileSize(long fileSize) {
        this.fileSize = fileSize;
    }

    /**
     * @return True if abort has been requested.
     */
    protected boolean isAbortRequested() {
        return abortRequested;
    }

    /**
     * Getter Methode fuer die Eigenschaft <code>transferRateLimiter</code>.
     * 
     * @return Wert der Eigenschaft <code>transferRateLimiter</code>.
     */
    public TransferRateLimiter getTransferRateLimiter() {
        return transferRateLimiter;
    }

    /**
     * @param transferRateLimiter the transferRateLimiter to set
     */
    public void setTransferRateLimiter(TransferRateLimiter transferRateLimiter) {
        this.transferRateLimiter = transferRateLimiter;
    }

}