package er.jquery.widgets;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.webobjects.appserver.WOActionResults;
import com.webobjects.appserver.WOApplication;
import com.webobjects.appserver.WOComponent;
import com.webobjects.appserver.WOContext;
import com.webobjects.appserver.WORequest;
import com.webobjects.appserver.WOResponse;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSData;
import com.webobjects.foundation.NSMutableArray;
import er.extensions.appserver.ERXResponseRewriter;
import er.extensions.appserver.ajax.ERXAjaxApplication;
import er.extensions.foundation.ERXFileUtilities;
import er.extensions.foundation.ERXProperties;
import er.extensions.foundation.ERXStringUtilities;
/**
* Encapsulation of @see <a href="http://valums.com/ajax-upload/">Ajax Upload</a>
* @binding onChange
* @binding onSubmit
* @binding onComplete
* @binding allowedExtensions E.g: "['jpg', 'jpeg', 'png', 'gif']"
*
* @property useUnobtrusively For Unobtrusive Javascript programming. Default it is ON.
* @property WOFileUpload.sizeLimit Max file upload size permitted
*
* @author mendis
*
* NOTES:
*
* 1. The progress indicator doesn't work properly with WODefaultAdaptor. If you want a progress % indicator, you may
* need to use an alternative WOAdaptor. e.g: ERWOAdaptor
*
* 2. Use of ERXSession breaks IE6-8 compatibility. Use WOSession instead.
*
*/
public abstract class FileUploader extends WOComponent {
private static final Logger log = LoggerFactory.getLogger(FileUploader.class);
private static boolean useUnobtrusively = ERXProperties.booleanForKeyWithDefault("er.jquery.useUnobtrusively", true);
public FileUploader(WOContext aContext) {
super(aContext);
}
private RuntimeException exception;
/*
* Bindings/API
*/
public static interface Bindings {
public static final String id = "id";
public static final String onChange = "onChange";
public static final String onSubmit = "onSubmit";
public static final String onComplete = "onComplete";
public static final String filePath = "filePath";
public static final String data = "data";
public static final String inputStream = "inputStream";
public static final String outputStream = "outputStream";
public static final String finalFilePath = "finalFilePath";
public static final String streamToFilePath = "streamToFilePath";
public static final String allowedExtensions = "allowedExtensions";
}
/*
* Form keys
*/
public static interface FormKeys {
public static final String qqfile = "qqfile";
public static final String qqfilename = "qqfile.filename";
public static final String _forceFormSubmitted = "_forceFormSubmitted";
}
@Override
public boolean synchronizesVariablesWithBindings() {
return false;
}
// accessors
public String id() {
return _id() == null ? "fu" + ERXStringUtilities.safeIdentifierName(context().elementID()) : _id();
}
private String _id() {
return (String) valueForBinding(Bindings.id);
}
private String _script() {
return "new qq.FileUploader({" + options() + "});";
}
public String script() {
return isAjax() ? _script() : "jQuery(function(){ " + _script() + " });";
}
private boolean isAjax() {
return ERXAjaxApplication.isAjaxRequest(context().request());
}
private String finalFilePath() {
return (String) valueForBinding(Bindings.finalFilePath);
}
public static Integer maxFileSize() {
return Integer.getInteger("WOFileUpload.sizeLimit", 1024*1024*100);
}
/*
* An array of options for File Uploader
*/
protected NSArray<String> _options() {
NSMutableArray<String> _options = new NSMutableArray<String>("action:'" + href() + "'");
// add options
_options.add("element: $('#" + id() + "')[0]");
_options.add("params: { " + FormKeys._forceFormSubmitted + ": '" + id() + "'}"); // TODO params binding
_options.add("sizeLimit: " + maxFileSize());
if (hasBinding(Bindings.onChange)) _options.add("onChange:" + valueForBinding(Bindings.onChange));
if (hasBinding(Bindings.onComplete)) _options.add("onComplete:" + valueForBinding(Bindings.onComplete));
if (hasBinding(Bindings.onSubmit)) _options.add("onSubmit:" + valueForBinding(Bindings.onSubmit));
if (hasBinding(Bindings.allowedExtensions)) _options.add("allowedExtensions:" + valueForBinding(Bindings.allowedExtensions));
return _options.immutableClone();
}
public String options() {
return _options().componentsJoinedByString(", ");
}
protected void setFilePath(String aPath) {
setValueForBinding(aPath, Bindings.filePath);
}
protected void setData(NSData data) {
setValueForBinding(data, Bindings.data);
}
private String href() {
return context().componentActionURL(application().ajaxRequestHandlerKey());
}
// R&R
@Override
public void appendToResponse(WOResponse response, WOContext context) {
super.appendToResponse(response, context);
if (!useUnobtrusively) {
ERXResponseRewriter.addScriptResourceInHead(response, context, "ERJQuery", "jquery-1.4.2.min.js");
ERXResponseRewriter.addScriptResourceInHead(response, context, "ERJQuery", "fileuploader.js");
}
}
@Override
public WOActionResults invokeAction(WORequest request, WOContext context) {
if (context.senderID().equals(context.elementID())) {
WOResponse response = WOApplication.application().createResponseInContext(context);
if (exception != null) {
response.appendContentString("{\"error\":" + exception.getMessage() + "}");
} else {
response.appendContentString("{\"success\":true}");
} return response;
} else return super.invokeAction(request, context);
}
@Override
public void takeValuesFromRequest(WORequest request, WOContext context) {
super.takeValuesFromRequest(request, context);
if (context.senderID().equals(context.elementID())) {
String aFileName;
InputStream anInputStream;
if (request.formValueForKey(FormKeys.qqfilename) != null) {
aFileName = (String) request.formValueForKey(FormKeys.qqfilename);
NSData data = (NSData) request.formValueForKey(FormKeys.qqfile);
anInputStream = data.stream();
} else if (request.formValueForKey(FormKeys.qqfile) != null) {
aFileName = (String) request.formValueForKey(FormKeys.qqfile);
anInputStream = (request.contentInputStream() != null) ? request.contentInputStream() : request.content().stream();
} else {
log.error("Unable to obtain filename from form values: {}", request.formValueKeys());
return;
}
// filepath
if (hasBinding(Bindings.filePath)) {
setFilePath(aFileName);
}
// file data
if (hasBinding(Bindings.data)) {
if (hasBinding(Bindings.filePath))
setData(request.content());
} else {
if (aFileName != null && aFileName.length() > 0) {
if (hasBinding(Bindings.inputStream)) {
setValueForBinding(anInputStream, Bindings.inputStream);
} else {
String localFilePath = null;
File tempFile = null;
if (hasBinding(Bindings.outputStream)) {
OutputStream anOutputStream = (OutputStream) valueForBinding(Bindings.outputStream);
try {
ERXFileUtilities.writeInputStreamToOutputStream(anInputStream, anOutputStream);
} catch (IOException e) {
exception = new RuntimeException("Couldn't write input stream to output stream: " + e);
throw exception;
} finally {
try { anOutputStream.close(); } catch (IOException e) {}
try { anInputStream.close(); } catch (IOException e) {}
}
} else {
if (hasBinding(Bindings.finalFilePath)) {
localFilePath = finalFilePath();
setValueForBinding(null, Bindings.finalFilePath);
}
try {
tempFile = ERXFileUtilities.writeInputStreamToTempFile(anInputStream, context.session().sessionID(), ".tmp");
} catch (IOException e) {
exception = new RuntimeException("Couldn't write input stream to temp file: " + e);
throw exception;
} finally {
try { anInputStream.close(); } catch (IOException e) {}
}
}
if (hasBinding(Bindings.streamToFilePath)) {
if (localFilePath == null) localFilePath = (String) valueForBinding(Bindings.streamToFilePath);
try {
ERXFileUtilities.renameTo(tempFile, new File(localFilePath));
setValueForBinding(localFilePath, Bindings.finalFilePath);
} catch (Exception e) {
setValueForBinding(tempFile.getPath(), Bindings.finalFilePath);
exception = new RuntimeException("Couldn't rename temp file: " + e);
throw exception;
}
}
}
} else {
if (hasBinding(Bindings.inputStream))
setValueForBinding(null, Bindings.inputStream);
if (hasBinding(Bindings.finalFilePath))
setValueForBinding(null, Bindings.finalFilePath);
byte buffer[] = new byte[128];
try {
while (anInputStream.read(buffer) != -1) ;
}
catch (IOException e) {
exception = new RuntimeException("Error skipping empty file upload: " + e);
throw exception;
} finally {
try { anInputStream.close(); } catch (IOException e) {}
}
}
}
}
}
}