All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.kawanfw.file.api.client.RemoteOutputStream Maven / Gradle / Ivy

Go to download

Awake FILE is a secure Open Source framework that allows to program very easily file uploads/downloads and RPC through http. File transfers include powerful features like file chunking and automatic recovery mechanism. Security has been taken into account from the design: server side allows to specify strong security rules in order to protect the files and to secure the RPC calls.

The newest version!
/*
 * This file is part of Awake FILE. 
 * Awake file: Easy file upload & download over HTTP with Java.                                    
 * Copyright (C) 2015,  KawanSoft SAS
 * (http://www.kawansoft.com). All rights reserved.                                
 *                                                                               
 * Awake FILE is free software; you can redistribute it and/or                 
 * modify it under the terms of the GNU Lesser General Public                    
 * License as published by the Free Software Foundation; either                  
 * version 2.1 of the License, or (at your option) any later version.            
 *                                                                               
 * Awake FILE 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             
 * Lesser General Public License for more details.                               
 *                                                                               
 * You should have received a copy of the GNU Lesser General Public              
 * License along with this library; if not, write to the Free Software           
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  
 * 02110-1301  USA
 *
 * Any modifications to this file must keep this entire header
 * intact.
 */
package org.kawanfw.file.api.client;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.logging.Level;

import org.apache.commons.io.FileUtils;
import org.kawanfw.commons.api.client.SessionParameters;
import org.kawanfw.commons.api.client.InvalidLoginException;
import org.kawanfw.commons.api.client.RemoteException;
import org.kawanfw.commons.util.ClientLogger;
import org.kawanfw.commons.util.FrameworkDebug;
import org.kawanfw.commons.util.Tag;
import org.kawanfw.file.api.util.client.ApiOutputStreamUploader;
import org.kawanfw.file.api.util.client.ChunkUtil;
import org.kawanfw.file.api.util.client.ExceptionThrower;
import org.kawanfw.file.api.util.client.RemoteFilePartStore;
import org.kawanfw.file.api.util.client.UniqueFileCreator;

/**
 * 
 * A remote output stream is an output stream for writing data to a remote
 * File.
 * 
* It allows to create a remote file by writing bytes on the * {@code RemoteOutputStream} with standards {@code OutputStream} write methods.
*
* Large streams are split in chunks that are uploaded in sequence. The default * chunk length is 10Mb. You can change the default value with * {@link SessionParameters#setUploadChunkLength(long)} before passing * {@code SessionParameters} to this {@link RemoteSession} constructor. *

* Note that stream chunking requires all chunks to be sent to the same web * server that will aggregate the chunks on the same file. Thus, stream chunking * does not support true stateless architecture with multiple identical web * servers. If you want to set a full stateless architecture with multiple * identical web servers, you must disable file chunking. This is done by * setting a 0 upload chunk length value using * {@link SessionParameters#setUploadChunkLength(long)}.
*
* A recovery mechanism allows - in case of failure - to start again in the same * JVM run the data upload from the last non-uploaded chunk. The recovery * mechanism is enabled only if the length is known (and so is different from * -1)
* See User Guide for more information.
*
* Note that {@code write} and {@code close} methods throw following subclasses * of {@code IOException}: *

    *
  • * {@code InvalidLoginException} the session has been closed by a * {@code RemoteSession.logoff()}.
  • *
  • {@code UnknownHostException} if host URL (http://www.acme.org) does not * exists or no Internet connection.
  • *
  • {@code ConnectException} if the Host is correct but the ServerFileManager * Servlet is not reachable (http://www.acme.org/ServerFileManager) and access * failed with a status != OK (200). (If the host is incorrect, or is impossible * to connect to - Tomcat down - the {@code ConnectException} will be the sub * exception {@code HttpHostConnectException}.)
  • *
  • {@code SocketException} if network failure during transmission.
  • *
  • {@link RemoteException} an exception has been thrown on the server side.
  • *
*
* Example: *
 * // Define URL of the path to the {@code ServerFileManager} servlet
 * char[] password = { 'm', 'y', 'P', 'a', 's', 's', 'w', 'o', 'r', 'd' };
 * 
 * // Establish a session with the remote server
 * RemoteSession remoteSession = new RemoteSession(url, username, password);
 * 
 * File file = new File("C:\\Users\\Mike\\Koala.jpg");
 * String pathname = "/Koala.jpg";
 * 
 * InputStream in = null;
 * OutputStream out = null;
 * 
 * try {
 * 
 *     // Get an InputStream from our local file
 *     in = new FileInputStream(file);
 * 
 *     // Create an OutputStream that maps a remote file on the host
 *     out = new RemoteOutputStream(remoteSession, pathname, file.length());
 * 
 *     // Create the remote file reading the InpuStream and writing
 *     // on the OutputStream
 *     byte[] buffer = new byte[1024 * 4];
 *     int n = 0;
 *     while ((n = in.read(buffer)) != -1) {
 * 	out.write(buffer, 0, n);
 *     }
 * 
 *     // It is better to also close out before finally
 *     // because RemoteOutputStream.close() sends data to the server and
 *     // is thus prone to IOException
 *     out.close();
 * 
 * } catch (IOException e) {
 *     // Treat IOException, including those thrown by out.close()
 *     e.printStackTrace();
 *     // Etc.
 * 
 * } finally {
 *     if (in != null)
 * 	in.close();
 *     if (out != null)
 * 	out.close();
 * }
 *  
* * @see org.kawanfw.file.api.client.RemoteFile * @see org.kawanfw.file.api.client.RemoteInputStream * * @author Nicolas de Pomereu * @since 2.0 */ public class RemoteOutputStream extends OutputStream { /** For debug info */ private static boolean DEBUG = FrameworkDebug .isSet(RemoteOutputStream.class); /** the file session in use */ private RemoteSession remoteSession; /** The remote file's pathname */ private String pathname = null; /** The stream containing the datas to upload */ private OutputStream out = null; /** The unique file used as container and reference for download */ private File fileUnique = null; /** The counter to use use for file chunks */ private int cpt = 0; /** Total length for one upload */ private long totalLength = 0; /** Temporary length for one chunk */ private long tempLength = 0; /** The remote file length */ private long remoteFileLength = -1; /** Compute the total length of upload files, must match totalLength */ private long totalFileLength = 0; /** Happens id the lase send id exactly the size of a chunk */ private boolean noSendInclose = false; /** * Creates an output stream to write to the remote file with the * specified pathname. *

* The real path of the remote file depends on the Awake FILE * configuration on the server. See User Documentation. * * @param remoteSession * the current remote session * @param pathname * the pathname on host with "/" as file separator. Must be absolute. * @param length * the final length of the remote file after creation, -1 if unknown * * @throws IllegalArgumentException * if remoteFile is null or length is < -1 * @throws InvalidLoginException * the session has been closed by a {@code logoff()} * @throws IOException * if an I/O error occurs. * * @since 2.0 */ public RemoteOutputStream(RemoteSession remoteSession, String pathname, long length) throws IOException { initConstructors(remoteSession, pathname, length); } /** * Creates an output stream to write to the remote file with the specified * pathname. *

* The real path of the remote file depends on the Awake FILE configuration * on the server. See User Documentation. * * @param fileSession * the current file session * @param pathname * the pathname on host with "/" as file separator. Must be * absolute. * @param length * the final length of the remote file after creation, -1 if * unknown * * @throws IllegalArgumentException * if remoteFile is null or length is < -1 * @throws InvalidLoginException * the session has been closed by a {@code logoff()} * @throws IOException * if an I/O error occurs. * * @deprecated As of version 3.0, replaced by: * {@link RemoteOutputStream#RemoteOutputStream(RemoteSession, String, long)} * @since 2.0 */ public RemoteOutputStream(FileSession fileSession, String pathname, long length) throws IOException { if (fileSession == null) { throw new IllegalArgumentException("fileSession is null!"); } initConstructors(fileSession.getRemoteSession(), pathname, length); } /** * Creates an output stream to write to the remote file represented by * the specified RemoteFile object. * * @param remoteFile * the remote file * @param length * the final remote file length after creation, -1 if unknown * * @throws IllegalArgumentException * if pathname is null or length is < -1 * @throws InvalidLoginException * the session has been closed by a {@code logoff()} * @throws IOException * if an I/O error occurs. * * @since 3.0 */ public RemoteOutputStream(RemoteFile remoteFile, long length) throws IOException { if (remoteFile == null) { throw new IllegalArgumentException("remoteFile is null!"); } initConstructors(remoteFile.getRemoteSession(), remoteFile.getPath(), length); } /** * Init done in constructors. * * @param remoteSession * @param pathname * @param length * @throws IllegalArgumentException * @throws InvalidLoginException * @throws IOException * @throws FileNotFoundException */ private void initConstructors(RemoteSession remoteSession, String pathname, long length) throws IllegalArgumentException, InvalidLoginException, IOException, FileNotFoundException { if (remoteSession == null) { throw new IllegalArgumentException("remoteSession is null!"); } if (remoteSession.getUsername() == null || remoteSession.getAuthenticationToken() == null) { throw new InvalidLoginException(RemoteSession.REMOTE_SESSION_IS_CLOSED); } if (pathname == null) { throw new IllegalArgumentException("pathname is null!"); } if (! pathname.startsWith("/")) { throw new IllegalArgumentException("pathname must be asbsolute and start with \"/\": " + pathname); } if (length < -1) { throw new IllegalArgumentException("length must be > -1."); } this.remoteSession = remoteSession; this.pathname = pathname; this.remoteFileLength = length; // Create the unique filename corresponding to username & pathname name // Must be done in constructor because close() uses fileUnique fileUnique = UniqueFileCreator.createUnique(remoteSession.getUsername(), this.pathname); out = new BufferedOutputStream(new FileOutputStream(fileUnique)); } /** * Returns the remote file's pathname * * @return the remote file's pathname */ public String getPathname() { return pathname; } /** * Writes len bytes from the specified byte array starting at * offset off to this output stream. The general contract for * write(b, off, len) is that some of the bytes in the array * b are written to the output stream in order; element * b[off] is the first byte written and * b[off+len-1] is the last byte written by this operation. *

* The write method of OutputStream calls the * write method of one argument on each of the bytes to be written out. *

* If b is null, a * NullPointerException is thrown. *

* If off is negative, or len is negative, or * off+len is greater than the length of the array * b, then an IndexOutOfBoundsException is thrown. * * @param b * the data. * @param off * the start offset in the data. * @param len * the number of bytes to write. * @exception IOException * if an I/O error occurs. In particular, an * IOException is thrown if the output stream is * closed. */ @Override public void write(byte[] b, int off, int len) throws IOException { out.write(b, off, len); totalLength += len; tempLength += len; // if length = exaclty only chunk==> mark it for the close if (tempLength == ChunkUtil.getUploadChunkLength(remoteSession)) { noSendInclose = true; } else { noSendInclose = false; } // if length >= chunkLength ==> send the chunk if (tempLength >= ChunkUtil.getUploadChunkLength(remoteSession)) { out.close(); cpt++; uploadPerChunks(remoteSession, fileUnique, pathname, cpt, false); tempLength = 0; fileUnique = UniqueFileCreator.createUnique( remoteSession.getUsername(), pathname); out = new BufferedOutputStream(new FileOutputStream(fileUnique)); } } /** * Writes the specified byte to this output stream. The general contract for * write is that one byte is written to the output stream. The * byte to be written is the eight low-order bits of the argument * b. The 24 high-order bits of b are ignored. * * @param b * the byte. * @exception IOException * if an I/O error occurs. In particular, an * IOException may be thrown if the output * stream has been closed. */ @Override public void write(int b) throws IOException { // out.write(b); throw new IOException(Tag.PRODUCT + " write(int b) method is not supported."); } /** * Writes b.length bytes from the specified byte array to this * output stream. The general contract for write(b) is that it * should have exactly the same effect as the call * write(b, 0, b.length). * * @param b * the data. * @exception IOException * if an I/O error occurs. * @see java.io.OutputStream#write(byte[], int, int) */ @Override public void write(byte[] b) throws IOException { write(b, 0, b.length); } /** * Flushes this stream by writing any buffered output to the underlying * stream. * * @throws IOException * If an I/O error occurs */ @Override public void flush() throws IOException { out.flush(); } /** * Closes this output stream and releases any system resources associated * with this stream. The general contract of close is that it * closes the output stream. A closed stream cannot perform output * operations and cannot be reopened. *

* WARNING: before the output stream close, the remaining bytes are sent to * server. *

* * @exception IOException * if an I/O error occurs. */ @Override public void close() throws IOException { // if (out == null) : it has been closed already so escape now // We don't want to resend datas... if (out == null) { return; } out.close(); // We immediately set out to null to avoid recall of this method out = null; try { // If close is due to Exception throw, total written lenth will be // < remote file length, so do nothing if (remoteFileLength != -1 && totalLength < remoteFileLength) { return; } if (noSendInclose) { // Last send is exactly chunk length. We have nothing to do: return; } // We must upload the end of our output stream before getting out if (totalLength <= ChunkUtil.getUploadChunkLength(remoteSession)) { ApiOutputStreamUploader apiOutputStreamUploader = new ApiOutputStreamUploader( remoteSession.getUsername(), remoteSession.getAuthenticationToken(), remoteSession.getHttpTransfer()); apiOutputStreamUploader.uploadOneChunk(fileUnique, pathname, ChunkUtil.getUploadChunkLength(remoteSession)); } else { sendLastChunk(); } } finally { FileUtils.deleteQuietly(fileUnique); } } /** * As is says. * * @throws FileNotFoundException * @throws IOException * @throws IllegalArgumentException * @throws InvalidLoginException * @throws UnknownHostException * @throws ConnectException * @throws SocketException * @throws RemoteException */ private void sendLastChunk() throws FileNotFoundException, IOException, IllegalArgumentException, InvalidLoginException, UnknownHostException, ConnectException, SocketException, RemoteException { cpt++; uploadPerChunks(remoteSession, fileUnique, pathname, cpt, true); // 1) Always remove if we don't know the remote file length // 2) If remote file length is known, remove only at end of // successfull transfer if (remoteFileLength == -1 || (remoteFileLength != -1 && totalLength >= remoteFileLength)) { RemoteFilePartStore remoteFilePartStore = new RemoteFilePartStore( remoteSession.getUsername(), fileUnique, pathname); remoteFilePartStore.remove(); } } /** * Upload the file per chunk * * @param file * the file to upload * @param pathname * the file name on the host * @param cpt * the counter for file chunks * @param isLastChunk * says if this is the lastchunk * * @throws FileNotFoundException * @throws IOException * @throws IllegalArgumentException * @throws InvalidLoginException * @throws UnknownHostException * @throws ConnectException * @throws SocketException * @throws RemoteException */ private void uploadPerChunks(RemoteSession remoteSession, File file, String remoteFile, int cpt, boolean isLastChunk) throws FileNotFoundException, IOException, IllegalArgumentException, InvalidLoginException, UnknownHostException, ConnectException, SocketException, RemoteException { // Upload files in chunk, creating temporary file with default size 10Mb RemoteFilePartStore remoteFilePartStore = new RemoteFilePartStore( remoteSession.getUsername(), file, remoteFile); String remoteFilePart = remoteFile + "." + cpt + ".kawanfw.chunk"; if (isLastChunk) { remoteFilePart += ".LASTCHUNK"; } ExceptionThrower.throwSocketExceptionIfFlagFileExists(); // Do the upload only if it has not been done if (!remoteFilePartStore.alreadyUploaded(remoteFilePart)) { debug(new Date() + " Uploading " + remoteFilePart + "..."); ApiOutputStreamUploader apiOutputStreamUploader = new ApiOutputStreamUploader( remoteSession.getUsername(), remoteSession.getAuthenticationToken(), remoteSession.getHttpTransfer()); ExceptionThrower.throwSocketExceptionIfFlagFileExists(); apiOutputStreamUploader.uploadOneChunk(file, remoteFilePart, ChunkUtil.getUploadChunkLength(remoteSession)); remoteFilePartStore.storeFilePart(remoteFilePart); totalFileLength += file.length(); debug(""); debug("totalLength / totalFileLength : " + totalLength + " / " + totalFileLength + " UPLOADED!"); FileUtils.deleteQuietly(file); } else { debug(new Date() + " No Uploading of " + remoteFilePart + ". Already done!"); } } /** * debug tool */ private void debug(String s) { if (DEBUG) { ClientLogger.getLogger().log(Level.WARNING, s); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy