package org.diretto.web.richwebclient.view.widgets.upload.server;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.commons.configuration.XMLConfiguration;
import org.diretto.web.richwebclient.RichWebClientApplication;
import org.diretto.web.richwebclient.view.widgets.upload.client.VMultipleUpload;
import org.diretto.web.richwebclient.view.widgets.upload.client.base.FileInfo;
import com.vaadin.terminal.PaintException;
import com.vaadin.terminal.PaintTarget;
import com.vaadin.terminal.StreamVariable;
import com.vaadin.ui.AbstractComponent;
import com.vaadin.ui.ClientWidget;
import com.vaadin.ui.ProgressIndicator;
/**
* The server side component of the {@code MultipleUpload}.
*
* @author Tobias Schlecht
*/
@ClientWidget(VMultipleUpload.class)
public final class MultipleUpload extends AbstractComponent
{
private static final long serialVersionUID = -6415400052804921797L;
private final String tempFolderPath;
private final int maxParallelStreams;
private final List<MultipleUploadHandler> multipleUploadHandlers = new CopyOnWriteArrayList<MultipleUploadHandler>();
private String buttonCaption;
private String buttonStyleName;
private boolean stopPolling = false;
private List<FileInfo> fileInfos = null;
private final Map<String, MultipleUploadStreamVariable> streamVariables = new ConcurrentHashMap<String, MultipleUploadStreamVariable>();
private final List<String> uploadFiles = new CopyOnWriteArrayList<String>();
private final List<String> cancelFiles = new CopyOnWriteArrayList<String>();
private final Map<String, File> files = new ConcurrentHashMap<String, File>();
private Timer timer = null;
/**
* Constructs a {@link MultipleUpload}.
*
* @param application The corresponding {@code RichWebClientApplication}
* @param caption The caption of the upload button
*/
public MultipleUpload(RichWebClientApplication application, String caption)
{
this(application, caption, "");
}
/**
* Constructs a {@link MultipleUpload}.
*
* @param application The corresponding {@code RichWebClientApplication}
* @param caption The caption of the upload button
* @param styleName The style of the upload button
*/
public MultipleUpload(RichWebClientApplication application, String caption, String styleName)
{
buttonCaption = caption;
buttonStyleName = styleName;
XMLConfiguration xmlConfiguration = application.getXMLConfiguration();
tempFolderPath = xmlConfiguration.getString("upload/temp-folder-path");
maxParallelStreams = xmlConfiguration.getInt("upload/max-parallel-streams");
}
@Override
public void paintContent(PaintTarget target) throws PaintException
{
super.paintContent(target);
target.addAttribute("buttonStyleName", buttonStyleName);
target.addVariable(this, "buttonCaption", buttonCaption);
if(stopPolling && uploadFiles.size() == 0)
{
target.addVariable(this, "stopPolling", true);
}
else
{
target.addVariable(this, "stopPolling", false);
}
String[] uploads = uploadFiles.toArray(new String[0]);
if(uploads.length > maxParallelStreams)
{
String[] limitedUploads = new String[maxParallelStreams];
for(int i = 0; i < maxParallelStreams; i++)
{
limitedUploads[i] = uploads[i];
}
uploads = limitedUploads;
}
for(String fileName : uploads)
{
target.addVariable(this, "target_" + fileName.hashCode(), streamVariables.get(fileName));
}
target.addVariable(this, "uploadFiles", uploads);
target.addVariable(this, "cancelFiles", cancelFiles.toArray(new String[0]));
}
@Override
public void setCaption(String caption)
{
buttonCaption = caption;
requestRepaint();
}
@Override
public void changeVariables(Object source, Map<String, Object> variables)
{
super.changeVariables(source, variables);
if(variables.containsKey("stopPolling"))
{
stopPolling = (Boolean) variables.get("stopPolling");
}
if(variables.containsKey("fileInfos"))
{
String[] fileInfoStrings = (String[]) variables.get("fileInfos");
fileInfos = new CopyOnWriteArrayList<FileInfo>();
for(String fileInfoString : fileInfoStrings)
{
if(fileInfoString != null)
{
FileInfo fileInfo = FileInfo.fromString(fileInfoString);
if(!fileInfos.contains(fileInfo))
{
fileInfos.add(fileInfo);
}
}
}
for(MultipleUploadHandler multipleUploadHandler : multipleUploadHandlers)
{
multipleUploadHandler.onUploadsSelected(new Vector<FileInfo>(fileInfos));
}
}
if(variables.containsKey("cancelFilesConfirmed"))
{
String[] cancelFilesConfirmed = (String[]) variables.get("cancelFilesConfirmed");
for(String file : cancelFilesConfirmed)
{
cancelFiles.remove(file);
}
}
}
/**
* Uploads the file with the given {@link FileInfo}.
*
* @param fileInfo The {@code FileInfo} of the file to be uploaded
* @return A {@code ProgressIndicator} for this upload procedure
*/
public ProgressIndicator upload(FileInfo fileInfo)
{
if(fileInfo == null)
{
throw new NullPointerException();
}
if(!files.containsKey(fileInfo.getName()))
{
File file = new File(tempFolderPath + "upload-" + UUID.randomUUID() + "-" + Math.abs(fileInfo.hashCode()) + ".tmp");
files.put(fileInfo.getName(), file);
MultipleUploadStreamVariable streamVariable = new MultipleUploadStreamVariable(fileInfo, file);
streamVariables.put(fileInfo.getName(), streamVariable);
if(!uploadFiles.contains(fileInfo.getName()))
{
uploadFiles.add(fileInfo.getName());
}
if(timer == null)
{
timer = new Timer();
timer.schedule(new TimerTask()
{
@Override
public void run()
{
requestRepaint();
if(timer != null && uploadFiles.size() == 0)
{
timer.cancel();
timer = null;
}
}
}, 0, 2000);
}
return streamVariable.getProgressIndicator();
}
else
{
MultipleUploadStreamVariable streamVariable = streamVariables.get(fileInfo.getName());
if(streamVariable != null)
{
return streamVariable.getProgressIndicator();
}
return null;
}
}
/**
* Cancels the upload with the given {@link FileInfo}.
*
* @param fileInfo The {@code FileInfo} of the upload to be cancelled
*/
public void cancelUpload(FileInfo fileInfo)
{
if(fileInfo == null)
{
throw new NullPointerException();
}
MultipleUploadStreamVariable multipleUploadStreamVariable = streamVariables.get(fileInfo.getName());
if(multipleUploadStreamVariable != null)
{
multipleUploadStreamVariable.cancelStreaming();
}
else
{
if(!cancelFiles.contains(fileInfo.getName()))
{
cancelFiles.add(fileInfo.getName());
}
}
requestRepaint();
}
/**
* Cancels the uploads with the given {@link FileInfo}s.
*
* @param fileInfos The {@code FileInfo}s of the uploads to be cancelled
*/
public void cancelUploads(List<FileInfo> fileInfos)
{
if(fileInfos == null)
{
throw new NullPointerException();
}
for(FileInfo fileInfo : fileInfos)
{
cancelUpload(fileInfo);
}
}
/**
* Cancels all uploads.
*/
public void cancelAllUploads()
{
if(fileInfos != null)
{
cancelUploads(new Vector<FileInfo>(fileInfos));
}
}
/**
* Finishes the current upload batch.
*/
public void finishCurrentUploadBatch()
{
stopPolling = true;
requestRepaint();
}
/**
* Adds the given {@link MultipleUploadHandler}.
*
* @param multipleUploadHandler A {@code MultipleUploadHandler}
*/
public void addMultipleUploadHandler(MultipleUploadHandler multipleUploadHandler)
{
multipleUploadHandlers.add(multipleUploadHandler);
}
/**
* This interface represents a handler for {@link MultipleUpload} events.
*/
public interface MultipleUploadHandler
{
/**
* Called when the files to be uploaded have been selected.
*
* @param fileInfos The {@code FileInfo}s of the files to be uploaded
*/
void onUploadsSelected(List<FileInfo> fileInfos);
/**
* Called when an upload procedure has been started.
*
* @param fileInfo The {@code FileInfo} of the corresponding file
*/
void onUploadStarted(FileInfo fileInfo);
/**
* Called when an upload procedure has been finished. <br/><br/>
*
* <i>Annotation:</i> The {@link File} object should be deleted if it is
* no longer used. This should be done by invoking the method
* {@link File#delete()}.
*
* @param fileInfo The {@code FileInfo} of the corresponding file
* @param file The corresponding {@code File} object
*/
void onUploadFinished(FileInfo fileInfo, File file);
/**
* Called when an upload procedure has been failed.
*
* @param fileInfo The {@code FileInfo} of the corresponding file
* @param exception The corresponding {@code Exception}
*/
void onUploadFailed(FileInfo fileInfo, Exception exception);
}
/**
* This class is an implementation class of the {@link StreamVariable}
* interface and serves as a special {@code StreamVariable} for a
* {@link MultipleUpload}.
*/
public class MultipleUploadStreamVariable implements com.vaadin.terminal.StreamVariable
{
private static final long serialVersionUID = 1772565622658765956L;
private final FileInfo fileInfo;
private final File file;
private final ProgressIndicator progressIndicator;
private boolean interrupted = false;
/**
* Constructs a {@link MultipleUploadStreamVariable} for the given data.
*
* @param fileInfo The {@code FileInfo} of the file to be uploaded
* @param file The {@code File} object
*/
public MultipleUploadStreamVariable(FileInfo fileInfo, File file)
{
this.fileInfo = fileInfo;
this.file = file;
progressIndicator = new ProgressIndicator(0.0f);
progressIndicator.setPollingInterval(500);
}
@Override
public OutputStream getOutputStream()
{
try
{
return new FileOutputStream(file);
}
catch(FileNotFoundException e)
{
e.printStackTrace();
return null;
}
}
@Override
public boolean listenProgress()
{
return true;
}
@Override
public void onProgress(StreamingProgressEvent event)
{
progressIndicator.setValue((double) event.getBytesReceived() / (double) event.getContentLength());
}
@Override
public void streamingStarted(StreamingStartEvent event)
{
for(MultipleUploadHandler multipleUploadHandler : multipleUploadHandlers)
{
multipleUploadHandler.onUploadStarted(fileInfo);
}
}
@Override
public void streamingFinished(StreamingEndEvent event)
{
files.remove(fileInfo.getName());
streamVariables.remove(fileInfo.getName());
uploadFiles.remove(fileInfo.getName());
for(MultipleUploadHandler multipleUploadHandler : multipleUploadHandlers)
{
multipleUploadHandler.onUploadFinished(fileInfo, file);
}
progressIndicator.setPollingInterval(Integer.MAX_VALUE);
}
@Override
public void streamingFailed(StreamingErrorEvent event)
{
File file = files.remove(fileInfo.getName());
if(file != null && file.exists())
{
file.delete();
}
streamVariables.remove(fileInfo.getName());
uploadFiles.remove(fileInfo.getName());
for(MultipleUploadHandler multipleUploadHandler : multipleUploadHandlers)
{
multipleUploadHandler.onUploadFailed(fileInfo, event.getException());
}
progressIndicator.setPollingInterval(Integer.MAX_VALUE);
}
@Override
public boolean isInterrupted()
{
return interrupted;
}
/**
* Returns the corresponding {@link ProgressIndicator}.
*
* @return The corresponding {@code ProgressIndicator}
*/
public ProgressIndicator getProgressIndicator()
{
return progressIndicator;
}
/**
* Cancels the current streaming procedure.
*/
public void cancelStreaming()
{
interrupted = true;
}
}
}