summaryrefslogblamecommitdiffstats
path: root/src/main/java/org/openslx/filetransfer/Transfer.java
blob: 34868e33089ef1b1f2ab24d78fa63cc2caa66e9f (plain) (tree)






































































































































































































































































































































                                                                                                             
package org.openslx.filetransfer;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.nio.charset.StandardCharsets;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

import org.apache.log4j.Logger;

public abstract class Transfer
{
	protected final SSLSocketFactory sslSocketFactory;
	protected final SSLSocket satelliteSocket;
	protected final DataOutputStream dataToServer;
	protected final DataInputStream dataFromServer;
	protected String TOKEN = null;
	protected int[] RANGE = null;
	protected String ERROR = null;

	protected final Logger log;

	protected Transfer( String ip, int port, SSLContext context, Logger log ) throws IOException
	{
		this.log = log;
		// create socket.
		sslSocketFactory = context.getSocketFactory();

		satelliteSocket = (SSLSocket)sslSocketFactory.createSocket( ip, port );
		satelliteSocket.setSoTimeout( 2000 ); // set socket timeout.

		dataToServer = new DataOutputStream( satelliteSocket.getOutputStream() );
		dataFromServer = new DataInputStream( satelliteSocket.getInputStream() );
	}

	protected Transfer( SSLSocket socket, Logger log ) throws IOException
	{
		this.log = log;
		satelliteSocket = socket;
		dataToServer = new DataOutputStream( satelliteSocket.getOutputStream() );
		dataFromServer = new DataInputStream( satelliteSocket.getInputStream() );
		sslSocketFactory = null;
	}

	protected boolean sendRange( int startOffset, int endOffset )
	{
		if ( RANGE != null ) {
			log.warn( "Range already set!" );
			return false;
		}
		try {
			sendKeyValuePair( "RANGE", startOffset + ":" + endOffset );
		} catch ( SocketTimeoutException ste ) {
			ste.printStackTrace();
			log.info( "Socket timeout occured ... close connection." );
			this.close();
		} catch ( IOException e ) {
			e.printStackTrace();
			readMetaData();
			if ( ERROR != null ) {
				if ( ERROR == "timeout" ) {
					log.info( "Socket timeout occured ... close connection." );
					this.close();
				}
			}
			log.info( "Sending RANGE in Uploader failed..." );
			return false;
		}
		return true;
	}

	/***********************************************************************/
	/**
	 * Method for sending token for identification from satellite to master.
	 * 
	 * @param token The token to send
	 */
	public boolean sendToken( String token )
	{
		if ( TOKEN != null ) {
			log.warn( "Trying to send token while a token is already set! Ignoring..." );
			return false;
		}
		TOKEN = token;
		try {
			sendKeyValuePair( "TOKEN", TOKEN );
		} catch ( SocketTimeoutException ste ) {
			ste.printStackTrace();
			log.info( "Socket timeout occured ... close connection." );
			this.close();
		} catch ( IOException e ) {
			e.printStackTrace();
			readMetaData();
			if ( ERROR != null ) {
				if ( ERROR == "timeout" ) {
					log.info( "Socket timeout occured ... close connection." );
					this.close();
				}
			}
			log.info( "Sending TOKEN in Downloader failed..." );
			return false;
		}
		return true;
	}

	/***********************************************************************/
	/**
	 * Method for reading incoming token for identification.
	 * 
	 */
	public String getToken()
	{
		return TOKEN;
	}

	private boolean parseRange( String range )
	{
		if ( range == null )
			return true;
		if ( RANGE != null ) {
			log.warn( "Warning: RANGE already set when trying to parse from " + range );
			return false;
		}
		String parts[] = range.split( ":", 2 );
		int ret[] = new int[ 2 ];
		try {
			ret[0] = Integer.parseInt( parts[0] );
			ret[1] = Integer.parseInt( parts[1] );
		} catch ( Throwable t ) {
			log.warn( "Not parsable range: '" + range + "'" );
			return false;
		}
		if ( ret[1] <= ret[0] ) {
			log.warn( "Invalid range. Start >= end" );
			return false;
		}
		RANGE = ret;
		return true;
	}

	/***********************************************************************/
	/**
	 * Getter for beginning of RANGE.
	 * 
	 * @return
	 */
	public int getStartOfRange()
	{
		if ( RANGE != null ) {
			return RANGE[0];
		}
		return -1;
	}

	/***********************************************************************/
	/**
	 * Getter for end of RANGE.
	 * 
	 * @return
	 */
	public int getEndOfRange()
	{
		if ( RANGE != null ) {
			return RANGE[1];
		}
		return -1;
	}

	/***********************************************************************/
	/**
	 * Method for returning difference of current Range.
	 * 
	 * @return
	 */
	public int getDiffOfRange()
	{
		int diff = Math.abs( getEndOfRange() - getStartOfRange() );
		return diff;
	}

	/***********************************************************************/
	/**
	 * Method for reading MetaData, like TOKEN and FileRange.
	 * Split incoming bytes after first '=' and store value to specific
	 * variable.
	 * 
	 * @return true on success, false if reading failed
	 */
	public boolean readMetaData()
	{
		try {
			while ( true ) {
				byte[] incoming = new byte[ 255 ];

				// First get length.
				int retLengthByte;
				retLengthByte = dataFromServer.read( incoming, 0, 1 );
				if ( retLengthByte != 1 ) {
					this.close();
					return false;
				}

				int length = incoming[0] & 0xFF;
				log.debug( "length (downloader): " + length );

				if ( length == 0 )
					break;

				/*
				 * Read the next available bytes and split by '=' for
				 * getting TOKEN or RANGE.
				 */
				int hasRead = 0;
				while ( hasRead < length ) {
					int ret = dataFromServer.read( incoming, hasRead, length - hasRead );
					if ( ret == -1 ) {
						log.warn( "Error occured while reading Metadata." );
						this.close();
						return false;
					}
					hasRead += ret;
				}

				String data = new String( incoming, 0, length, StandardCharsets.UTF_8 );

				String[] splitted = data.split( "=", 2 );
				if ( splitted.length != 2 ) {
					log.warn( "Invalid key value pair received (" + data + ")" );
					continue;
				}
				if ( splitted[0].equals( "TOKEN" ) ) {
					if ( TOKEN != null ) {
						log.warn( "Received a token when a token is already set!" );
						this.close();
						return false;
					}
					TOKEN = splitted[1];
					log.debug( "TOKEN: " + TOKEN );
				}
				else if ( splitted[0].equals( "RANGE" ) ) {
					if ( !parseRange( splitted[1] ) ) {
						this.close();
						return false;
					}
					log.debug( "RANGE: '" + splitted[1] + "'" );
				}
				else if ( splitted[0].equals( "ERROR" ) ) {
					ERROR = splitted[1];
					log.debug( "ERROR: " + ERROR );
				}
			}
		} catch ( SocketTimeoutException ste ) {
			ste.printStackTrace();
			sendErrorCode( "timeout" );
			log.info( "Socket Timeout occured in Downloader." );
			this.close();
			return false;
		} catch ( Exception e ) {
			e.printStackTrace();
			this.close();
			return false;
		}
		return true;
	}

	private void sendKeyValuePair( String key, String value ) throws IOException
	{
		byte[] data = ( key + "=" + value ).getBytes( StandardCharsets.UTF_8 );
		dataToServer.writeByte( data.length );
		dataToServer.write( data );
	}

	/***********************************************************************/
	/**
	 * Method for sending error Code to server. For example in case of wrong
	 * token, send code for wrong token.
	 * 
	 */
	public Boolean sendErrorCode( String errString )
	{
		try {
			sendKeyValuePair( "ERROR", errString );
		} catch ( IOException e ) {
			e.printStackTrace();
			this.close();
			return false;
		}
		return true;
	}

	/***********************************************************************/
	/**
	 * Method for closing connection, if download has finished.
	 * 
	 */
	public void close()
	{
		try {
			if ( satelliteSocket != null ) {
				this.satelliteSocket.close();
			}
			if ( dataFromServer != null )
				dataFromServer.close();
			if ( dataToServer != null )
				dataToServer.close();
		} catch ( IOException e ) {
			e.printStackTrace();
		}
	}

	/**
	 * Returns whether this transfer/connection is considered valid or usable,
	 * which means the socket is still properly connected to the remote peer.
	 * 
	 * @return true or false
	 */
	public boolean isValid()
	{
		return satelliteSocket.isConnected() && !satelliteSocket.isClosed()
				&& !satelliteSocket.isInputShutdown() && !satelliteSocket.isOutputShutdown();
	}

}